Merge "Added a broadcast intent for clients to listen for country changes"
diff --git a/Android.bp b/Android.bp
index ab7d709..afdcadd 100644
--- a/Android.bp
+++ b/Android.bp
@@ -49,6 +49,9 @@
         "rs/java/**/*.java",
 
         ":framework-javastream-protos",
+        // TODO: Resolve circular library dependency and remove media1-srcs and mediasession2-srcs
+        ":media1-srcs",
+        ":mediasession2-srcs",
 
         "core/java/android/accessibilityservice/IAccessibilityServiceConnection.aidl",
         "core/java/android/accessibilityservice/IAccessibilityServiceClient.aidl",
@@ -268,6 +271,7 @@
         "core/java/android/os/IThermalService.aidl",
         "core/java/android/os/IUpdateLock.aidl",
         "core/java/android/os/IUserManager.aidl",
+        ":libvibrator_aidl",
         "core/java/android/os/IVibratorService.aidl",
         "core/java/android/os/storage/IStorageManager.aidl",
         "core/java/android/os/storage/IStorageEventListener.aidl",
@@ -471,14 +475,11 @@
         "media/java/android/media/IAudioRoutesObserver.aidl",
         "media/java/android/media/IAudioService.aidl",
         "media/java/android/media/IAudioServerStateDispatcher.aidl",
-        "media/java/android/media/IMediaController2.aidl",
         "media/java/android/media/IMediaHTTPConnection.aidl",
         "media/java/android/media/IMediaHTTPService.aidl",
         "media/java/android/media/IMediaResourceMonitor.aidl",
         "media/java/android/media/IMediaRouterClient.aidl",
         "media/java/android/media/IMediaRouterService.aidl",
-        "media/java/android/media/IMediaSession2.aidl",
-        "media/java/android/media/IMediaSession2Service.aidl",
         "media/java/android/media/IMediaScannerListener.aidl",
         "media/java/android/media/IMediaScannerService.aidl",
         "media/java/android/media/IPlaybackConfigDispatcher.aidl",
@@ -504,11 +505,7 @@
         "media/java/android/media/session/ICallback.aidl",
         "media/java/android/media/session/IOnMediaKeyListener.aidl",
         "media/java/android/media/session/IOnVolumeKeyLongPressListener.aidl",
-        "media/java/android/media/session/ISession.aidl",
         "media/java/android/media/session/ISession2TokensListener.aidl",
-        "media/java/android/media/session/ISessionCallback.aidl",
-        "media/java/android/media/session/ISessionController.aidl",
-        "media/java/android/media/session/ISessionControllerCallback.aidl",
         "media/java/android/media/session/ISessionManager.aidl",
         "media/java/android/media/soundtrigger/ISoundTriggerDetectionService.aidl",
         "media/java/android/media/soundtrigger/ISoundTriggerDetectionServiceClient.aidl",
@@ -523,8 +520,6 @@
         "media/java/android/media/tv/ITvInputSessionCallback.aidl",
         "media/java/android/media/tv/ITvRemoteProvider.aidl",
         "media/java/android/media/tv/ITvRemoteServiceInput.aidl",
-        "media/java/android/service/media/IMediaBrowserService.aidl",
-        "media/java/android/service/media/IMediaBrowserServiceCallbacks.aidl",
         "telecomm/java/com/android/internal/telecom/ICallRedirectionAdapter.aidl",
         "telecomm/java/com/android/internal/telecom/ICallRedirectionService.aidl",
         "telecomm/java/com/android/internal/telecom/ICallScreeningAdapter.aidl",
@@ -567,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",
@@ -637,6 +633,7 @@
         "wifi/java/android/net/wifi/ISoftApCallback.aidl",
         "wifi/java/android/net/wifi/ITrafficStateCallback.aidl",
         "wifi/java/android/net/wifi/IWifiManager.aidl",
+        "wifi/java/android/net/wifi/IWifiUsabilityStatsListener.aidl",
         "wifi/java/android/net/wifi/aware/IWifiAwareDiscoverySessionCallback.aidl",
         "wifi/java/android/net/wifi/aware/IWifiAwareEventCallback.aidl",
         "wifi/java/android/net/wifi/aware/IWifiAwareMacAddressProvider.aidl",
@@ -691,6 +688,7 @@
             "location/java",
             "lowpan/java",
             "media/java",
+            "media/apex/java",
             "media/mca/effect/java",
             "media/mca/filterfw/java",
             "media/mca/filterpacks/java",
@@ -723,8 +721,6 @@
     exclude_srcs: [
         // See comment on framework-atb-backward-compatibility module below
         "core/java/android/content/pm/AndroidTestBaseUpdater.java",
-        // See comment on framework-oahl-backward-compatibility module below
-        "core/java/android/content/pm/OrgApacheHttpLegacyUpdater.java",
     ],
 
     no_framework_libs: true,
@@ -732,7 +728,7 @@
         "ext",
     ],
 
-    jarjar_rules: ":framework-hidl-jarjar",
+    jarjar_rules: "jarjar_rules_hidl.txt",
 
     static_libs: [
         "apex_aidl_interface-java",
@@ -744,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",
@@ -751,15 +756,13 @@
         "android.hardware.tv.input-V1.0-java-constants",
         "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",
@@ -794,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 {
@@ -822,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
@@ -915,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 {
@@ -1267,7 +1285,7 @@
         ":non_openjdk_javadoc_files",
         ":android_icu4j_src_files_for_docs",
         ":conscrypt_public_api_files",
-        ":media2-srcs",
+        ":media-srcs-without-aidls",
         "test-mock/src/**/*.java",
         "test-runner/src/**/*.java",
     ],
@@ -1329,7 +1347,7 @@
         ":non_openjdk_javadoc_files",
         ":android_icu4j_src_files_for_docs",
         ":conscrypt_public_api_files",
-        ":media2-srcs",
+        ":media-srcs-without-aidls",
     ],
     srcs_lib: "framework",
     srcs_lib_whitelist_dirs: frameworks_base_subdirs,
@@ -1775,6 +1793,7 @@
     name: "framework-media-annotation-srcs",
     srcs: [
         "core/java/android/annotation/CallbackExecutor.java",
+        "core/java/android/annotation/CallSuper.java",
         "core/java/android/annotation/DrawableRes.java",
         "core/java/android/annotation/IntDef.java",
         "core/java/android/annotation/LongDef.java",
@@ -1783,6 +1802,7 @@
         "core/java/android/annotation/RequiresPermission.java",
         "core/java/android/annotation/SdkConstant.java",
         "core/java/android/annotation/StringDef.java",
+        "core/java/android/annotation/SystemApi.java",
         "core/java/android/annotation/TestApi.java",
         "core/java/android/annotation/UnsupportedAppUsage.java",
         "core/java/com/android/internal/annotations/GuardedBy.java",
diff --git a/apct-tests/perftests/core/src/android/textclassifier/TextClassifierPerfTest.java b/apct-tests/perftests/core/src/android/textclassifier/TextClassifierPerfTest.java
index a7a81f2..767434d 100644
--- a/apct-tests/perftests/core/src/android/textclassifier/TextClassifierPerfTest.java
+++ b/apct-tests/perftests/core/src/android/textclassifier/TextClassifierPerfTest.java
@@ -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 410d410..b10137c 100644
--- a/api/current.txt
+++ b/api/current.txt
@@ -5101,7 +5101,7 @@
   }
 
   public class KeyguardManager {
-    method public android.content.Intent createConfirmDeviceCredentialIntent(CharSequence, CharSequence);
+    method @Deprecated public android.content.Intent createConfirmDeviceCredentialIntent(CharSequence, CharSequence);
     method @Deprecated @RequiresPermission(android.Manifest.permission.DISABLE_KEYGUARD) public void exitKeyguardSecurely(android.app.KeyguardManager.OnKeyguardExitResult);
     method @Deprecated public boolean inKeyguardRestrictedInputMode();
     method public boolean isDeviceLocked();
@@ -5466,9 +5466,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 +5479,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);
   }
 
@@ -5793,6 +5797,7 @@
     method public java.util.List<android.app.NotificationChannel> getNotificationChannels();
     method @Nullable public String getNotificationDelegate();
     method public android.app.NotificationManager.Policy getNotificationPolicy();
+    method public boolean isNotificationAssistantAccessGranted(android.content.ComponentName);
     method public boolean isNotificationListenerAccessGranted(android.content.ComponentName);
     method public boolean isNotificationPolicyAccessGranted();
     method public void notify(int, android.app.Notification);
@@ -6578,7 +6583,6 @@
   }
 
   public class DevicePolicyManager {
-    method public void addCrossProfileCalendarPackage(@NonNull android.content.ComponentName, @NonNull String);
     method public void addCrossProfileIntentFilter(@NonNull android.content.ComponentName, android.content.IntentFilter, int);
     method public boolean addCrossProfileWidgetProvider(@NonNull android.content.ComponentName, String);
     method public int addOverrideApn(@NonNull android.content.ComponentName, @NonNull android.telephony.data.ApnSetting);
@@ -6608,7 +6612,7 @@
     method public boolean getBluetoothContactSharingDisabled(@NonNull android.content.ComponentName);
     method public boolean getCameraDisabled(@Nullable android.content.ComponentName);
     method @Deprecated @Nullable public String getCertInstallerPackage(@NonNull android.content.ComponentName) throws java.lang.SecurityException;
-    method @NonNull public java.util.Set<java.lang.String> getCrossProfileCalendarPackages(@NonNull android.content.ComponentName);
+    method @Nullable public java.util.Set<java.lang.String> getCrossProfileCalendarPackages(@NonNull android.content.ComponentName);
     method public boolean getCrossProfileCallerIdDisabled(@NonNull android.content.ComponentName);
     method public boolean getCrossProfileContactsSearchDisabled(@NonNull android.content.ComponentName);
     method @NonNull public java.util.List<java.lang.String> getCrossProfileWidgetProviders(@NonNull android.content.ComponentName);
@@ -6698,7 +6702,6 @@
     method public int logoutUser(@NonNull android.content.ComponentName);
     method public void reboot(@NonNull android.content.ComponentName);
     method public void removeActiveAdmin(@NonNull android.content.ComponentName);
-    method public boolean removeCrossProfileCalendarPackage(@NonNull android.content.ComponentName, @NonNull String);
     method public boolean removeCrossProfileWidgetProvider(@NonNull android.content.ComponentName, String);
     method public boolean removeKeyPair(@Nullable android.content.ComponentName, @NonNull String);
     method public boolean removeOverrideApn(@NonNull android.content.ComponentName, int);
@@ -6720,6 +6723,7 @@
     method public void setBluetoothContactSharingDisabled(@NonNull android.content.ComponentName, boolean);
     method public void setCameraDisabled(@NonNull android.content.ComponentName, boolean);
     method @Deprecated public void setCertInstallerPackage(@NonNull android.content.ComponentName, @Nullable String) throws java.lang.SecurityException;
+    method public void setCrossProfileCalendarPackages(@NonNull android.content.ComponentName, @Nullable java.util.Set<java.lang.String>);
     method public void setCrossProfileCallerIdDisabled(@NonNull android.content.ComponentName, boolean);
     method public void setCrossProfileContactsSearchDisabled(@NonNull android.content.ComponentName, boolean);
     method public void setDelegatedScopes(@NonNull android.content.ComponentName, @NonNull String, @NonNull java.util.List<java.lang.String>);
@@ -6787,7 +6791,7 @@
     method public void uninstallCaCert(@Nullable android.content.ComponentName, byte[]);
     method public boolean updateOverrideApn(@NonNull android.content.ComponentName, int, @NonNull android.telephony.data.ApnSetting);
     method public void wipeData(int);
-    method public void wipeData(int, CharSequence);
+    method public void wipeData(int, @NonNull CharSequence);
     field public static final String ACTION_ADD_DEVICE_ADMIN = "android.app.action.ADD_DEVICE_ADMIN";
     field public static final String ACTION_ADMIN_POLICY_COMPLIANCE = "android.app.action.ADMIN_POLICY_COMPLIANCE";
     field public static final String ACTION_APPLICATION_DELEGATION_SCOPES_CHANGED = "android.app.action.APPLICATION_DELEGATION_SCOPES_CHANGED";
@@ -6931,6 +6935,7 @@
     field public static final int WIPE_EUICC = 4; // 0x4
     field public static final int WIPE_EXTERNAL_STORAGE = 1; // 0x1
     field public static final int WIPE_RESET_PROTECTION_DATA = 2; // 0x2
+    field public static final int WIPE_SILENTLY = 8; // 0x8
   }
 
   public abstract static class DevicePolicyManager.InstallUpdateCallback {
@@ -8610,6 +8615,13 @@
     method @Deprecated @BinderThread public void onHealthChannelStateChange(android.bluetooth.BluetoothHealthAppConfiguration, android.bluetooth.BluetoothDevice, int, int, android.os.ParcelFileDescriptor, int);
   }
 
+  public final class BluetoothHearingAid implements android.bluetooth.BluetoothProfile {
+    method public java.util.List<android.bluetooth.BluetoothDevice> getConnectedDevices();
+    method public int getConnectionState(android.bluetooth.BluetoothDevice);
+    method public java.util.List<android.bluetooth.BluetoothDevice> getDevicesMatchingConnectionStates(int[]);
+    field public static final String ACTION_CONNECTION_STATE_CHANGED = "android.bluetooth.hearingaid.profile.action.CONNECTION_STATE_CHANGED";
+  }
+
   public final class BluetoothHidDevice implements android.bluetooth.BluetoothProfile {
     method public boolean connect(android.bluetooth.BluetoothDevice);
     method public boolean disconnect(android.bluetooth.BluetoothDevice);
@@ -8705,6 +8717,7 @@
     field public static final int GATT_SERVER = 8; // 0x8
     field public static final int HEADSET = 1; // 0x1
     field @Deprecated public static final int HEALTH = 3; // 0x3
+    field public static final int HEARING_AID = 21; // 0x15
     field public static final int HID_DEVICE = 19; // 0x13
     field public static final int SAP = 10; // 0xa
     field public static final int STATE_CONNECTED = 2; // 0x2
@@ -10213,6 +10226,7 @@
     field public static final String ACTION_MEDIA_REMOVED = "android.intent.action.MEDIA_REMOVED";
     field public static final String ACTION_MEDIA_SCANNER_FINISHED = "android.intent.action.MEDIA_SCANNER_FINISHED";
     field public static final String ACTION_MEDIA_SCANNER_SCAN_FILE = "android.intent.action.MEDIA_SCANNER_SCAN_FILE";
+    field public static final String ACTION_MEDIA_SCANNER_SCAN_VOLUME = "android.intent.action.MEDIA_SCANNER_SCAN_VOLUME";
     field public static final String ACTION_MEDIA_SCANNER_STARTED = "android.intent.action.MEDIA_SCANNER_STARTED";
     field public static final String ACTION_MEDIA_SHARED = "android.intent.action.MEDIA_SHARED";
     field public static final String ACTION_MEDIA_UNMOUNTABLE = "android.intent.action.MEDIA_UNMOUNTABLE";
@@ -11210,6 +11224,7 @@
 
   public class LauncherApps {
     method public java.util.List<android.content.pm.LauncherActivityInfo> getActivityList(String, android.os.UserHandle);
+    method @Nullable public android.content.pm.LauncherApps.AppUsageLimit getAppUsageLimit(String, android.os.UserHandle);
     method public android.content.pm.ApplicationInfo getApplicationInfo(@NonNull String, int, @NonNull android.os.UserHandle) throws android.content.pm.PackageManager.NameNotFoundException;
     method public android.content.pm.LauncherApps.PinItemRequest getPinItemRequest(android.content.Intent);
     method public java.util.List<android.os.UserHandle> getProfiles();
@@ -11237,6 +11252,14 @@
     field public static final String EXTRA_PIN_ITEM_REQUEST = "android.content.pm.extra.PIN_ITEM_REQUEST";
   }
 
+  public static final class LauncherApps.AppUsageLimit implements android.os.Parcelable {
+    method public int describeContents();
+    method public long getTotalUsageLimit();
+    method public long getUsageRemaining();
+    method public void writeToParcel(android.os.Parcel, int);
+    field public static final android.os.Parcelable.Creator<android.content.pm.LauncherApps.AppUsageLimit> CREATOR;
+  }
+
   public abstract static class LauncherApps.Callback {
     ctor public LauncherApps.Callback();
     method public abstract void onPackageAdded(String, android.os.UserHandle);
@@ -11412,6 +11435,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();
@@ -11433,6 +11457,7 @@
     method public void setAppIcon(@Nullable android.graphics.Bitmap);
     method public void setAppLabel(@Nullable CharSequence);
     method public void setAppPackageName(@Nullable String);
+    method public void setInstallAsApex();
     method public void setInstallLocation(int);
     method public void setInstallReason(int);
     method public void setMultiPackage();
@@ -11675,6 +11700,7 @@
     field public static final String FEATURE_TELEPHONY_CDMA = "android.hardware.telephony.cdma";
     field public static final String FEATURE_TELEPHONY_EUICC = "android.hardware.telephony.euicc";
     field public static final String FEATURE_TELEPHONY_GSM = "android.hardware.telephony.gsm";
+    field public static final String FEATURE_TELEPHONY_IMS = "android.hardware.telephony.ims";
     field public static final String FEATURE_TELEPHONY_MBMS = "android.hardware.telephony.mbms";
     field @Deprecated public static final String FEATURE_TELEVISION = "android.hardware.type.television";
     field public static final String FEATURE_TOUCHSCREEN = "android.hardware.touchscreen";
@@ -12486,6 +12512,7 @@
     method public int getInt(int);
     method public long getLong(int);
     method public android.net.Uri getNotificationUri();
+    method public default java.util.List<android.net.Uri> getNotificationUris();
     method public int getPosition();
     method public short getShort(int);
     method public String getString(int);
@@ -12509,6 +12536,7 @@
     method public android.os.Bundle respond(android.os.Bundle);
     method public void setExtras(android.os.Bundle);
     method public void setNotificationUri(android.content.ContentResolver, android.net.Uri);
+    method public default void setNotificationUris(@NonNull android.content.ContentResolver, @NonNull java.util.List<android.net.Uri>);
     method public void unregisterContentObserver(android.database.ContentObserver);
     method public void unregisterDataSetObserver(android.database.DataSetObserver);
     field public static final int FIELD_TYPE_BLOB = 4; // 0x4
@@ -13921,8 +13949,6 @@
   @AnyThread public abstract class ColorSpace {
     method @NonNull public static android.graphics.ColorSpace adapt(@NonNull android.graphics.ColorSpace, @NonNull @Size(min=2, max=3) float[]);
     method @NonNull public static android.graphics.ColorSpace adapt(@NonNull android.graphics.ColorSpace, @NonNull @Size(min=2, max=3) float[], @NonNull android.graphics.ColorSpace.Adaptation);
-    method @NonNull @Size(3) public static float[] cctToIlluminantdXyz(@IntRange(from=1) int);
-    method @NonNull @Size(9) public static float[] chromaticAdaptation(@NonNull android.graphics.ColorSpace.Adaptation, @NonNull @Size(min=2, max=3) float[], @NonNull @Size(min=2, max=3) float[]);
     method @NonNull public static android.graphics.ColorSpace.Connector connect(@NonNull android.graphics.ColorSpace, @NonNull android.graphics.ColorSpace);
     method @NonNull public static android.graphics.ColorSpace.Connector connect(@NonNull android.graphics.ColorSpace, @NonNull android.graphics.ColorSpace, @NonNull android.graphics.ColorSpace.RenderIntent);
     method @NonNull public static android.graphics.ColorSpace.Connector connect(@NonNull android.graphics.ColorSpace);
@@ -16472,6 +16498,7 @@
     ctor public BiometricPrompt.Builder(android.content.Context);
     method public android.hardware.biometrics.BiometricPrompt build();
     method public android.hardware.biometrics.BiometricPrompt.Builder setDescription(@NonNull CharSequence);
+    method public android.hardware.biometrics.BiometricPrompt.Builder setEnableFallback(boolean);
     method public android.hardware.biometrics.BiometricPrompt.Builder setNegativeButton(@NonNull CharSequence, @NonNull java.util.concurrent.Executor, @NonNull android.content.DialogInterface.OnClickListener);
     method public android.hardware.biometrics.BiometricPrompt.Builder setRequireConfirmation(boolean);
     method public android.hardware.biometrics.BiometricPrompt.Builder setSubtitle(@NonNull CharSequence);
@@ -17078,6 +17105,7 @@
     field public static final android.hardware.camera2.CaptureResult.Key<float[]> LENS_POSE_TRANSLATION;
     field @Deprecated public static final android.hardware.camera2.CaptureResult.Key<float[]> LENS_RADIAL_DISTORTION;
     field public static final android.hardware.camera2.CaptureResult.Key<java.lang.Integer> LENS_STATE;
+    field public static final android.hardware.camera2.CaptureResult.Key<java.lang.String> LOGICAL_MULTI_CAMERA_ACTIVE_PHYSICAL_ID;
     field public static final android.hardware.camera2.CaptureResult.Key<java.lang.Integer> NOISE_REDUCTION_MODE;
     field public static final android.hardware.camera2.CaptureResult.Key<java.lang.Float> REPROCESS_EFFECTIVE_EXPOSURE_FACTOR;
     field public static final android.hardware.camera2.CaptureResult.Key<java.lang.Byte> REQUEST_PIPELINE_DEPTH;
@@ -23495,6 +23523,8 @@
     method public int setBufferSizeInFrames(int);
     method public int setLoopPoints(int, int, int);
     method public int setNotificationMarkerPosition(int);
+    method public void setOffloadDelayPadding(int, int);
+    method public void setOffloadEndOfStream();
     method public int setPlaybackHeadPosition(int);
     method public void setPlaybackParams(@NonNull android.media.PlaybackParams);
     method public void setPlaybackPositionUpdateListener(android.media.AudioTrack.OnPlaybackPositionUpdateListener);
@@ -25409,6 +25439,7 @@
     method public void addOnRoutingChangedListener(android.media.AudioRouting.OnRoutingChangedListener, android.os.Handler);
     method public Object attachAuxEffect(int);
     method public boolean cancelCommand(@NonNull Object);
+    method public void clearDrmEventCallback();
     method public Object clearNextDataSources();
     method public void clearPendingCommands();
     method public void close();
@@ -25925,7 +25956,6 @@
     method @NonNull public abstract android.media.MediaSession2 onGetPrimarySession();
     method @Nullable public abstract android.media.MediaSession2Service.MediaNotification onUpdateNotification(@NonNull android.media.MediaSession2);
     method public final void removeSession(@NonNull android.media.MediaSession2);
-    field public static final String SERVICE_INTERFACE = "android.media.MediaSession2Service";
   }
 
   public static class MediaSession2Service.MediaNotification {
@@ -27448,6 +27478,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);
   }
@@ -28470,6 +28501,7 @@
   public class ConnectivityManager {
     method public void addDefaultNetworkActiveListener(android.net.ConnectivityManager.OnNetworkActiveListener);
     method public boolean bindProcessToNetwork(@Nullable android.net.Network);
+    method public android.net.SocketKeepalive createSocketKeepalive(@NonNull android.net.Network, @NonNull android.net.IpSecManager.UdpEncapsulationSocket, @NonNull java.net.InetAddress, @NonNull java.net.InetAddress, @NonNull java.util.concurrent.Executor, @NonNull android.net.SocketKeepalive.Callback);
     method @RequiresPermission(android.Manifest.permission.ACCESS_NETWORK_STATE) @Nullable public android.net.Network getActiveNetwork();
     method @Deprecated @Nullable @RequiresPermission(android.Manifest.permission.ACCESS_NETWORK_STATE) public android.net.NetworkInfo getActiveNetworkInfo();
     method @Deprecated @NonNull @RequiresPermission(android.Manifest.permission.ACCESS_NETWORK_STATE) public android.net.NetworkInfo[] getAllNetworkInfo();
@@ -28974,6 +29006,29 @@
     ctor public SSLSessionCache(android.content.Context);
   }
 
+  public abstract class SocketKeepalive implements java.lang.AutoCloseable {
+    method public final void close();
+    method public final void start(@IntRange(from=0xa, to=0xe10) int);
+    method public final void stop();
+    field public static final int ERROR_HARDWARE_ERROR = -31; // 0xffffffe1
+    field public static final int ERROR_HARDWARE_UNSUPPORTED = -30; // 0xffffffe2
+    field public static final int ERROR_INVALID_INTERVAL = -24; // 0xffffffe8
+    field public static final int ERROR_INVALID_IP_ADDRESS = -21; // 0xffffffeb
+    field public static final int ERROR_INVALID_LENGTH = -23; // 0xffffffe9
+    field public static final int ERROR_INVALID_NETWORK = -20; // 0xffffffec
+    field public static final int ERROR_INVALID_PORT = -22; // 0xffffffea
+    field public static final int ERROR_INVALID_SOCKET = -25; // 0xffffffe7
+    field public static final int ERROR_SOCKET_NOT_IDLE = -26; // 0xffffffe6
+  }
+
+  public static class SocketKeepalive.Callback {
+    ctor public SocketKeepalive.Callback();
+    method public void onDataReceived();
+    method public void onError(int);
+    method public void onStarted();
+    method public void onStopped();
+  }
+
   public class TrafficStats {
     ctor public TrafficStats();
     method public static void clearThreadStatsTag();
@@ -29184,6 +29239,7 @@
     method public android.os.ParcelFileDescriptor establish();
     method public android.net.VpnService.Builder setBlocking(boolean);
     method public android.net.VpnService.Builder setConfigureIntent(android.app.PendingIntent);
+    method public android.net.VpnService.Builder setHttpProxy(android.net.ProxyInfo);
     method public android.net.VpnService.Builder setMtu(int);
     method public android.net.VpnService.Builder setSession(String);
     method public android.net.VpnService.Builder setUnderlyingNetworks(android.net.Network[]);
@@ -29792,7 +29848,7 @@
     method @Deprecated public boolean disableNetwork(int);
     method @Deprecated public boolean disconnect();
     method @Deprecated public boolean enableNetwork(int, boolean);
-    method @Deprecated @RequiresPermission(allOf={android.Manifest.permission.ACCESS_COARSE_LOCATION, android.Manifest.permission.ACCESS_WIFI_STATE}) public java.util.List<android.net.wifi.WifiConfiguration> getConfiguredNetworks();
+    method @Deprecated @RequiresPermission(allOf={android.Manifest.permission.ACCESS_FINE_LOCATION, android.Manifest.permission.ACCESS_WIFI_STATE}) public java.util.List<android.net.wifi.WifiConfiguration> getConfiguredNetworks();
     method public android.net.wifi.WifiInfo getConnectionInfo();
     method public android.net.DhcpInfo getDhcpInfo();
     method public int getMaxNumberOfNetworkSuggestionsPerApp();
@@ -30283,26 +30339,26 @@
   }
 
   public class WifiP2pManager {
-    method @RequiresPermission(android.Manifest.permission.ACCESS_COARSE_LOCATION) public void addLocalService(android.net.wifi.p2p.WifiP2pManager.Channel, android.net.wifi.p2p.nsd.WifiP2pServiceInfo, android.net.wifi.p2p.WifiP2pManager.ActionListener);
+    method @RequiresPermission(android.Manifest.permission.ACCESS_FINE_LOCATION) public void addLocalService(android.net.wifi.p2p.WifiP2pManager.Channel, android.net.wifi.p2p.nsd.WifiP2pServiceInfo, android.net.wifi.p2p.WifiP2pManager.ActionListener);
     method public void addServiceRequest(android.net.wifi.p2p.WifiP2pManager.Channel, android.net.wifi.p2p.nsd.WifiP2pServiceRequest, android.net.wifi.p2p.WifiP2pManager.ActionListener);
     method public void cancelConnect(android.net.wifi.p2p.WifiP2pManager.Channel, android.net.wifi.p2p.WifiP2pManager.ActionListener);
     method public void clearLocalServices(android.net.wifi.p2p.WifiP2pManager.Channel, android.net.wifi.p2p.WifiP2pManager.ActionListener);
     method public void clearServiceRequests(android.net.wifi.p2p.WifiP2pManager.Channel, android.net.wifi.p2p.WifiP2pManager.ActionListener);
-    method @RequiresPermission(android.Manifest.permission.ACCESS_COARSE_LOCATION) public void connect(android.net.wifi.p2p.WifiP2pManager.Channel, android.net.wifi.p2p.WifiP2pConfig, android.net.wifi.p2p.WifiP2pManager.ActionListener);
-    method @RequiresPermission(android.Manifest.permission.ACCESS_COARSE_LOCATION) public void createGroup(android.net.wifi.p2p.WifiP2pManager.Channel, android.net.wifi.p2p.WifiP2pManager.ActionListener);
-    method @RequiresPermission(android.Manifest.permission.ACCESS_COARSE_LOCATION) public void createGroup(@NonNull android.net.wifi.p2p.WifiP2pManager.Channel, @Nullable android.net.wifi.p2p.WifiP2pConfig, @Nullable android.net.wifi.p2p.WifiP2pManager.ActionListener);
-    method @RequiresPermission(android.Manifest.permission.ACCESS_COARSE_LOCATION) public void discoverPeers(android.net.wifi.p2p.WifiP2pManager.Channel, android.net.wifi.p2p.WifiP2pManager.ActionListener);
-    method @RequiresPermission(android.Manifest.permission.ACCESS_COARSE_LOCATION) public void discoverServices(android.net.wifi.p2p.WifiP2pManager.Channel, android.net.wifi.p2p.WifiP2pManager.ActionListener);
+    method @RequiresPermission(android.Manifest.permission.ACCESS_FINE_LOCATION) public void connect(android.net.wifi.p2p.WifiP2pManager.Channel, android.net.wifi.p2p.WifiP2pConfig, android.net.wifi.p2p.WifiP2pManager.ActionListener);
+    method @RequiresPermission(android.Manifest.permission.ACCESS_FINE_LOCATION) public void createGroup(android.net.wifi.p2p.WifiP2pManager.Channel, android.net.wifi.p2p.WifiP2pManager.ActionListener);
+    method @RequiresPermission(android.Manifest.permission.ACCESS_FINE_LOCATION) public void createGroup(@NonNull android.net.wifi.p2p.WifiP2pManager.Channel, @Nullable android.net.wifi.p2p.WifiP2pConfig, @Nullable android.net.wifi.p2p.WifiP2pManager.ActionListener);
+    method @RequiresPermission(android.Manifest.permission.ACCESS_FINE_LOCATION) public void discoverPeers(android.net.wifi.p2p.WifiP2pManager.Channel, android.net.wifi.p2p.WifiP2pManager.ActionListener);
+    method @RequiresPermission(android.Manifest.permission.ACCESS_FINE_LOCATION) public void discoverServices(android.net.wifi.p2p.WifiP2pManager.Channel, android.net.wifi.p2p.WifiP2pManager.ActionListener);
     method public android.net.wifi.p2p.WifiP2pManager.Channel initialize(android.content.Context, android.os.Looper, android.net.wifi.p2p.WifiP2pManager.ChannelListener);
     method public void removeGroup(android.net.wifi.p2p.WifiP2pManager.Channel, android.net.wifi.p2p.WifiP2pManager.ActionListener);
     method public void removeLocalService(android.net.wifi.p2p.WifiP2pManager.Channel, android.net.wifi.p2p.nsd.WifiP2pServiceInfo, android.net.wifi.p2p.WifiP2pManager.ActionListener);
     method public void removeServiceRequest(android.net.wifi.p2p.WifiP2pManager.Channel, android.net.wifi.p2p.nsd.WifiP2pServiceRequest, android.net.wifi.p2p.WifiP2pManager.ActionListener);
     method public void requestConnectionInfo(android.net.wifi.p2p.WifiP2pManager.Channel, android.net.wifi.p2p.WifiP2pManager.ConnectionInfoListener);
     method public void requestDiscoveryState(@NonNull android.net.wifi.p2p.WifiP2pManager.Channel, @NonNull android.net.wifi.p2p.WifiP2pManager.DiscoveryStateListener);
-    method @RequiresPermission(android.Manifest.permission.ACCESS_COARSE_LOCATION) public void requestGroupInfo(android.net.wifi.p2p.WifiP2pManager.Channel, android.net.wifi.p2p.WifiP2pManager.GroupInfoListener);
+    method @RequiresPermission(android.Manifest.permission.ACCESS_FINE_LOCATION) public void requestGroupInfo(android.net.wifi.p2p.WifiP2pManager.Channel, android.net.wifi.p2p.WifiP2pManager.GroupInfoListener);
     method public void requestNetworkInfo(@NonNull android.net.wifi.p2p.WifiP2pManager.Channel, @NonNull android.net.wifi.p2p.WifiP2pManager.NetworkInfoListener);
     method public void requestP2pState(@NonNull android.net.wifi.p2p.WifiP2pManager.Channel, @NonNull android.net.wifi.p2p.WifiP2pManager.P2pStateListener);
-    method @RequiresPermission(android.Manifest.permission.ACCESS_COARSE_LOCATION) public void requestPeers(android.net.wifi.p2p.WifiP2pManager.Channel, android.net.wifi.p2p.WifiP2pManager.PeerListListener);
+    method @RequiresPermission(android.Manifest.permission.ACCESS_FINE_LOCATION) public void requestPeers(android.net.wifi.p2p.WifiP2pManager.Channel, android.net.wifi.p2p.WifiP2pManager.PeerListListener);
     method public void setDnsSdResponseListeners(android.net.wifi.p2p.WifiP2pManager.Channel, android.net.wifi.p2p.WifiP2pManager.DnsSdServiceResponseListener, android.net.wifi.p2p.WifiP2pManager.DnsSdTxtRecordListener);
     method public void setServiceResponseListener(android.net.wifi.p2p.WifiP2pManager.Channel, android.net.wifi.p2p.WifiP2pManager.ServiceResponseListener);
     method public void setUpnpServiceResponseListener(android.net.wifi.p2p.WifiP2pManager.Channel, android.net.wifi.p2p.WifiP2pManager.UpnpServiceResponseListener);
@@ -34352,8 +34408,12 @@
   }
 
   public abstract class FileObserver {
-    ctor public FileObserver(String);
-    ctor public FileObserver(String, int);
+    ctor @Deprecated public FileObserver(String);
+    ctor public FileObserver(@NonNull java.io.File);
+    ctor public FileObserver(@NonNull java.util.List<java.io.File>);
+    ctor @Deprecated public FileObserver(String, int);
+    ctor public FileObserver(@NonNull java.io.File, int);
+    ctor public FileObserver(@NonNull java.util.List<java.io.File>, int);
     method protected void finalize();
     method public abstract void onEvent(int, @Nullable String);
     method public void startWatching();
@@ -35250,11 +35310,16 @@
 
   public abstract class VibrationEffect implements android.os.Parcelable {
     method public static android.os.VibrationEffect createOneShot(long, int);
+    method public static android.os.VibrationEffect createPrebaked(int);
     method public static android.os.VibrationEffect createWaveform(long[], int);
     method public static android.os.VibrationEffect createWaveform(long[], int[], int);
     method public int describeContents();
     field public static final android.os.Parcelable.Creator<android.os.VibrationEffect> CREATOR;
     field public static final int DEFAULT_AMPLITUDE = -1; // 0xffffffff
+    field public static final int EFFECT_CLICK = 0; // 0x0
+    field public static final int EFFECT_DOUBLE_CLICK = 1; // 0x1
+    field public static final int EFFECT_HEAVY_CLICK = 5; // 0x5
+    field public static final int EFFECT_TICK = 2; // 0x2
   }
 
   public abstract class Vibrator {
@@ -38195,6 +38260,8 @@
     field public static final String MEDIA_SCANNER_VOLUME = "volume";
     field public static final String META_DATA_STILL_IMAGE_CAMERA_PREWARM_SERVICE = "android.media.still_image_camera_preview_service";
     field public static final String UNKNOWN_STRING = "<unknown>";
+    field public static final String VOLUME_EXTERNAL = "external";
+    field public static final String VOLUME_INTERNAL = "internal";
   }
 
   public static final class MediaStore.Audio {
@@ -38401,28 +38468,28 @@
     field public static final android.net.Uri INTERNAL_CONTENT_URI;
   }
 
-  public static class MediaStore.Images.Thumbnails implements android.provider.BaseColumns {
-    ctor public MediaStore.Images.Thumbnails();
+  @Deprecated public static class MediaStore.Images.Thumbnails implements android.provider.BaseColumns {
+    ctor @Deprecated public MediaStore.Images.Thumbnails();
     method @Deprecated public static void cancelThumbnailRequest(android.content.ContentResolver, long);
     method @Deprecated public static void cancelThumbnailRequest(android.content.ContentResolver, long, long);
-    method public static android.net.Uri getContentUri(String);
+    method @Deprecated public static android.net.Uri getContentUri(String);
     method @Deprecated public static android.graphics.Bitmap getThumbnail(android.content.ContentResolver, long, int, android.graphics.BitmapFactory.Options);
     method @Deprecated public static android.graphics.Bitmap getThumbnail(android.content.ContentResolver, long, long, int, android.graphics.BitmapFactory.Options);
-    method public static final android.database.Cursor query(android.content.ContentResolver, android.net.Uri, String[]);
-    method public static final android.database.Cursor queryMiniThumbnail(android.content.ContentResolver, long, int, String[]);
-    method public static final android.database.Cursor queryMiniThumbnails(android.content.ContentResolver, android.net.Uri, int, String[]);
+    method @Deprecated public static final android.database.Cursor query(android.content.ContentResolver, android.net.Uri, String[]);
+    method @Deprecated public static final android.database.Cursor queryMiniThumbnail(android.content.ContentResolver, long, int, String[]);
+    method @Deprecated public static final android.database.Cursor queryMiniThumbnails(android.content.ContentResolver, android.net.Uri, int, String[]);
     field @Deprecated public static final String DATA = "_data";
-    field public static final String DEFAULT_SORT_ORDER = "image_id ASC";
-    field public static final android.net.Uri EXTERNAL_CONTENT_URI;
-    field public static final int FULL_SCREEN_KIND = 2; // 0x2
-    field public static final String HEIGHT = "height";
-    field public static final String IMAGE_ID = "image_id";
-    field public static final android.net.Uri INTERNAL_CONTENT_URI;
-    field public static final String KIND = "kind";
-    field public static final int MICRO_KIND = 3; // 0x3
-    field public static final int MINI_KIND = 1; // 0x1
-    field public static final String THUMB_DATA = "thumb_data";
-    field public static final String WIDTH = "width";
+    field @Deprecated public static final String DEFAULT_SORT_ORDER = "image_id ASC";
+    field @Deprecated public static final android.net.Uri EXTERNAL_CONTENT_URI;
+    field @Deprecated public static final int FULL_SCREEN_KIND = 2; // 0x2
+    field @Deprecated public static final String HEIGHT = "height";
+    field @Deprecated public static final String IMAGE_ID = "image_id";
+    field @Deprecated public static final android.net.Uri INTERNAL_CONTENT_URI;
+    field @Deprecated public static final String KIND = "kind";
+    field @Deprecated public static final int MICRO_KIND = 3; // 0x3
+    field @Deprecated public static final int MINI_KIND = 1; // 0x1
+    field @Deprecated public static final String THUMB_DATA = "thumb_data";
+    field @Deprecated public static final String WIDTH = "width";
   }
 
   public static interface MediaStore.MediaColumns extends android.provider.BaseColumns {
@@ -38474,24 +38541,24 @@
     field public static final android.net.Uri INTERNAL_CONTENT_URI;
   }
 
-  public static class MediaStore.Video.Thumbnails implements android.provider.BaseColumns {
-    ctor public MediaStore.Video.Thumbnails();
+  @Deprecated public static class MediaStore.Video.Thumbnails implements android.provider.BaseColumns {
+    ctor @Deprecated public MediaStore.Video.Thumbnails();
     method @Deprecated public static void cancelThumbnailRequest(android.content.ContentResolver, long);
     method @Deprecated public static void cancelThumbnailRequest(android.content.ContentResolver, long, long);
-    method public static android.net.Uri getContentUri(String);
+    method @Deprecated public static android.net.Uri getContentUri(String);
     method @Deprecated public static android.graphics.Bitmap getThumbnail(android.content.ContentResolver, long, int, android.graphics.BitmapFactory.Options);
     method @Deprecated public static android.graphics.Bitmap getThumbnail(android.content.ContentResolver, long, long, int, android.graphics.BitmapFactory.Options);
     field @Deprecated public static final String DATA = "_data";
-    field public static final String DEFAULT_SORT_ORDER = "video_id ASC";
-    field public static final android.net.Uri EXTERNAL_CONTENT_URI;
-    field public static final int FULL_SCREEN_KIND = 2; // 0x2
-    field public static final String HEIGHT = "height";
-    field public static final android.net.Uri INTERNAL_CONTENT_URI;
-    field public static final String KIND = "kind";
-    field public static final int MICRO_KIND = 3; // 0x3
-    field public static final int MINI_KIND = 1; // 0x1
-    field public static final String VIDEO_ID = "video_id";
-    field public static final String WIDTH = "width";
+    field @Deprecated public static final String DEFAULT_SORT_ORDER = "video_id ASC";
+    field @Deprecated public static final android.net.Uri EXTERNAL_CONTENT_URI;
+    field @Deprecated public static final int FULL_SCREEN_KIND = 2; // 0x2
+    field @Deprecated public static final String HEIGHT = "height";
+    field @Deprecated public static final android.net.Uri INTERNAL_CONTENT_URI;
+    field @Deprecated public static final String KIND = "kind";
+    field @Deprecated public static final int MICRO_KIND = 3; // 0x3
+    field @Deprecated public static final int MINI_KIND = 1; // 0x1
+    field @Deprecated public static final String VIDEO_ID = "video_id";
+    field @Deprecated public static final String WIDTH = "width";
   }
 
   public static interface MediaStore.Video.VideoColumns extends android.provider.MediaStore.MediaColumns {
@@ -38582,6 +38649,7 @@
     field public static final String ACTION_NOTIFICATION_POLICY_ACCESS_SETTINGS = "android.settings.NOTIFICATION_POLICY_ACCESS_SETTINGS";
     field public static final String ACTION_PRINT_SETTINGS = "android.settings.ACTION_PRINT_SETTINGS";
     field public static final String ACTION_PRIVACY_SETTINGS = "android.settings.PRIVACY_SETTINGS";
+    field public static final String ACTION_PROCESS_WIFI_EASY_CONNECT_QR_CODE = "android.settings.PROCESS_WIFI_EASY_CONNECT_QR_CODE";
     field public static final String ACTION_QUICK_LAUNCH_SETTINGS = "android.settings.QUICK_LAUNCH_SETTINGS";
     field public static final String ACTION_REQUEST_IGNORE_BATTERY_OPTIMIZATIONS = "android.settings.REQUEST_IGNORE_BATTERY_OPTIMIZATIONS";
     field public static final String ACTION_REQUEST_SET_AUTOFILL_SERVICE = "android.settings.REQUEST_SET_AUTOFILL_SERVICE";
@@ -38615,6 +38683,7 @@
     field public static final String EXTRA_DO_NOT_DISTURB_MODE_ENABLED = "android.settings.extra.do_not_disturb_mode_enabled";
     field public static final String EXTRA_DO_NOT_DISTURB_MODE_MINUTES = "android.settings.extra.do_not_disturb_mode_minutes";
     field public static final String EXTRA_INPUT_METHOD_ID = "input_method_id";
+    field public static final String EXTRA_QR_CODE = "android.provider.extra.QR_CODE";
     field public static final String EXTRA_SUB_ID = "android.provider.extra.SUB_ID";
     field public static final String INTENT_CATEGORY_USAGE_ACCESS_CONFIG = "android.intent.category.USAGE_ACCESS_CONFIG";
     field public static final String METADATA_USAGE_ACCESS_REASON = "android.settings.metadata.USAGE_ACCESS_REASON";
@@ -38689,6 +38758,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";
   }
 
@@ -39072,6 +39142,7 @@
     field public static final String CONTENT_ID = "cid";
     field public static final String CONTENT_LOCATION = "cl";
     field public static final String CONTENT_TYPE = "ct";
+    field public static final android.net.Uri CONTENT_URI;
     field public static final String CT_START = "ctt_s";
     field public static final String CT_TYPE = "ctt_t";
     field public static final String FILENAME = "fn";
@@ -41333,6 +41404,22 @@
 
 package android.service.notification {
 
+  public final class Adjustment implements android.os.Parcelable {
+    ctor public Adjustment(String, String, android.os.Bundle, CharSequence, int);
+    method public int describeContents();
+    method public CharSequence getExplanation();
+    method public String getKey();
+    method public String getPackage();
+    method public android.os.Bundle getSignals();
+    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_TEXT_REPLIES = "key_text_replies";
+    field public static final String KEY_USER_SENTIMENT = "key_user_sentiment";
+  }
+
   public final class Condition implements android.os.Parcelable {
     ctor public Condition(android.net.Uri, String, int);
     ctor public Condition(android.net.Uri, String, String, String, int, int, int);
@@ -41379,6 +41466,24 @@
     field @Deprecated public static final String SERVICE_INTERFACE = "android.service.notification.ConditionProviderService";
   }
 
+  public abstract class NotificationAssistantService extends android.service.notification.NotificationListenerService {
+    ctor public NotificationAssistantService();
+    method public final void adjustNotification(android.service.notification.Adjustment);
+    method public final void adjustNotifications(java.util.List<android.service.notification.Adjustment>);
+    method public void onActionInvoked(@NonNull String, @NonNull android.app.Notification.Action, int);
+    method public final android.os.IBinder onBind(android.content.Intent);
+    method public void onNotificationDirectReplied(@NonNull String);
+    method public android.service.notification.Adjustment onNotificationEnqueued(android.service.notification.StatusBarNotification);
+    method public android.service.notification.Adjustment onNotificationEnqueued(android.service.notification.StatusBarNotification, android.app.NotificationChannel);
+    method public void onNotificationExpansionChanged(@NonNull String, boolean, boolean);
+    method public void onNotificationRemoved(android.service.notification.StatusBarNotification, android.service.notification.NotificationListenerService.RankingMap, android.service.notification.NotificationStats, int);
+    method public void onNotificationsSeen(java.util.List<java.lang.String>);
+    method public void onSuggestedReplySent(@NonNull String, @NonNull CharSequence, int);
+    field public static final String SERVICE_INTERFACE = "android.service.notification.NotificationAssistantService";
+    field public static final int SOURCE_FROM_APP = 0; // 0x0
+    field public static final int SOURCE_FROM_ASSISTANT = 1; // 0x1
+  }
+
   public abstract class NotificationListenerService extends android.app.Service {
     ctor public NotificationListenerService();
     method public final void cancelAllNotifications();
@@ -41459,6 +41564,8 @@
     method public long getLastAudiblyAlertedMillis();
     method public String getOverrideGroupKey();
     method public int getRank();
+    method public java.util.List<android.app.Notification.Action> getSmartActions();
+    method public java.util.List<java.lang.CharSequence> getSmartReplies();
     method public int getSuppressedVisualEffects();
     method public int getUserSentiment();
     method public boolean isAmbient();
@@ -41477,6 +41584,37 @@
     field public static final android.os.Parcelable.Creator<android.service.notification.NotificationListenerService.RankingMap> CREATOR;
   }
 
+  public final class NotificationStats implements android.os.Parcelable {
+    ctor public NotificationStats();
+    method public int describeContents();
+    method public int getDismissalSentiment();
+    method public int getDismissalSurface();
+    method public boolean hasDirectReplied();
+    method public boolean hasExpanded();
+    method public boolean hasInteracted();
+    method public boolean hasSeen();
+    method public boolean hasSnoozed();
+    method public boolean hasViewedSettings();
+    method public void setDirectReplied();
+    method public void setDismissalSentiment(int);
+    method public void setDismissalSurface(int);
+    method public void setExpanded();
+    method public void setSeen();
+    method public void setSnoozed();
+    method public void setViewedSettings();
+    method public void writeToParcel(android.os.Parcel, int);
+    field public static final android.os.Parcelable.Creator<android.service.notification.NotificationStats> CREATOR;
+    field public static final int DISMISSAL_AOD = 2; // 0x2
+    field public static final int DISMISSAL_NOT_DISMISSED = -1; // 0xffffffff
+    field public static final int DISMISSAL_OTHER = 0; // 0x0
+    field public static final int DISMISSAL_PEEK = 1; // 0x1
+    field public static final int DISMISSAL_SHADE = 3; // 0x3
+    field public static final int DISMISS_SENTIMENT_NEGATIVE = 0; // 0x0
+    field public static final int DISMISS_SENTIMENT_NEUTRAL = 1; // 0x1
+    field public static final int DISMISS_SENTIMENT_POSITIVE = 2; // 0x2
+    field public static final int DISMISS_SENTIMENT_UNKNOWN = -1000; // 0xfffffc18
+  }
+
   public class StatusBarNotification implements android.os.Parcelable {
     ctor @Deprecated public StatusBarNotification(String, String, int, String, int, int, int, android.app.Notification, android.os.UserHandle, long);
     ctor public StatusBarNotification(android.os.Parcel);
@@ -41671,6 +41809,7 @@
 
   public class VoiceInteractionService extends android.app.Service {
     ctor public VoiceInteractionService();
+    method public final void clearTranscription(boolean);
     method public final android.service.voice.AlwaysOnHotwordDetector createAlwaysOnHotwordDetector(String, java.util.Locale, android.service.voice.AlwaysOnHotwordDetector.Callback);
     method public int getDisabledShowContext();
     method public static boolean isActiveService(android.content.Context, android.content.ComponentName);
@@ -41680,9 +41819,15 @@
     method public void onReady();
     method public void onShutdown();
     method public void setDisabledShowContext(int);
+    method public final void setTranscription(@NonNull String);
+    method public final void setVoiceState(int);
     method public void showSession(android.os.Bundle, int);
     field public static final String SERVICE_INTERFACE = "android.service.voice.VoiceInteractionService";
     field public static final String SERVICE_META_DATA = "android.voice_interaction";
+    field public static final int VOICE_STATE_CONDITIONAL_LISTENING = 1; // 0x1
+    field public static final int VOICE_STATE_FULFILLING = 3; // 0x3
+    field public static final int VOICE_STATE_LISTENING = 2; // 0x2
+    field public static final int VOICE_STATE_NONE = 0; // 0x0
   }
 
   public class VoiceInteractionSession implements android.content.ComponentCallbacks2 android.view.KeyEvent.Callback {
@@ -43043,6 +43188,15 @@
     method public abstract void onScreenCall(@NonNull android.telecom.Call.Details);
     method public final void provideCallIdentification(@NonNull android.telecom.Call.Details, @NonNull android.telecom.CallIdentification);
     method public final void respondToCall(@NonNull android.telecom.Call.Details, @NonNull android.telecom.CallScreeningService.CallResponse);
+    field public static final String ACTION_NUISANCE_CALL_STATUS_CHANGED = "android.telecom.action.NUISANCE_CALL_STATUS_CHANGED";
+    field public static final int CALL_DURATION_LONG = 4; // 0x4
+    field public static final int CALL_DURATION_MEDIUM = 3; // 0x3
+    field public static final int CALL_DURATION_SHORT = 2; // 0x2
+    field public static final int CALL_DURATION_VERY_SHORT = 1; // 0x1
+    field public static final String EXTRA_CALL_DURATION = "android.telecom.extra.CALL_DURATION";
+    field public static final String EXTRA_CALL_HANDLE = "android.telecom.extra.CALL_HANDLE";
+    field public static final String EXTRA_CALL_TYPE = "android.telecom.extra.CALL_TYPE";
+    field public static final String EXTRA_IS_NUISANCE = "android.telecom.extra.IS_NUISANCE";
     field public static final String SERVICE_INTERFACE = "android.telecom.CallScreeningService";
   }
 
@@ -43648,6 +43802,7 @@
     method @RequiresPermission(android.Manifest.permission.READ_PHONE_STATE) public boolean isVoiceMailNumber(android.telecom.PhoneAccountHandle, String);
     method @RequiresPermission(anyOf={android.Manifest.permission.CALL_PHONE, android.Manifest.permission.MANAGE_OWN_CALLS}) public void placeCall(android.net.Uri, android.os.Bundle);
     method public void registerPhoneAccount(android.telecom.PhoneAccount);
+    method @RequiresPermission(android.Manifest.permission.READ_PHONE_STATE) public void reportNuisanceCallStatus(@NonNull android.net.Uri, boolean);
     method @RequiresPermission(android.Manifest.permission.READ_PHONE_STATE) public void showInCallScreen(boolean);
     method @RequiresPermission(android.Manifest.permission.MODIFY_PHONE_STATE) public void silenceRinger();
     method public void unregisterPhoneAccount(android.telecom.PhoneAccountHandle);
@@ -43895,7 +44050,9 @@
     field public static final String KEY_CARRIER_NAME_OVERRIDE_BOOL = "carrier_name_override_bool";
     field public static final String KEY_CARRIER_NAME_STRING = "carrier_name_string";
     field public static final String KEY_CARRIER_SETTINGS_ENABLE_BOOL = "carrier_settings_enable_bool";
+    field public static final String KEY_CARRIER_SUPPORTS_SS_OVER_UT_BOOL = "carrier_supports_ss_over_ut_bool";
     field public static final String KEY_CARRIER_USE_IMS_FIRST_FOR_EMERGENCY_BOOL = "carrier_use_ims_first_for_emergency_bool";
+    field public static final String KEY_CARRIER_UT_PROVISIONING_REQUIRED_BOOL = "carrier_ut_provisioning_required_bool";
     field public static final String KEY_CARRIER_VOLTE_AVAILABLE_BOOL = "carrier_volte_available_bool";
     field public static final String KEY_CARRIER_VOLTE_PROVISIONED_BOOL = "carrier_volte_provisioned_bool";
     field public static final String KEY_CARRIER_VOLTE_PROVISIONING_REQUIRED_BOOL = "carrier_volte_provisioning_required_bool";
@@ -44514,12 +44671,14 @@
 
   public final class SmsManager {
     method public String createAppSpecificSmsToken(android.app.PendingIntent);
+    method @Nullable public String createAppSpecificSmsTokenWithPackageInfo(@Nullable String, @NonNull android.app.PendingIntent);
     method public java.util.ArrayList<java.lang.String> divideMessage(String);
     method public void downloadMultimediaMessage(android.content.Context, String, android.net.Uri, android.os.Bundle, android.app.PendingIntent);
     method public android.os.Bundle getCarrierConfigValues();
     method public static android.telephony.SmsManager getDefault();
     method public static int getDefaultSmsSubscriptionId();
     method public static android.telephony.SmsManager getSmsManagerForSubscriptionId(int);
+    method @RequiresPermission(android.Manifest.permission.SMS_FINANCIAL_TRANSACTIONS) public void getSmsMessagesForFinancialApp(android.os.Bundle, @NonNull java.util.concurrent.Executor, @NonNull android.telephony.SmsManager.FinancialSmsCallback);
     method public int getSubscriptionId();
     method public void injectSmsPdu(byte[], String, android.app.PendingIntent);
     method public void sendDataMessage(String, String, short, byte[], android.app.PendingIntent, android.app.PendingIntent);
@@ -44582,6 +44741,11 @@
     field public static final int STATUS_ON_ICC_UNSENT = 7; // 0x7
   }
 
+  public abstract static class SmsManager.FinancialSmsCallback {
+    ctor public SmsManager.FinancialSmsCallback();
+    method public abstract void onFinancialSmsMessages(android.database.CursorWindow);
+  }
+
   public class SmsMessage {
     method public static int[] calculateLength(CharSequence, boolean);
     method public static int[] calculateLength(String, boolean);
@@ -44659,6 +44823,7 @@
     method public String getNumber();
     method public int getSimSlotIndex();
     method public int getSubscriptionId();
+    method public int getSubscriptionType();
     method public boolean isEmbedded();
     method public boolean isOpportunistic();
     method public void writeToParcel(android.os.Parcel, int);
@@ -44709,6 +44874,8 @@
     field public static final String EXTRA_SUBSCRIPTION_INDEX = "android.telephony.extra.SUBSCRIPTION_INDEX";
     field public static final int INVALID_SIM_SLOT_INDEX = -1; // 0xffffffff
     field public static final int INVALID_SUBSCRIPTION_ID = -1; // 0xffffffff
+    field public static final int SUBSCRIPTION_TYPE_LOCAL_SIM = 0; // 0x0
+    field public static final int SUBSCRIPTION_TYPE_REMOTE_SIM = 1; // 0x1
   }
 
   public static class SubscriptionManager.OnOpportunisticSubscriptionsChangedListener {
@@ -48419,6 +48586,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);
@@ -52623,6 +52791,7 @@
   public static final class ContentCaptureContext.Builder {
     ctor public ContentCaptureContext.Builder();
     method public android.view.contentcapture.ContentCaptureContext build();
+    method @NonNull public android.view.contentcapture.ContentCaptureContext.Builder setAction(@NonNull String);
     method @NonNull public android.view.contentcapture.ContentCaptureContext.Builder setExtras(@NonNull android.os.Bundle);
     method @NonNull public android.view.contentcapture.ContentCaptureContext.Builder setUri(@NonNull android.net.Uri);
   }
@@ -52983,8 +53152,8 @@
     method @Deprecated public boolean isWatchingCursor(android.view.View);
     method public void restartInput(android.view.View);
     method public void sendAppPrivateCommand(android.view.View, String, android.os.Bundle);
-    method public void setAdditionalInputMethodSubtypes(String, android.view.inputmethod.InputMethodSubtype[]);
-    method @RequiresPermission(android.Manifest.permission.WRITE_SECURE_SETTINGS) public boolean setCurrentInputMethodSubtype(android.view.inputmethod.InputMethodSubtype);
+    method @Deprecated public void setAdditionalInputMethodSubtypes(String, android.view.inputmethod.InputMethodSubtype[]);
+    method @Deprecated @RequiresPermission(android.Manifest.permission.WRITE_SECURE_SETTINGS) public boolean setCurrentInputMethodSubtype(android.view.inputmethod.InputMethodSubtype);
     method @Deprecated public void setInputMethod(android.os.IBinder, String);
     method @Deprecated public void setInputMethodAndSubtype(@NonNull android.os.IBinder, String, android.view.inputmethod.InputMethodSubtype);
     method @Deprecated public boolean shouldOfferSwitchingToNextInputMethod(android.os.IBinder);
@@ -53081,6 +53250,16 @@
     ctor public InspectionCompanion.UninitializedPropertyMapException();
   }
 
+  public final class IntEnumMapping {
+    method @Nullable public String get(int);
+  }
+
+  public static final class IntEnumMapping.Builder {
+    ctor public IntEnumMapping.Builder();
+    method @NonNull public android.view.inspector.IntEnumMapping.Builder addValue(@NonNull String, int);
+    method @NonNull public android.view.inspector.IntEnumMapping build();
+  }
+
   public final class IntFlagMapping {
     method @NonNull public java.util.Set<java.lang.String> get(int);
   }
@@ -53101,7 +53280,7 @@
     method public int mapFloat(@NonNull String, @AttrRes int);
     method public int mapGravity(@NonNull String, @AttrRes int);
     method public int mapInt(@NonNull String, @AttrRes int);
-    method public int mapIntEnum(@NonNull String, @AttrRes int, @NonNull android.util.SparseArray<java.lang.String>);
+    method public int mapIntEnum(@NonNull String, @AttrRes int, @NonNull android.view.inspector.IntEnumMapping);
     method public int mapIntFlag(@NonNull String, @AttrRes int, @NonNull android.view.inspector.IntFlagMapping);
     method public int mapLong(@NonNull String, @AttrRes int);
     method public int mapObject(@NonNull String, @AttrRes int);
@@ -53186,8 +53365,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 {
@@ -53338,6 +53517,7 @@
 
   public final class TextClassificationManager {
     method @NonNull public android.view.textclassifier.TextClassifier createTextClassificationSession(@NonNull android.view.textclassifier.TextClassificationContext);
+    method @NonNull public android.view.textclassifier.TextClassifier getLocalTextClassifier();
     method @NonNull public android.view.textclassifier.TextClassifier getTextClassifier();
     method public void setTextClassificationSessionFactory(@Nullable android.view.textclassifier.TextClassificationSessionFactory);
     method public void setTextClassifier(@Nullable android.view.textclassifier.TextClassifier);
@@ -53415,7 +53595,7 @@
   public final class TextClassifierEvent implements android.os.Parcelable {
     method public int describeContents();
     method @NonNull public int[] getActionIndices();
-    method @Nullable public String getEntityType();
+    method @NonNull public String[] getEntityTypes();
     method public int getEventCategory();
     method @Nullable public android.view.textclassifier.TextClassificationContext getEventContext();
     method public int getEventIndex();
@@ -53428,6 +53608,7 @@
     method public int getRelativeWordEndIndex();
     method public int getRelativeWordStartIndex();
     method @Nullable public String getResultId();
+    method public float getScore();
     method public void writeToParcel(android.os.Parcel, int);
     field public static final int CATEGORY_CONVERSATION_ACTIONS = 3; // 0x3
     field public static final int CATEGORY_LANGUAGE_DETECTION = 4; // 0x4
@@ -53462,7 +53643,7 @@
     ctor public TextClassifierEvent.Builder(int, int);
     method @NonNull public android.view.textclassifier.TextClassifierEvent build();
     method @NonNull public android.view.textclassifier.TextClassifierEvent.Builder setActionIndices(@NonNull int...);
-    method @NonNull public android.view.textclassifier.TextClassifierEvent.Builder setEntityType(@Nullable String);
+    method @NonNull public android.view.textclassifier.TextClassifierEvent.Builder setEntityTypes(@NonNull java.lang.String...);
     method @NonNull public android.view.textclassifier.TextClassifierEvent.Builder setEventContext(@Nullable android.view.textclassifier.TextClassificationContext);
     method @NonNull public android.view.textclassifier.TextClassifierEvent.Builder setEventIndex(int);
     method @NonNull public android.view.textclassifier.TextClassifierEvent.Builder setEventTime(long);
@@ -53473,6 +53654,7 @@
     method @NonNull public android.view.textclassifier.TextClassifierEvent.Builder setRelativeWordEndIndex(int);
     method @NonNull public android.view.textclassifier.TextClassifierEvent.Builder setRelativeWordStartIndex(int);
     method @NonNull public android.view.textclassifier.TextClassifierEvent.Builder setResultId(@Nullable String);
+    method @NonNull public android.view.textclassifier.TextClassifierEvent.Builder setScore(float);
   }
 
   public final class TextLanguage implements android.os.Parcelable {
@@ -55768,10 +55950,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 {
@@ -62223,20 +62405,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 b731a3d..328b6d2 100644
--- a/api/system-current.txt
+++ b/api/system-current.txt
@@ -244,6 +244,7 @@
   }
 
   public static final class R.style {
+    field public static final int Theme_DeviceDefault_DocumentsUI = 16974562; // 0x10302e2
     field public static final int Theme_Leanback_FormWizard = 16974544; // 0x10302d0
   }
 
@@ -507,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";
@@ -882,8 +885,8 @@
 
   public final class ClassificationsRequest implements android.os.Parcelable {
     method public int describeContents();
-    method public android.os.Bundle getExtras();
-    method public java.util.List<android.app.contentsuggestions.ContentSelection> getSelections();
+    method @Nullable public android.os.Bundle getExtras();
+    method @NonNull public java.util.List<android.app.contentsuggestions.ContentSelection> getSelections();
     method public void writeToParcel(android.os.Parcel, int);
     field public static final android.os.Parcelable.Creator<android.app.contentsuggestions.ClassificationsRequest> CREATOR;
   }
@@ -897,8 +900,8 @@
   public final class ContentClassification implements android.os.Parcelable {
     ctor public ContentClassification(@NonNull String, @NonNull android.os.Bundle);
     method public int describeContents();
-    method public android.os.Bundle getExtras();
-    method public String getId();
+    method @NonNull public android.os.Bundle getExtras();
+    method @NonNull public String getId();
     method public void writeToParcel(android.os.Parcel, int);
     field public static final android.os.Parcelable.Creator<android.app.contentsuggestions.ContentClassification> CREATOR;
   }
@@ -906,8 +909,8 @@
   public final class ContentSelection implements android.os.Parcelable {
     ctor public ContentSelection(@NonNull String, @NonNull android.os.Bundle);
     method public int describeContents();
-    method public android.os.Bundle getExtras();
-    method public String getId();
+    method @NonNull public android.os.Bundle getExtras();
+    method @NonNull public String getId();
     method public void writeToParcel(android.os.Parcel, int);
     field public static final android.os.Parcelable.Creator<android.app.contentsuggestions.ContentSelection> CREATOR;
   }
@@ -929,8 +932,8 @@
 
   public final class SelectionsRequest implements android.os.Parcelable {
     method public int describeContents();
-    method public android.os.Bundle getExtras();
-    method public android.graphics.Point getInterestPoint();
+    method @Nullable public android.os.Bundle getExtras();
+    method @Nullable public android.graphics.Point getInterestPoint();
     method public int getTaskId();
     method public void writeToParcel(android.os.Parcel, int);
     field public static final android.os.Parcelable.Creator<android.app.contentsuggestions.SelectionsRequest> CREATOR;
@@ -1052,6 +1055,7 @@
     method @RequiresPermission(android.Manifest.permission.MANAGE_ROLE_HOLDERS) public void removeRoleHolderAsUser(@NonNull String, @NonNull String, @NonNull android.os.UserHandle, @NonNull java.util.concurrent.Executor, @NonNull android.app.role.RoleManagerCallback);
     method @RequiresPermission("com.android.permissioncontroller.permission.MANAGE_ROLES_FROM_CONTROLLER") public boolean removeRoleHolderFromController(@NonNull String, @NonNull String);
     method @RequiresPermission("com.android.permissioncontroller.permission.MANAGE_ROLES_FROM_CONTROLLER") public void setRoleNamesFromController(@NonNull java.util.List<java.lang.String>);
+    field public static final String ROLE_ASSISTANT = "android.app.role.ASSISTANT";
   }
 
   public interface RoleManagerCallback {
@@ -1112,6 +1116,7 @@
     method @RequiresPermission(android.Manifest.permission.PACKAGE_USAGE_STATS) public int getAppStandbyBucket(String);
     method @RequiresPermission(android.Manifest.permission.PACKAGE_USAGE_STATS) public java.util.Map<java.lang.String,java.lang.Integer> getAppStandbyBuckets();
     method public int getUsageSource();
+    method @RequiresPermission(allOf={android.Manifest.permission.SUSPEND_APPS, android.Manifest.permission.OBSERVE_APP_USAGE}) public void registerAppUsageLimitObserver(int, @NonNull String[], long, @NonNull java.util.concurrent.TimeUnit, @NonNull android.app.PendingIntent);
     method @RequiresPermission(android.Manifest.permission.OBSERVE_APP_USAGE) public void registerAppUsageObserver(int, @NonNull String[], long, @NonNull java.util.concurrent.TimeUnit, @NonNull android.app.PendingIntent);
     method @RequiresPermission(android.Manifest.permission.OBSERVE_APP_USAGE) public void registerUsageSessionObserver(int, @NonNull String[], long, @NonNull java.util.concurrent.TimeUnit, long, @NonNull java.util.concurrent.TimeUnit, @NonNull android.app.PendingIntent, @Nullable android.app.PendingIntent);
     method public void reportUsageStart(@NonNull android.app.Activity, @NonNull String);
@@ -1119,6 +1124,7 @@
     method public void reportUsageStop(@NonNull android.app.Activity, @NonNull String);
     method @RequiresPermission(android.Manifest.permission.CHANGE_APP_IDLE_STATE) public void setAppStandbyBucket(String, int);
     method @RequiresPermission(android.Manifest.permission.CHANGE_APP_IDLE_STATE) public void setAppStandbyBuckets(java.util.Map<java.lang.String,java.lang.Integer>);
+    method @RequiresPermission(allOf={android.Manifest.permission.SUSPEND_APPS, android.Manifest.permission.OBSERVE_APP_USAGE}) public void unregisterAppUsageLimitObserver(int);
     method @RequiresPermission(android.Manifest.permission.OBSERVE_APP_USAGE) public void unregisterAppUsageObserver(int);
     method @RequiresPermission(android.Manifest.permission.OBSERVE_APP_USAGE) public void unregisterUsageSessionObserver(int);
     method @RequiresPermission(android.Manifest.permission.CHANGE_DEVICE_IDLE_TEMP_WHITELIST) public void whitelistAppTemporarily(String, long, android.os.UserHandle);
@@ -1147,7 +1153,7 @@
     field public static final String ACTION_REQUEST_BLE_SCAN_ALWAYS_AVAILABLE = "android.bluetooth.adapter.action.REQUEST_BLE_SCAN_ALWAYS_AVAILABLE";
   }
 
-  public abstract class BluetoothAdapter.MetadataListener {
+  public abstract static class BluetoothAdapter.MetadataListener {
     ctor public BluetoothAdapter.MetadataListener();
     method public void onMetadataChanged(android.bluetooth.BluetoothDevice, int, String);
   }
@@ -1155,14 +1161,18 @@
   public final class BluetoothDevice implements android.os.Parcelable {
     method @RequiresPermission(android.Manifest.permission.BLUETOOTH_ADMIN) public boolean cancelBondProcess();
     method @RequiresPermission(android.Manifest.permission.BLUETOOTH_PRIVILEGED) public String getMetadata(int);
+    method @RequiresPermission(android.Manifest.permission.BLUETOOTH_PRIVILEGED) public boolean getSilenceMode();
     method @RequiresPermission(android.Manifest.permission.BLUETOOTH) public boolean isConnected();
     method @RequiresPermission(android.Manifest.permission.BLUETOOTH) public boolean isEncrypted();
     method @RequiresPermission(android.Manifest.permission.BLUETOOTH_ADMIN) public boolean removeBond();
     method @RequiresPermission(android.Manifest.permission.BLUETOOTH_PRIVILEGED) public boolean setMetadata(int, String);
     method @RequiresPermission(android.Manifest.permission.BLUETOOTH_PRIVILEGED) public boolean setPhonebookAccessPermission(int);
+    method @RequiresPermission(android.Manifest.permission.BLUETOOTH_PRIVILEGED) public boolean setSilenceMode(boolean);
     field public static final int ACCESS_ALLOWED = 1; // 0x1
     field public static final int ACCESS_REJECTED = 2; // 0x2
     field public static final int ACCESS_UNKNOWN = 0; // 0x0
+    field public static final String ACTION_SILENCE_MODE_CHANGED = "android.bluetooth.device.action.SILENCE_MODE_CHANGED";
+    field public static final String EXTRA_SILENCE_ENABLED = "android.bluetooth.device.extra.SILENCE_ENABLED";
     field public static final int METADATA_COMPANION_APP = 4; // 0x4
     field public static final int METADATA_ENHANCED_SETTINGS_UI_URI = 16; // 0x10
     field public static final int METADATA_HARDWARE_VERSION = 3; // 0x3
@@ -1257,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";
@@ -1295,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";
@@ -1687,18 +1698,19 @@
 
   public final class RollbackInfo implements android.os.Parcelable {
     method public int describeContents();
+    method public java.util.List<android.content.rollback.PackageRollbackInfo> getPackages();
     method public int getRollbackId();
+    method public int getSessionId();
+    method public boolean isStaged();
     method public void writeToParcel(android.os.Parcel, int);
     field public static final android.os.Parcelable.Creator<android.content.rollback.RollbackInfo> CREATOR;
-    field public final android.content.rollback.PackageRollbackInfo targetPackage;
   }
 
   public final class RollbackManager {
-    method @RequiresPermission(android.Manifest.permission.MANAGE_ROLLBACKS) public void executeRollback(@NonNull android.content.rollback.RollbackInfo, @NonNull android.content.IntentSender);
+    method @RequiresPermission(android.Manifest.permission.MANAGE_ROLLBACKS) public void commitRollback(int, @NonNull android.content.IntentSender);
     method @RequiresPermission(android.Manifest.permission.MANAGE_ROLLBACKS) public void expireRollbackForPackage(@NonNull String);
-    method @RequiresPermission(android.Manifest.permission.MANAGE_ROLLBACKS) @Nullable public android.content.rollback.RollbackInfo getAvailableRollback(@NonNull String);
-    method @RequiresPermission(android.Manifest.permission.MANAGE_ROLLBACKS) @NonNull public java.util.List<java.lang.String> getPackagesWithAvailableRollbacks();
-    method @RequiresPermission(android.Manifest.permission.MANAGE_ROLLBACKS) @NonNull public java.util.List<android.content.rollback.RollbackInfo> getRecentlyExecutedRollbacks();
+    method @RequiresPermission(android.Manifest.permission.MANAGE_ROLLBACKS) public java.util.List<android.content.rollback.RollbackInfo> getAvailableRollbacks();
+    method @RequiresPermission(android.Manifest.permission.MANAGE_ROLLBACKS) @NonNull public java.util.List<android.content.rollback.RollbackInfo> getRecentlyCommittedRollbacks();
     method @RequiresPermission(android.Manifest.permission.MANAGE_ROLLBACKS) public void reloadPersistedData();
   }
 
@@ -1804,9 +1816,16 @@
   }
 
   public final class ColorDisplayManager {
+    method @RequiresPermission(android.Manifest.permission.CONTROL_DISPLAY_COLOR_TRANSFORMS) public int getNightDisplayAutoMode();
     method @RequiresPermission(android.Manifest.permission.CONTROL_DISPLAY_COLOR_TRANSFORMS) public int getTransformCapabilities();
     method @RequiresPermission(android.Manifest.permission.CONTROL_DISPLAY_COLOR_TRANSFORMS) public boolean setAppSaturationLevel(@NonNull String, @IntRange(from=0, to=100) int);
+    method @RequiresPermission(android.Manifest.permission.CONTROL_DISPLAY_COLOR_TRANSFORMS) public boolean setNightDisplayAutoMode(int);
+    method @RequiresPermission(android.Manifest.permission.CONTROL_DISPLAY_COLOR_TRANSFORMS) public boolean setNightDisplayCustomEndTime(@NonNull java.time.LocalTime);
+    method @RequiresPermission(android.Manifest.permission.CONTROL_DISPLAY_COLOR_TRANSFORMS) public boolean setNightDisplayCustomStartTime(@NonNull java.time.LocalTime);
     method @RequiresPermission(android.Manifest.permission.CONTROL_DISPLAY_COLOR_TRANSFORMS) public boolean setSaturationLevel(@IntRange(from=0, to=100) int);
+    field public static final int AUTO_MODE_CUSTOM_TIME = 1; // 0x1
+    field public static final int AUTO_MODE_DISABLED = 0; // 0x0
+    field public static final int AUTO_MODE_TWILIGHT = 2; // 0x2
     field public static final int CAPABILITY_HARDWARE_ACCELERATION_GLOBAL = 2; // 0x2
     field public static final int CAPABILITY_HARDWARE_ACCELERATION_PER_APP = 4; // 0x4
     field public static final int CAPABILITY_NONE = 0; // 0x0
@@ -1838,9 +1857,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
@@ -1930,6 +1955,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);
   }
@@ -2056,6 +2084,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);
@@ -3380,6 +3418,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);
@@ -4021,6 +4068,7 @@
   }
 
   public class ConnectivityManager {
+    method @RequiresPermission("android.permission.PACKET_KEEPALIVE_OFFLOAD") public android.net.SocketKeepalive createNattKeepalive(@NonNull android.net.Network, @NonNull java.io.FileDescriptor, @NonNull java.net.InetAddress, @NonNull java.net.InetAddress, @NonNull java.util.concurrent.Executor, @NonNull android.net.SocketKeepalive.Callback);
     method public boolean getAvoidBadWifi();
     method @RequiresPermission(android.Manifest.permission.LOCAL_MAC_ADDRESS) public String getCaptivePortalServerUrl();
     method @RequiresPermission(anyOf={android.Manifest.permission.TETHER_PRIVILEGED, android.Manifest.permission.WRITE_SETTINGS}) public boolean isTetheringSupported();
@@ -4041,6 +4089,10 @@
     method public void onTetheringStarted();
   }
 
+  public final class IpPrefix implements android.os.Parcelable {
+    ctor public IpPrefix(java.net.InetAddress, int);
+  }
+
   public final class IpSecManager {
     method @RequiresPermission(android.Manifest.permission.MANAGE_IPSEC_TUNNELS) public void applyTunnelModeTransform(@NonNull android.net.IpSecManager.IpSecTunnelInterface, int, @NonNull android.net.IpSecTransform) throws java.io.IOException;
     method @NonNull @RequiresPermission(android.Manifest.permission.MANAGE_IPSEC_TUNNELS) public android.net.IpSecManager.IpSecTunnelInterface createIpSecTunnelInterface(@NonNull java.net.InetAddress, @NonNull java.net.InetAddress, @NonNull android.net.Network) throws java.io.IOException, android.net.IpSecManager.ResourceUnavailableException;
@@ -4053,26 +4105,12 @@
     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);
     method public boolean isGlobalPreferred();
@@ -4083,9 +4121,12 @@
 
   public final class LinkProperties implements android.os.Parcelable {
     ctor public LinkProperties();
+    ctor public LinkProperties(android.net.LinkProperties);
     method public boolean addDnsServer(java.net.InetAddress);
     method public boolean addRoute(android.net.RouteInfo);
     method public void clear();
+    method @Nullable public android.net.IpPrefix getNat64Prefix();
+    method public java.util.List<java.net.InetAddress> getPcscfServers();
     method public String getTcpBufferSizes();
     method public java.util.List<java.net.InetAddress> getValidatedPrivateDnsServers();
     method public boolean hasGlobalIPv6Address();
@@ -4103,6 +4144,8 @@
     method public void setInterfaceName(String);
     method public void setLinkAddresses(java.util.Collection<android.net.LinkAddress>);
     method public void setMtu(int);
+    method public void setNat64Prefix(android.net.IpPrefix);
+    method public void setPcscfServers(java.util.Collection<java.net.InetAddress>);
     method public void setPrivateDnsServerName(@Nullable String);
     method public void setTcpBufferSizes(String);
     method public void setUsePrivateDns(boolean);
@@ -4157,6 +4200,7 @@
   }
 
   public final class RouteInfo implements android.os.Parcelable {
+    ctor public RouteInfo(android.net.IpPrefix, java.net.InetAddress, String, int);
     method public int getType();
     field public static final int RTN_THROW = 9; // 0x9
     field public static final int RTN_UNICAST = 1; // 0x1
@@ -4194,6 +4238,24 @@
     field public final android.net.RssiCurve rssiCurve;
   }
 
+  public final class StaticIpConfiguration implements android.os.Parcelable {
+    ctor public StaticIpConfiguration();
+    ctor public StaticIpConfiguration(android.net.StaticIpConfiguration);
+    method public void addDnsServer(java.net.InetAddress);
+    method public void clear();
+    method public int describeContents();
+    method public java.util.List<java.net.InetAddress> getDnsServers();
+    method public String getDomains();
+    method public java.net.InetAddress getGateway();
+    method public android.net.LinkAddress getIpAddress();
+    method public java.util.List<android.net.RouteInfo> getRoutes(String);
+    method public void setDomains(String);
+    method public void setGateway(java.net.InetAddress);
+    method public void setIpAddress(android.net.LinkAddress);
+    method public void writeToParcel(android.os.Parcel, int);
+    field public static final android.os.Parcelable.Creator<android.net.StaticIpConfiguration> CREATOR;
+  }
+
   public class TrafficStats {
     method public static void setThreadStatsTagApp();
     method public static void setThreadStatsTagBackup();
@@ -4219,6 +4281,47 @@
 
 }
 
+package android.net.apf {
+
+  public class ApfCapabilities {
+    ctor public ApfCapabilities(int, int, int);
+    method public boolean hasDataAccess();
+    field public final int apfPacketFormat;
+    field public final int apfVersionSupported;
+    field public final int maximumApfProgramSize;
+  }
+
+}
+
+package android.net.captiveportal {
+
+  public final class CaptivePortalProbeResult {
+    ctor public CaptivePortalProbeResult(int);
+    ctor public CaptivePortalProbeResult(int, String, String);
+    ctor public CaptivePortalProbeResult(int, String, String, android.net.captiveportal.CaptivePortalProbeSpec);
+    method public boolean isFailed();
+    method public boolean isPortal();
+    method public boolean isSuccessful();
+    field public static final android.net.captiveportal.CaptivePortalProbeResult FAILED;
+    field public static final int FAILED_CODE = 599; // 0x257
+    field public static final int PORTAL_CODE = 302; // 0x12e
+    field public static final android.net.captiveportal.CaptivePortalProbeResult SUCCESS;
+    field public static final int SUCCESS_CODE = 204; // 0xcc
+    field public final String detectUrl;
+    field @Nullable public final android.net.captiveportal.CaptivePortalProbeSpec probeSpec;
+    field public final String redirectUrl;
+  }
+
+  public abstract class CaptivePortalProbeSpec {
+    method public String getEncodedSpec();
+    method public abstract android.net.captiveportal.CaptivePortalProbeResult getResult(int, @Nullable String);
+    method public java.net.URL getUrl();
+    method public static java.util.Collection<android.net.captiveportal.CaptivePortalProbeSpec> parseCaptivePortalProbeSpecs(String);
+    method @Nullable public static android.net.captiveportal.CaptivePortalProbeSpec parseSpecOrNull(@Nullable String);
+  }
+
+}
+
 package android.net.metrics {
 
   public final class ApfProgramEvent implements android.net.metrics.IpConnectivityLog.Event {
@@ -4290,6 +4393,7 @@
   }
 
   public class IpConnectivityLog {
+    ctor public IpConnectivityLog();
     method public boolean log(long, android.net.metrics.IpConnectivityLog.Event);
     method public boolean log(String, android.net.metrics.IpConnectivityLog.Event);
     method public boolean log(android.net.Network, int[], android.net.metrics.IpConnectivityLog.Event);
@@ -4338,6 +4442,20 @@
     field public static final int NETWORK_VALIDATION_FAILED = 3; // 0x3
   }
 
+  public final class RaEvent implements android.net.metrics.IpConnectivityLog.Event {
+  }
+
+  public static class RaEvent.Builder {
+    ctor public RaEvent.Builder();
+    method public android.net.metrics.RaEvent build();
+    method public android.net.metrics.RaEvent.Builder updateDnsslLifetime(long);
+    method public android.net.metrics.RaEvent.Builder updatePrefixPreferredLifetime(long);
+    method public android.net.metrics.RaEvent.Builder updatePrefixValidLifetime(long);
+    method public android.net.metrics.RaEvent.Builder updateRdnssLifetime(long);
+    method public android.net.metrics.RaEvent.Builder updateRouteInfoLifetime(long);
+    method public android.net.metrics.RaEvent.Builder updateRouterLifetime(long);
+  }
+
   public final class ValidationProbeEvent implements android.net.metrics.IpConnectivityLog.Event {
     method public static String getProbeName(int);
     field public static final int DNS_FAILURE = 0; // 0x0
@@ -4609,6 +4727,7 @@
   }
 
   public class WifiManager {
+    method @RequiresPermission("android.permission.WIFI_UPDATE_USABILITY_STATS_SCORE") public void addWifiUsabilityStatsListener(@NonNull java.util.concurrent.Executor, @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 connect(android.net.wifi.WifiConfiguration, android.net.wifi.WifiManager.ActionListener);
     method @RequiresPermission(anyOf={"android.permission.NETWORK_SETTINGS", android.Manifest.permission.NETWORK_SETUP_WIZARD, "android.permission.NETWORK_STACK"}) public void connect(int, android.net.wifi.WifiManager.ActionListener);
     method @RequiresPermission(anyOf={"android.permission.NETWORK_SETTINGS", android.Manifest.permission.NETWORK_SETUP_WIZARD, "android.permission.NETWORK_STACK"}) public void disable(int, android.net.wifi.WifiManager.ActionListener);
@@ -4616,14 +4735,14 @@
     method @RequiresPermission(anyOf={"android.permission.NETWORK_SETTINGS", android.Manifest.permission.NETWORK_SETUP_WIZARD}) public java.util.List<android.util.Pair<android.net.wifi.WifiConfiguration,java.util.Map<java.lang.Integer,java.util.List<android.net.wifi.ScanResult>>>> getAllMatchingWifiConfigs(@NonNull java.util.List<android.net.wifi.ScanResult>);
     method @RequiresPermission(anyOf={"android.permission.NETWORK_SETTINGS", android.Manifest.permission.NETWORK_SETUP_WIZARD}) public java.util.Map<android.net.wifi.hotspot2.OsuProvider,java.util.List<android.net.wifi.ScanResult>> getMatchingOsuProviders(java.util.List<android.net.wifi.ScanResult>);
     method @RequiresPermission(anyOf={"android.permission.NETWORK_SETTINGS", android.Manifest.permission.NETWORK_SETUP_WIZARD}) public java.util.Map<android.net.wifi.hotspot2.OsuProvider,android.net.wifi.hotspot2.PasspointConfiguration> getMatchingPasspointConfigsForOsuProviders(@NonNull java.util.Set<android.net.wifi.hotspot2.OsuProvider>);
-    method @RequiresPermission(allOf={android.Manifest.permission.ACCESS_COARSE_LOCATION, android.Manifest.permission.ACCESS_WIFI_STATE, android.Manifest.permission.READ_WIFI_CREDENTIAL}) public java.util.List<android.net.wifi.WifiConfiguration> getPrivilegedConfiguredNetworks();
+    method @RequiresPermission(allOf={android.Manifest.permission.ACCESS_FINE_LOCATION, android.Manifest.permission.ACCESS_WIFI_STATE, android.Manifest.permission.READ_WIFI_CREDENTIAL}) public java.util.List<android.net.wifi.WifiConfiguration> getPrivilegedConfiguredNetworks();
     method @RequiresPermission(android.Manifest.permission.ACCESS_WIFI_STATE) public android.net.wifi.WifiConfiguration getWifiApConfiguration();
     method @RequiresPermission(android.Manifest.permission.ACCESS_WIFI_STATE) public int getWifiApState();
     method public boolean isDeviceToDeviceRttSupported();
     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);
     method @RequiresPermission(android.Manifest.permission.CHANGE_WIFI_STATE) public boolean setWifiApConfiguration(android.net.wifi.WifiConfiguration);
@@ -4632,7 +4751,7 @@
     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
     field public static final int CHANGE_REASON_REMOVED = 1; // 0x1
@@ -4668,17 +4787,8 @@
     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);
   }
 
   public class WifiNetworkConnectionStatistics implements android.os.Parcelable {
@@ -4810,6 +4920,31 @@
     field @Deprecated public int unchangedSampleSize;
   }
 
+  public final class WifiUsabilityStatsEntry implements android.os.Parcelable {
+    method public int describeContents();
+    method public void writeToParcel(android.os.Parcel, int);
+    field public static final android.os.Parcelable.Creator<android.net.wifi.WifiUsabilityStatsEntry> CREATOR;
+    field public final int linkSpeedMbps;
+    field public final int rssi;
+    field public final long timeStampMs;
+    field public final long totalBackgroundScanTimeMs;
+    field public final long totalBeaconRx;
+    field public final long totalCcaBusyFreqTimeMs;
+    field public final long totalHotspot2ScanTimeMs;
+    field public final long totalNanScanTimeMs;
+    field public final long totalPnoScanTimeMs;
+    field public final long totalRadioOnFreqTimeMs;
+    field public final long totalRadioOnTimeMs;
+    field public final long totalRadioRxTimeMs;
+    field public final long totalRadioTxTimeMs;
+    field public final long totalRoamScanTimeMs;
+    field public final long totalRxSuccess;
+    field public final long totalScanTimeMs;
+    field public final long totalTxBad;
+    field public final long totalTxRetries;
+    field public final long totalTxSuccess;
+  }
+
 }
 
 package android.net.wifi.aware {
@@ -4955,6 +5090,7 @@
 package android.os {
 
   public class BatteryManager {
+    method @RequiresPermission(android.Manifest.permission.POWER_SAVER) public boolean setChargingStateUpdateDelayMillis(int);
     field public static final String EXTRA_EVENTS = "android.os.extra.EVENTS";
     field public static final String EXTRA_EVENT_TIMESTAMP = "android.os.extra.EVENT_TIMESTAMP";
   }
@@ -4972,6 +5108,36 @@
     method public Object onTransactStarted(android.os.IBinder, int);
   }
 
+  public class BugreportManager {
+    method @RequiresPermission(android.Manifest.permission.DUMP) public void cancelBugreport();
+    method @RequiresPermission(android.Manifest.permission.DUMP) public void startBugreport(@NonNull android.os.ParcelFileDescriptor, @Nullable android.os.ParcelFileDescriptor, @NonNull android.os.BugreportParams, @NonNull java.util.concurrent.Executor, @NonNull android.os.BugreportManager.BugreportCallback);
+  }
+
+  public abstract static class BugreportManager.BugreportCallback {
+    ctor public BugreportManager.BugreportCallback();
+    method public void onError(int);
+    method public void onFinished();
+    method public void onProgress(float);
+    field public static final int BUGREPORT_ERROR_INVALID_INPUT = 1; // 0x1
+    field public static final int BUGREPORT_ERROR_RUNTIME = 2; // 0x2
+    field public static final int BUGREPORT_ERROR_USER_CONSENT_TIMED_OUT = 4; // 0x4
+    field public static final int BUGREPORT_ERROR_USER_DENIED_CONSENT = 3; // 0x3
+  }
+
+  public final class BugreportParams {
+    ctor public BugreportParams(@android.os.BugreportParams.BugreportMode int);
+    method public int getMode();
+    field public static final int BUGREPORT_MODE_FULL = 0; // 0x0
+    field public static final int BUGREPORT_MODE_INTERACTIVE = 1; // 0x1
+    field public static final int BUGREPORT_MODE_REMOTE = 2; // 0x2
+    field public static final int BUGREPORT_MODE_TELEPHONY = 4; // 0x4
+    field public static final int BUGREPORT_MODE_WEAR = 3; // 0x3
+    field public static final int BUGREPORT_MODE_WIFI = 5; // 0x5
+  }
+
+  @java.lang.annotation.Retention(java.lang.annotation.RetentionPolicy.SOURCE) @IntDef(prefix={"BUGREPORT_MODE_"}, value={android.os.BugreportParams.BUGREPORT_MODE_FULL, android.os.BugreportParams.BUGREPORT_MODE_INTERACTIVE, android.os.BugreportParams.BUGREPORT_MODE_REMOTE, android.os.BugreportParams.BUGREPORT_MODE_WEAR, android.os.BugreportParams.BUGREPORT_MODE_TELEPHONY, android.os.BugreportParams.BUGREPORT_MODE_WIFI}) public static @interface BugreportParams.BugreportMode {
+  }
+
   public static class Build.VERSION {
     field public static final String PREVIEW_SDK_FINGERPRINT;
   }
@@ -5400,8 +5566,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";
@@ -5445,7 +5612,6 @@
     method @Deprecated public final void attachBaseContext(android.content.Context);
     method @Deprecated public final android.os.IBinder onBind(android.content.Intent);
     method @Deprecated public abstract java.util.List<android.content.pm.permission.RuntimePermissionPresentationInfo> onGetAppPermissions(@NonNull String);
-    method @Deprecated public abstract void onRevokeRuntimePermission(@NonNull String, @NonNull String);
     field @Deprecated public static final String SERVICE_INTERFACE = "android.permissionpresenterservice.RuntimePermissionPresenterService";
   }
 
@@ -5565,10 +5731,39 @@
     field public static final String NAMESPACE_NOTIFICATION_ASSISTANT = "notification_assistant";
   }
 
+  public static interface DeviceConfig.ActivityManager {
+    field public static final String KEY_COMPACT_ACTION_1 = "compact_action_1";
+    field public static final String KEY_COMPACT_ACTION_2 = "compact_action_2";
+    field public static final String KEY_COMPACT_THROTTLE_1 = "compact_throttle_1";
+    field public static final String KEY_COMPACT_THROTTLE_2 = "compact_throttle_2";
+    field public static final String KEY_COMPACT_THROTTLE_3 = "compact_throttle_3";
+    field public static final String KEY_COMPACT_THROTTLE_4 = "compact_throttle_4";
+    field public static final String KEY_MAX_CACHED_PROCESSES = "max_cached_processes";
+    field public static final String KEY_USE_COMPACTION = "use_compaction";
+    field public static final String NAMESPACE = "activity_manager";
+  }
+
+  public static interface DeviceConfig.FsiBoot {
+    field public static final String NAMESPACE = "fsi_boot";
+    field public static final String OOB_ENABLED = "oob_enabled";
+    field public static final String OOB_WHITELIST = "oob_whitelist";
+  }
+
+  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";
+  }
+
   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";
@@ -5756,6 +5951,7 @@
     field public static final String LOCK_SCREEN_ALLOW_PRIVATE_NOTIFICATIONS = "lock_screen_allow_private_notifications";
     field public static final String LOCK_SCREEN_SHOW_NOTIFICATIONS = "lock_screen_show_notifications";
     field public static final String MANUAL_RINGER_TOGGLE_COUNT = "manual_ringer_toggle_count";
+    field public static final String THEME_CUSTOMIZATION_OVERLAY_PACKAGES = "theme_customization_overlay_packages";
     field public static final String USER_SETUP_COMPLETE = "user_setup_complete";
     field public static final int USER_SETUP_PERSONALIZATION_COMPLETE = 10; // 0xa
     field public static final int USER_SETUP_PERSONALIZATION_NOT_STARTED = 0; // 0x0
@@ -6270,77 +6466,6 @@
 
 package android.service.notification {
 
-  public final class Adjustment implements android.os.Parcelable {
-    ctor public Adjustment(String, String, android.os.Bundle, CharSequence, int);
-    ctor protected Adjustment(android.os.Parcel);
-    method public int describeContents();
-    method public CharSequence getExplanation();
-    method public String getKey();
-    method public String getPackage();
-    method public android.os.Bundle getSignals();
-    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_IMPORTANCE = "key_importance";
-    field public static final String KEY_PEOPLE = "key_people";
-    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_SNOOZE_CRITERIA = "key_snooze_criteria";
-    field public static final String KEY_USER_SENTIMENT = "key_user_sentiment";
-  }
-
-  public abstract class NotificationAssistantService extends android.service.notification.NotificationListenerService {
-    ctor public NotificationAssistantService();
-    method public final void adjustNotification(android.service.notification.Adjustment);
-    method public final void adjustNotifications(java.util.List<android.service.notification.Adjustment>);
-    method public void onActionInvoked(@NonNull String, @NonNull android.app.Notification.Action, int);
-    method public final android.os.IBinder onBind(android.content.Intent);
-    method public void onNotificationDirectReplied(@NonNull String);
-    method public android.service.notification.Adjustment onNotificationEnqueued(android.service.notification.StatusBarNotification);
-    method public android.service.notification.Adjustment onNotificationEnqueued(android.service.notification.StatusBarNotification, android.app.NotificationChannel);
-    method public void onNotificationExpansionChanged(@NonNull String, boolean, boolean);
-    method public void onNotificationRemoved(android.service.notification.StatusBarNotification, android.service.notification.NotificationListenerService.RankingMap, android.service.notification.NotificationStats, int);
-    method public abstract void onNotificationSnoozedUntilContext(android.service.notification.StatusBarNotification, String);
-    method public void onNotificationsSeen(java.util.List<java.lang.String>);
-    method public void onSuggestedReplySent(@NonNull String, @NonNull CharSequence, int);
-    method public final void unsnoozeNotification(String);
-    field public static final String SERVICE_INTERFACE = "android.service.notification.NotificationAssistantService";
-    field public static final int SOURCE_FROM_APP = 0; // 0x0
-    field public static final int SOURCE_FROM_ASSISTANT = 1; // 0x1
-  }
-
-  public final class NotificationStats implements android.os.Parcelable {
-    ctor public NotificationStats();
-    ctor protected NotificationStats(android.os.Parcel);
-    method public int describeContents();
-    method public int getDismissalSentiment();
-    method public int getDismissalSurface();
-    method public boolean hasDirectReplied();
-    method public boolean hasExpanded();
-    method public boolean hasInteracted();
-    method public boolean hasSeen();
-    method public boolean hasSnoozed();
-    method public boolean hasViewedSettings();
-    method public void setDirectReplied();
-    method public void setDismissalSentiment(int);
-    method public void setDismissalSurface(int);
-    method public void setExpanded();
-    method public void setSeen();
-    method public void setSnoozed();
-    method public void setViewedSettings();
-    method public void writeToParcel(android.os.Parcel, int);
-    field public static final android.os.Parcelable.Creator<android.service.notification.NotificationStats> CREATOR;
-    field public static final int DISMISSAL_AOD = 2; // 0x2
-    field public static final int DISMISSAL_NOT_DISMISSED = -1; // 0xffffffff
-    field public static final int DISMISSAL_OTHER = 0; // 0x0
-    field public static final int DISMISSAL_PEEK = 1; // 0x1
-    field public static final int DISMISSAL_SHADE = 3; // 0x3
-    field public static final int DISMISS_SENTIMENT_NEGATIVE = 0; // 0x0
-    field public static final int DISMISS_SENTIMENT_NEUTRAL = 1; // 0x1
-    field public static final int DISMISS_SENTIMENT_POSITIVE = 2; // 0x2
-    field public static final int DISMISS_SENTIMENT_UNKNOWN = -1000; // 0xfffffc18
-  }
-
   public final class SnoozeCriterion implements android.os.Parcelable {
     ctor public SnoozeCriterion(String, CharSequence, CharSequence);
     ctor protected SnoozeCriterion(android.os.Parcel);
@@ -7581,10 +7706,13 @@
 
   public class SubscriptionManager {
     method public java.util.List<android.telephony.SubscriptionInfo> getAvailableSubscriptionInfoList();
+    method @RequiresPermission(android.Manifest.permission.READ_PRIVILEGED_PHONE_STATE) public int getEnabledSubscriptionId(int);
+    method @RequiresPermission(android.Manifest.permission.READ_PRIVILEGED_PHONE_STATE) public boolean isSubscriptionEnabled(int);
     method public void requestEmbeddedSubscriptionInfoListRefresh();
     method public void requestEmbeddedSubscriptionInfoListRefresh(int);
     method @RequiresPermission(android.Manifest.permission.MODIFY_PHONE_STATE) public void setDefaultDataSubId(int);
     method @RequiresPermission(android.Manifest.permission.MODIFY_PHONE_STATE) public void setDefaultSmsSubId(int);
+    method @RequiresPermission(android.Manifest.permission.MODIFY_PHONE_STATE) public boolean setSubscriptionEnabled(int, boolean);
     field public static final android.net.Uri ADVANCED_CALLING_ENABLED_CONTENT_URI;
     field public static final int PROFILE_CLASS_DEFAULT = -1; // 0xffffffff
     field public static final int PROFILE_CLASS_OPERATIONAL = 2; // 0x2
@@ -7799,7 +7927,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();
@@ -7810,9 +7938,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;
   }
@@ -7826,8 +7954,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();
@@ -7961,10 +8089,10 @@
     method @RequiresPermission(android.Manifest.permission.WRITE_EMBEDDED_SUBSCRIPTIONS) public int getOtaStatus();
     field public static final String ACTION_DELETE_SUBSCRIPTION_PRIVILEGED = "android.telephony.euicc.action.DELETE_SUBSCRIPTION_PRIVILEGED";
     field @RequiresPermission(android.Manifest.permission.WRITE_EMBEDDED_SUBSCRIPTIONS) public static final String ACTION_OTA_STATUS_CHANGED = "android.telephony.euicc.action.OTA_STATUS_CHANGED";
-    field public static final String ACTION_PROFILE_SELECTION = "android.telephony.euicc.action.PROFILE_SELECTION";
     field public static final String ACTION_PROVISION_EMBEDDED_SUBSCRIPTION = "android.telephony.euicc.action.PROVISION_EMBEDDED_SUBSCRIPTION";
     field public static final String ACTION_RENAME_SUBSCRIPTION_PRIVILEGED = "android.telephony.euicc.action.RENAME_SUBSCRIPTION_PRIVILEGED";
     field public static final String ACTION_TOGGLE_SUBSCRIPTION_PRIVILEGED = "android.telephony.euicc.action.TOGGLE_SUBSCRIPTION_PRIVILEGED";
+    field public static final int EUICC_ACTIVATION_TYPE_ACCOUNT_REQUIRED = 4; // 0x4
     field public static final int EUICC_ACTIVATION_TYPE_BACKUP = 2; // 0x2
     field public static final int EUICC_ACTIVATION_TYPE_DEFAULT = 1; // 0x1
     field public static final int EUICC_ACTIVATION_TYPE_TRANSFER = 3; // 0x3
@@ -8194,6 +8322,16 @@
     field public final java.util.HashMap<java.lang.String,android.os.Bundle> mParticipants;
   }
 
+  public class ImsException extends java.lang.Exception {
+    ctor public ImsException(@Nullable String);
+    ctor public ImsException(@Nullable String, int);
+    ctor public ImsException(@Nullable String, int, Throwable);
+    method public int getCode();
+    field public static final int CODE_ERROR_SERVICE_UNAVAILABLE = 1; // 0x1
+    field public static final int CODE_ERROR_UNSPECIFIED = 0; // 0x0
+    field public static final int CODE_ERROR_UNSUPPORTED_OPERATION = 2; // 0x2
+  }
+
   public final class ImsExternalCallState implements android.os.Parcelable {
     ctor public ImsExternalCallState(String, android.net.Uri, android.net.Uri, boolean, int, int, boolean);
     method public int describeContents();
@@ -8211,7 +8349,7 @@
   }
 
   public class ImsMmTelManager {
-    method public static android.telephony.ims.ImsMmTelManager createForSubscriptionId(android.content.Context, int);
+    method public static android.telephony.ims.ImsMmTelManager createForSubscriptionId(int);
     method @RequiresPermission(android.Manifest.permission.READ_PRIVILEGED_PHONE_STATE) public int getVoWiFiModeSetting();
     method @RequiresPermission(android.Manifest.permission.READ_PRIVILEGED_PHONE_STATE) public int getVoWiFiRoamingModeSetting();
     method @RequiresPermission(android.Manifest.permission.READ_PRIVILEGED_PHONE_STATE) public boolean isAdvancedCallingSettingEnabled();
@@ -8220,8 +8358,8 @@
     method @RequiresPermission(android.Manifest.permission.READ_PRIVILEGED_PHONE_STATE) public boolean isVoWiFiRoamingSettingEnabled();
     method @RequiresPermission(android.Manifest.permission.READ_PRIVILEGED_PHONE_STATE) public boolean isVoWiFiSettingEnabled();
     method @RequiresPermission(android.Manifest.permission.READ_PRIVILEGED_PHONE_STATE) public boolean isVtSettingEnabled();
-    method @RequiresPermission(android.Manifest.permission.READ_PRIVILEGED_PHONE_STATE) public void registerImsRegistrationCallback(java.util.concurrent.Executor, @NonNull android.telephony.ims.ImsMmTelManager.RegistrationCallback);
-    method @RequiresPermission(android.Manifest.permission.READ_PRIVILEGED_PHONE_STATE) public void registerMmTelCapabilityCallback(@NonNull java.util.concurrent.Executor, @NonNull android.telephony.ims.ImsMmTelManager.CapabilityCallback);
+    method @RequiresPermission(android.Manifest.permission.READ_PRIVILEGED_PHONE_STATE) public void registerImsRegistrationCallback(java.util.concurrent.Executor, @NonNull android.telephony.ims.ImsMmTelManager.RegistrationCallback) throws android.telephony.ims.ImsException;
+    method @RequiresPermission(android.Manifest.permission.READ_PRIVILEGED_PHONE_STATE) public void registerMmTelCapabilityCallback(@NonNull java.util.concurrent.Executor, @NonNull android.telephony.ims.ImsMmTelManager.CapabilityCallback) throws android.telephony.ims.ImsException;
     method @RequiresPermission(android.Manifest.permission.MODIFY_PHONE_STATE) public void setAdvancedCallingSetting(boolean);
     method @RequiresPermission(android.Manifest.permission.MODIFY_PHONE_STATE) public void setRttCapabilitySetting(boolean);
     method @RequiresPermission(android.Manifest.permission.MODIFY_PHONE_STATE) public void setVoWiFiModeSetting(int);
@@ -8656,13 +8794,21 @@
   }
 
   public class ProvisioningManager {
-    method public static android.telephony.ims.ProvisioningManager createForSubscriptionId(android.content.Context, int);
-    method @RequiresPermission(android.Manifest.permission.READ_PRIVILEGED_PHONE_STATE) public int getProvisioningIntValue(int);
-    method @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.MODIFY_PHONE_STATE) public int setProvisioningIntValue(int, int);
-    method @RequiresPermission(android.Manifest.permission.MODIFY_PHONE_STATE) public int setProvisioningStringValue(int, String);
+    method public static android.telephony.ims.ProvisioningManager createForSubscriptionId(int);
+    method @WorkerThread @RequiresPermission(android.Manifest.permission.READ_PRIVILEGED_PHONE_STATE) public int getProvisioningIntValue(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) throws android.telephony.ims.ImsException;
+    method @RequiresPermission(android.Manifest.permission.MODIFY_PHONE_STATE) @WorkerThread public int setProvisioningIntValue(int, int);
+    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
+    field public static final int KEY_VOICE_OVER_WIFI_ROAMING_ENABLED_OVERRIDE = 26; // 0x1a
+    field public static final int PROVISIONING_VALUE_DISABLED = 0; // 0x0
+    field public static final int PROVISIONING_VALUE_ENABLED = 1; // 0x1
+    field public static final String STRING_QUERY_RESULT_ERROR_GENERIC = "STRING_QUERY_RESULT_ERROR_GENERIC";
+    field public static final String STRING_QUERY_RESULT_ERROR_NOT_READY = "STRING_QUERY_RESULT_ERROR_NOT_READY";
   }
 
   public static class ProvisioningManager.Callback {
@@ -9104,6 +9250,7 @@
 package android.view.contentcapture {
 
   public final class ContentCaptureContext implements android.os.Parcelable {
+    method @Nullable public String getAction();
     method @Nullable public android.content.ComponentName getActivityComponent();
     method public int getDisplayId();
     method @Nullable public android.os.Bundle getExtras();
diff --git a/api/test-current.txt b/api/test-current.txt
index c746882..049e002 100644
--- a/api/test-current.txt
+++ b/api/test-current.txt
@@ -6,14 +6,20 @@
     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";
   }
 
+  public static final class R.array {
+    field public static final int config_defaultRoleHolders = 17235974; // 0x1070006
+  }
+
 }
 
 package android.animation {
@@ -332,6 +338,7 @@
     method @NonNull @RequiresPermission("android.permission.MANAGE_ROLE_HOLDERS") public java.util.List<java.lang.String> getRoleHolders(@NonNull String);
     method @NonNull @RequiresPermission("android.permission.MANAGE_ROLE_HOLDERS") public java.util.List<java.lang.String> getRoleHoldersAsUser(@NonNull String, @NonNull android.os.UserHandle);
     method @RequiresPermission("android.permission.MANAGE_ROLE_HOLDERS") public void removeRoleHolderAsUser(@NonNull String, @NonNull String, @NonNull android.os.UserHandle, @NonNull java.util.concurrent.Executor, @NonNull android.app.role.RoleManagerCallback);
+    field public static final String ROLE_ASSISTANT = "android.app.role.ASSISTANT";
   }
 
   public interface RoleManagerCallback {
@@ -802,11 +809,16 @@
     field public static final String EXTRA_CAPTIVE_PORTAL_USER_AGENT = "android.net.extra.CAPTIVE_PORTAL_USER_AGENT";
   }
 
+  public final class IpPrefix implements android.os.Parcelable {
+    ctor public IpPrefix(java.net.InetAddress, int);
+  }
+
   public final class IpSecManager {
     field public static final int INVALID_SECURITY_PARAMETER_INDEX = 0; // 0x0
   }
 
   public class LinkAddress implements android.os.Parcelable {
+    ctor public LinkAddress(java.net.InetAddress, int, int, int);
     method public boolean isGlobalPreferred();
     method public boolean isIPv4();
     method public boolean isIPv6();
@@ -814,7 +826,10 @@
   }
 
   public final class LinkProperties implements android.os.Parcelable {
+    ctor public LinkProperties(android.net.LinkProperties);
     method public boolean addDnsServer(java.net.InetAddress);
+    method @Nullable public android.net.IpPrefix getNat64Prefix();
+    method public java.util.List<java.net.InetAddress> getPcscfServers();
     method public String getTcpBufferSizes();
     method public java.util.List<java.net.InetAddress> getValidatedPrivateDnsServers();
     method public boolean hasGlobalIPv6Address();
@@ -826,6 +841,8 @@
     method public boolean isReachable(java.net.InetAddress);
     method public boolean removeDnsServer(java.net.InetAddress);
     method public boolean removeRoute(android.net.RouteInfo);
+    method public void setNat64Prefix(android.net.IpPrefix);
+    method public void setPcscfServers(java.util.Collection<java.net.InetAddress>);
     method public void setPrivateDnsServerName(@Nullable String);
     method public void setTcpBufferSizes(String);
     method public void setUsePrivateDns(boolean);
@@ -843,12 +860,31 @@
   }
 
   public final class RouteInfo implements android.os.Parcelable {
+    ctor public RouteInfo(android.net.IpPrefix, java.net.InetAddress, String, int);
     method public int getType();
     field public static final int RTN_THROW = 9; // 0x9
     field public static final int RTN_UNICAST = 1; // 0x1
     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();
@@ -858,6 +894,47 @@
 
 }
 
+package android.net.apf {
+
+  public class ApfCapabilities {
+    ctor public ApfCapabilities(int, int, int);
+    method public boolean hasDataAccess();
+    field public final int apfPacketFormat;
+    field public final int apfVersionSupported;
+    field public final int maximumApfProgramSize;
+  }
+
+}
+
+package android.net.captiveportal {
+
+  public final class CaptivePortalProbeResult {
+    ctor public CaptivePortalProbeResult(int);
+    ctor public CaptivePortalProbeResult(int, String, String);
+    ctor public CaptivePortalProbeResult(int, String, String, android.net.captiveportal.CaptivePortalProbeSpec);
+    method public boolean isFailed();
+    method public boolean isPortal();
+    method public boolean isSuccessful();
+    field public static final android.net.captiveportal.CaptivePortalProbeResult FAILED;
+    field public static final int FAILED_CODE = 599; // 0x257
+    field public static final int PORTAL_CODE = 302; // 0x12e
+    field public static final android.net.captiveportal.CaptivePortalProbeResult SUCCESS;
+    field public static final int SUCCESS_CODE = 204; // 0xcc
+    field public final String detectUrl;
+    field @Nullable public final android.net.captiveportal.CaptivePortalProbeSpec probeSpec;
+    field public final String redirectUrl;
+  }
+
+  public abstract class CaptivePortalProbeSpec {
+    method public String getEncodedSpec();
+    method public abstract android.net.captiveportal.CaptivePortalProbeResult getResult(int, @Nullable String);
+    method public java.net.URL getUrl();
+    method public static java.util.Collection<android.net.captiveportal.CaptivePortalProbeSpec> parseCaptivePortalProbeSpecs(String);
+    method @Nullable public static android.net.captiveportal.CaptivePortalProbeSpec parseSpecOrNull(@Nullable String);
+  }
+
+}
+
 package android.net.metrics {
 
   public final class ApfProgramEvent implements android.net.metrics.IpConnectivityLog.Event {
@@ -929,6 +1006,7 @@
   }
 
   public class IpConnectivityLog {
+    ctor public IpConnectivityLog();
     method public boolean log(long, android.net.metrics.IpConnectivityLog.Event);
     method public boolean log(String, android.net.metrics.IpConnectivityLog.Event);
     method public boolean log(android.net.Network, int[], android.net.metrics.IpConnectivityLog.Event);
@@ -977,6 +1055,20 @@
     field public static final int NETWORK_VALIDATION_FAILED = 3; // 0x3
   }
 
+  public final class RaEvent implements android.net.metrics.IpConnectivityLog.Event {
+  }
+
+  public static class RaEvent.Builder {
+    ctor public RaEvent.Builder();
+    method public android.net.metrics.RaEvent build();
+    method public android.net.metrics.RaEvent.Builder updateDnsslLifetime(long);
+    method public android.net.metrics.RaEvent.Builder updatePrefixPreferredLifetime(long);
+    method public android.net.metrics.RaEvent.Builder updatePrefixValidLifetime(long);
+    method public android.net.metrics.RaEvent.Builder updateRdnssLifetime(long);
+    method public android.net.metrics.RaEvent.Builder updateRouteInfoLifetime(long);
+    method public android.net.metrics.RaEvent.Builder updateRouterLifetime(long);
+  }
+
   public final class ValidationProbeEvent implements android.net.metrics.IpConnectivityLog.Event {
     method public static String getProbeName(int);
     field public static final int DNS_FAILURE = 0; // 0x0
@@ -1021,6 +1113,10 @@
     method public static java.io.File getStorageDirectory();
   }
 
+  public class FileUtils {
+    method public static boolean contains(java.io.File, java.io.File);
+  }
+
   public abstract class HwBinder implements android.os.IHwBinder {
     ctor public HwBinder();
     method public static final void configureRpcThreadpool(long, boolean);
@@ -1186,6 +1282,10 @@
     method public boolean hasSingleFileDescriptor();
   }
 
+  public class ParcelFileDescriptor implements java.io.Closeable android.os.Parcelable {
+    method public static java.io.File getFile(java.io.FileDescriptor) throws java.io.IOException;
+  }
+
   public final class PowerManager {
     method @RequiresPermission("android.permission.POWER_SAVER") public int getPowerSaveMode();
     method @RequiresPermission("android.permission.POWER_SAVER") public boolean setDynamicPowerSavings(boolean, int);
@@ -1273,15 +1373,11 @@
     method @Nullable public static android.os.VibrationEffect get(android.net.Uri, android.content.Context);
     method public abstract long getDuration();
     method protected static int scale(int, float, int);
-    field public static final int EFFECT_CLICK = 0; // 0x0
-    field public static final int EFFECT_DOUBLE_CLICK = 1; // 0x1
-    field public static final int EFFECT_HEAVY_CLICK = 5; // 0x5
     field public static final int EFFECT_POP = 4; // 0x4
     field public static final int EFFECT_STRENGTH_LIGHT = 0; // 0x0
     field public static final int EFFECT_STRENGTH_MEDIUM = 1; // 0x1
     field public static final int EFFECT_STRENGTH_STRONG = 2; // 0x2
     field public static final int EFFECT_THUD = 3; // 0x3
-    field public static final int EFFECT_TICK = 2; // 0x2
     field public static final int[] RINGTONES;
   }
 
@@ -1457,17 +1553,37 @@
 
 package android.provider {
 
+  public static final class CalendarContract.Calendars implements android.provider.BaseColumns android.provider.CalendarContract.CalendarColumns android.provider.CalendarContract.SyncColumns {
+    field public static final String[] SYNC_WRITABLE_COLUMNS;
+  }
+
+  public static final class CalendarContract.Events implements android.provider.BaseColumns android.provider.CalendarContract.CalendarColumns android.provider.CalendarContract.EventsColumns android.provider.CalendarContract.SyncColumns {
+    field public static final String[] SYNC_WRITABLE_COLUMNS;
+  }
+
+  public final class ContactsContract {
+    field public static final String HIDDEN_COLUMN_PREFIX = "x_";
+  }
+
   public static final class ContactsContract.CommonDataKinds.Phone implements android.provider.ContactsContract.CommonDataKinds.CommonColumns android.provider.ContactsContract.DataColumnsWithJoins {
     field public static final android.net.Uri ENTERPRISE_CONTENT_URI;
   }
 
+  public static final class ContactsContract.PinnedPositions {
+    field public static final String UNDEMOTE_METHOD = "undemote";
+  }
+
   public static final class ContactsContract.RawContactsEntity implements android.provider.BaseColumns android.provider.ContactsContract.DataColumns android.provider.ContactsContract.RawContactsColumns {
     field public static final android.net.Uri CORP_CONTENT_URI;
   }
 
   public final class MediaStore {
-    method @RequiresPermission("android.permission.CLEAR_APP_USER_DATA") public static void deleteContributedMedia(android.content.Context, String, android.os.UserHandle) throws java.io.IOException;
-    method @RequiresPermission("android.permission.CLEAR_APP_USER_DATA") public static long getContributedMediaSize(android.content.Context, String, android.os.UserHandle) throws java.io.IOException;
+    method @RequiresPermission(android.Manifest.permission.CLEAR_APP_USER_DATA) public static void deleteContributedMedia(android.content.Context, String, android.os.UserHandle) throws java.io.IOException;
+    method @RequiresPermission(android.Manifest.permission.CLEAR_APP_USER_DATA) public static long getContributedMediaSize(android.content.Context, String, android.os.UserHandle) throws java.io.IOException;
+    method @NonNull public static java.io.File getVolumePath(@NonNull String) throws java.io.FileNotFoundException;
+    method @NonNull public static java.util.Collection<java.io.File> getVolumeScanPaths(@NonNull String) throws java.io.FileNotFoundException;
+    field public static final String SCAN_FILE_CALL = "scan_file";
+    field public static final String SCAN_VOLUME_CALL = "scan_volume";
   }
 
   public final class Settings {
@@ -1527,6 +1643,10 @@
     field public static final String SMS_CARRIER_PROVISION_ACTION = "android.provider.Telephony.SMS_CARRIER_PROVISION";
   }
 
+  public static final class VoicemailContract.Voicemails implements android.provider.BaseColumns android.provider.OpenableColumns {
+    field public static final String _DATA = "_data";
+  }
+
 }
 
 package android.security {
@@ -1725,84 +1845,14 @@
 
 package android.service.notification {
 
-  public final class Adjustment implements android.os.Parcelable {
-    ctor public Adjustment(String, String, android.os.Bundle, CharSequence, int);
-    ctor protected Adjustment(android.os.Parcel);
-    method public int describeContents();
-    method public CharSequence getExplanation();
-    method public String getKey();
-    method public String getPackage();
-    method public android.os.Bundle getSignals();
-    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_IMPORTANCE = "key_importance";
-    field public static final String KEY_PEOPLE = "key_people";
-    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_SNOOZE_CRITERIA = "key_snooze_criteria";
-    field public static final String KEY_USER_SENTIMENT = "key_user_sentiment";
-  }
-
   @Deprecated public abstract class ConditionProviderService extends android.app.Service {
     method @Deprecated public boolean isBound();
   }
 
-  public abstract class NotificationAssistantService extends android.service.notification.NotificationListenerService {
-    ctor public NotificationAssistantService();
-    method public final void adjustNotification(android.service.notification.Adjustment);
-    method public final void adjustNotifications(java.util.List<android.service.notification.Adjustment>);
-    method public void onActionInvoked(@NonNull String, @NonNull android.app.Notification.Action, int);
-    method public final android.os.IBinder onBind(android.content.Intent);
-    method public void onNotificationDirectReplied(@NonNull String);
-    method public android.service.notification.Adjustment onNotificationEnqueued(android.service.notification.StatusBarNotification);
-    method public android.service.notification.Adjustment onNotificationEnqueued(android.service.notification.StatusBarNotification, android.app.NotificationChannel);
-    method public void onNotificationExpansionChanged(@NonNull String, boolean, boolean);
-    method public abstract void onNotificationSnoozedUntilContext(android.service.notification.StatusBarNotification, String);
-    method public void onNotificationsSeen(java.util.List<java.lang.String>);
-    method public void onSuggestedReplySent(@NonNull String, @NonNull CharSequence, int);
-    method public final void unsnoozeNotification(String);
-    field public static final String SERVICE_INTERFACE = "android.service.notification.NotificationAssistantService";
-    field public static final int SOURCE_FROM_APP = 0; // 0x0
-    field public static final int SOURCE_FROM_ASSISTANT = 1; // 0x1
-  }
-
   public abstract class NotificationListenerService extends android.app.Service {
     method public void onNotificationRemoved(android.service.notification.StatusBarNotification, android.service.notification.NotificationListenerService.RankingMap, android.service.notification.NotificationStats, int);
   }
 
-  public final class NotificationStats implements android.os.Parcelable {
-    ctor public NotificationStats();
-    ctor protected NotificationStats(android.os.Parcel);
-    method public int describeContents();
-    method public int getDismissalSentiment();
-    method public int getDismissalSurface();
-    method public boolean hasDirectReplied();
-    method public boolean hasExpanded();
-    method public boolean hasInteracted();
-    method public boolean hasSeen();
-    method public boolean hasSnoozed();
-    method public boolean hasViewedSettings();
-    method public void setDirectReplied();
-    method public void setDismissalSentiment(int);
-    method public void setDismissalSurface(int);
-    method public void setExpanded();
-    method public void setSeen();
-    method public void setSnoozed();
-    method public void setViewedSettings();
-    method public void writeToParcel(android.os.Parcel, int);
-    field public static final android.os.Parcelable.Creator<android.service.notification.NotificationStats> CREATOR;
-    field public static final int DISMISSAL_AOD = 2; // 0x2
-    field public static final int DISMISSAL_NOT_DISMISSED = -1; // 0xffffffff
-    field public static final int DISMISSAL_OTHER = 0; // 0x0
-    field public static final int DISMISSAL_PEEK = 1; // 0x1
-    field public static final int DISMISSAL_SHADE = 3; // 0x3
-    field public static final int DISMISS_SENTIMENT_NEGATIVE = 0; // 0x0
-    field public static final int DISMISS_SENTIMENT_NEUTRAL = 1; // 0x1
-    field public static final int DISMISS_SENTIMENT_POSITIVE = 2; // 0x2
-    field public static final int DISMISS_SENTIMENT_UNKNOWN = -1000; // 0xfffffc18
-  }
-
   public final class SnoozeCriterion implements android.os.Parcelable {
     ctor public SnoozeCriterion(String, CharSequence, CharSequence);
     ctor protected SnoozeCriterion(android.os.Parcel);
@@ -1871,10 +1921,15 @@
   }
 
   public class TelephonyManager {
+    method public int checkCarrierPrivilegesForPackage(String);
     method public int getCarrierIdListVersion();
     method public boolean isRttSupported();
     method @RequiresPermission(android.Manifest.permission.MODIFY_PHONE_STATE) public void refreshUiccProfile();
     method public void setCarrierTestOverride(String, String, String, String, String, String, String);
+    field public static final int CARRIER_PRIVILEGE_STATUS_ERROR_LOADING_RULES = -2; // 0xfffffffe
+    field public static final int CARRIER_PRIVILEGE_STATUS_HAS_ACCESS = 1; // 0x1
+    field public static final int CARRIER_PRIVILEGE_STATUS_NO_ACCESS = 0; // 0x0
+    field public static final int CARRIER_PRIVILEGE_STATUS_RULES_NOT_LOADED = -1; // 0xffffffff
     field public static final int UNKNOWN_CARRIER_ID_LIST_VERSION = -1; // 0xffffffff
   }
 
diff --git a/cmds/bmgr/src/com/android/commands/bmgr/Bmgr.java b/cmds/bmgr/src/com/android/commands/bmgr/Bmgr.java
index 3defdc5..062ba65 100644
--- a/cmds/bmgr/src/com/android/commands/bmgr/Bmgr.java
+++ b/cmds/bmgr/src/com/android/commands/bmgr/Bmgr.java
@@ -102,7 +102,17 @@
         String op = nextArg();
         Slog.v(TAG, "Running " + op + " for user:" + userId);
 
-        if (!isBmgrActive(userId)) {
+        if (mBmgr == null) {
+            System.err.println(BMGR_NOT_RUNNING_ERR);
+            return;
+        }
+
+        if ("activate".equals(op)) {
+            doActivateService(userId);
+            return;
+        }
+
+        if (!isBackupActive(userId)) {
             return;
         }
 
@@ -175,12 +185,7 @@
         showUsage();
     }
 
-    boolean isBmgrActive(@UserIdInt int userId) {
-        if (mBmgr == null) {
-            System.err.println(BMGR_NOT_RUNNING_ERR);
-            return false;
-        }
-
+    boolean isBackupActive(@UserIdInt int userId) {
         try {
             if (!mBmgr.isBackupServiceActive(userId)) {
                 System.err.println(BMGR_NOT_RUNNING_ERR);
@@ -845,6 +850,27 @@
         }
     }
 
+    private void doActivateService(int userId) {
+        String arg = nextArg();
+        if (arg == null) {
+            showUsage();
+            return;
+        }
+
+        try {
+            boolean activate = Boolean.parseBoolean(arg);
+            mBmgr.setBackupServiceActive(userId, activate);
+            System.out.println(
+                    "Backup service now "
+                            + (activate ? "activated" : "deactivated")
+                            + " for user "
+                            + userId);
+        } catch (RemoteException e) {
+            System.err.println(e.toString());
+            System.err.println(BMGR_NOT_RUNNING_ERR);
+        }
+    }
+
     private String nextArg() {
         if (mNextArg >= mArgs.length) {
             return null;
@@ -880,6 +906,7 @@
         System.err.println("       bmgr backupnow [--monitor|--monitor-verbose] --all|PACKAGE...");
         System.err.println("       bmgr cancel backups");
         System.err.println("       bmgr init TRANSPORT...");
+        System.err.println("       bmgr activate BOOL");
         System.err.println("");
         System.err.println("The '--user' option specifies the user on which the operation is run.");
         System.err.println("It must be the first argument before the operation.");
@@ -946,6 +973,11 @@
         System.err.println("");
         System.err.println("The 'init' command initializes the given transports, wiping all data");
         System.err.println("from their backing data stores.");
+        System.err.println("");
+        System.err.println("The 'activate' command activates or deactivates the backup service.");
+        System.err.println("If the argument is 'true' it will be activated, otherwise it will be");
+        System.err.println("deactivated. When deactivated, the service will not be running and no");
+        System.err.println("operations can be performed until activation.");
     }
 
     private static class BackupMonitor extends IBackupManagerMonitor.Stub {
diff --git a/cmds/idmap2/idmap2/Create.cpp b/cmds/idmap2/idmap2/Create.cpp
index c455ac0..0c581f3 100644
--- a/cmds/idmap2/idmap2/Create.cpp
+++ b/cmds/idmap2/idmap2/Create.cpp
@@ -39,6 +39,7 @@
 using android::idmap2::PolicyFlags;
 using android::idmap2::Result;
 using android::idmap2::utils::kIdmapFilePermissionMask;
+using android::idmap2::utils::UidHasWriteAccessToPath;
 
 bool Create(const std::vector<std::string>& args, std::ostream& out_error) {
   std::string target_apk_path;
@@ -66,6 +67,13 @@
     return false;
   }
 
+  const uid_t uid = getuid();
+  if (!UidHasWriteAccessToPath(uid, idmap_path)) {
+    out_error << "error: uid " << uid << " does not have write access to " << idmap_path
+              << std::endl;
+    return false;
+  }
+
   PolicyBitmask fulfilled_policies = 0;
   if (auto result = PoliciesToBitmask(policies, out_error)) {
     fulfilled_policies |= *result;
diff --git a/cmds/idmap2/idmap2d/Idmap2Service.cpp b/cmds/idmap2/idmap2d/Idmap2Service.cpp
index a3c7527..f30ce9b 100644
--- a/cmds/idmap2/idmap2d/Idmap2Service.cpp
+++ b/cmds/idmap2/idmap2d/Idmap2Service.cpp
@@ -27,6 +27,7 @@
 
 #include "android-base/macros.h"
 #include "android-base/stringprintf.h"
+#include "binder/IPCThreadState.h"
 #include "utils/String8.h"
 #include "utils/Trace.h"
 
@@ -38,18 +39,19 @@
 
 #include "idmap2d/Idmap2Service.h"
 
+using android::IPCThreadState;
 using android::binder::Status;
 using android::idmap2::BinaryStreamVisitor;
 using android::idmap2::Idmap;
 using android::idmap2::IdmapHeader;
 using android::idmap2::PolicyBitmask;
 using android::idmap2::Result;
+using android::idmap2::utils::kIdmapCacheDir;
 using android::idmap2::utils::kIdmapFilePermissionMask;
+using android::idmap2::utils::UidHasWriteAccessToPath;
 
 namespace {
 
-constexpr const char* kIdmapCacheDir = "/data/resource-cache";
-
 Status ok() {
   return Status::ok();
 }
@@ -77,7 +79,13 @@
 Status Idmap2Service::removeIdmap(const std::string& overlay_apk_path,
                                   int32_t user_id ATTRIBUTE_UNUSED, bool* _aidl_return) {
   assert(_aidl_return);
+  const uid_t uid = IPCThreadState::self()->getCallingUid();
   const std::string idmap_path = Idmap::CanonicalIdmapPathFor(kIdmapCacheDir, overlay_apk_path);
+  if (!UidHasWriteAccessToPath(uid, idmap_path)) {
+    *_aidl_return = false;
+    return error(base::StringPrintf("failed to unlink %s: calling uid %d lacks write access",
+                                    idmap_path.c_str(), uid));
+  }
   if (unlink(idmap_path.c_str()) != 0) {
     *_aidl_return = false;
     return error("failed to unlink " + idmap_path + ": " + strerror(errno));
@@ -118,6 +126,13 @@
 
   const PolicyBitmask policy_bitmask = ConvertAidlArgToPolicyBitmask(fulfilled_policies);
 
+  const std::string idmap_path = Idmap::CanonicalIdmapPathFor(kIdmapCacheDir, overlay_apk_path);
+  const uid_t uid = IPCThreadState::self()->getCallingUid();
+  if (!UidHasWriteAccessToPath(uid, idmap_path)) {
+    return error(base::StringPrintf("will not write to %s: calling uid %d lacks write accesss",
+                                    idmap_path.c_str(), uid));
+  }
+
   const std::unique_ptr<const ApkAssets> target_apk = ApkAssets::Load(target_apk_path);
   if (!target_apk) {
     return error("failed to load apk " + target_apk_path);
@@ -137,7 +152,6 @@
   }
 
   umask(kIdmapFilePermissionMask);
-  const std::string idmap_path = Idmap::CanonicalIdmapPathFor(kIdmapCacheDir, overlay_apk_path);
   std::ofstream fout(idmap_path);
   if (fout.fail()) {
     return error("failed to open idmap path " + idmap_path);
diff --git a/cmds/idmap2/include/idmap2/FileUtils.h b/cmds/idmap2/include/idmap2/FileUtils.h
index 5c41c49..3f03236 100644
--- a/cmds/idmap2/include/idmap2/FileUtils.h
+++ b/cmds/idmap2/include/idmap2/FileUtils.h
@@ -17,6 +17,8 @@
 #ifndef IDMAP2_INCLUDE_IDMAP2_FILEUTILS_H_
 #define IDMAP2_INCLUDE_IDMAP2_FILEUTILS_H_
 
+#include <sys/types.h>
+
 #include <functional>
 #include <memory>
 #include <string>
@@ -24,6 +26,7 @@
 
 namespace android::idmap2::utils {
 
+constexpr const char* kIdmapCacheDir = "/data/resource-cache";
 constexpr const mode_t kIdmapFilePermissionMask = 0133;  // u=rw,g=r,o=r
 
 typedef std::function<bool(unsigned char type /* DT_* from dirent.h */, const std::string& path)>
@@ -35,6 +38,8 @@
 
 std::unique_ptr<std::string> ReadFile(const std::string& path);
 
+bool UidHasWriteAccessToPath(uid_t uid, const std::string& path);
+
 }  // namespace android::idmap2::utils
 
 #endif  // IDMAP2_INCLUDE_IDMAP2_FILEUTILS_H_
diff --git a/cmds/idmap2/libidmap2/FileUtils.cpp b/cmds/idmap2/libidmap2/FileUtils.cpp
index 0255727..a9b68cd 100644
--- a/cmds/idmap2/libidmap2/FileUtils.cpp
+++ b/cmds/idmap2/libidmap2/FileUtils.cpp
@@ -19,12 +19,20 @@
 #include <unistd.h>
 
 #include <cerrno>
+#include <climits>
+#include <cstdlib>
+#include <cstring>
 #include <fstream>
 #include <memory>
 #include <string>
 #include <utility>
 #include <vector>
 
+#include "android-base/file.h"
+#include "android-base/macros.h"
+#include "android-base/stringprintf.h"
+#include "private/android_filesystem_config.h"
+
 #include "idmap2/FileUtils.h"
 
 namespace android::idmap2::utils {
@@ -77,4 +85,26 @@
   return r == 0 ? std::move(str) : nullptr;
 }
 
+#ifdef __ANDROID__
+bool UidHasWriteAccessToPath(uid_t uid, const std::string& path) {
+  // resolve symlinks and relative paths; the directories must exist
+  std::string canonical_path;
+  if (!base::Realpath(base::Dirname(path), &canonical_path)) {
+    return false;
+  }
+
+  const std::string cache_subdir = base::StringPrintf("%s/", kIdmapCacheDir);
+  if (canonical_path == kIdmapCacheDir ||
+      canonical_path.compare(0, cache_subdir.size(), cache_subdir) == 0) {
+    // limit access to /data/resource-cache to root and system
+    return uid == AID_ROOT || uid == AID_SYSTEM;
+  }
+  return true;
+}
+#else
+bool UidHasWriteAccessToPath(uid_t uid ATTRIBUTE_UNUSED, const std::string& path ATTRIBUTE_UNUSED) {
+  return true;
+}
+#endif
+
 }  // namespace android::idmap2::utils
diff --git a/cmds/idmap2/tests/FileUtilsTests.cpp b/cmds/idmap2/tests/FileUtilsTests.cpp
index d9d9a7f..45f84fe 100644
--- a/cmds/idmap2/tests/FileUtilsTests.cpp
+++ b/cmds/idmap2/tests/FileUtilsTests.cpp
@@ -22,6 +22,8 @@
 #include "gtest/gtest.h"
 
 #include "android-base/macros.h"
+#include "android-base/stringprintf.h"
+#include "private/android_filesystem_config.h"
 
 #include "idmap2/FileUtils.h"
 
@@ -71,4 +73,25 @@
   close(pipefd[0]);
 }
 
+#ifdef __ANDROID__
+TEST(FileUtilsTests, UidHasWriteAccessToPath) {
+  constexpr const char* tmp_path = "/data/local/tmp/test@idmap";
+  const std::string cache_path(base::StringPrintf("%s/test@idmap", kIdmapCacheDir));
+  const std::string sneaky_cache_path(base::StringPrintf("/data/../%s/test@idmap", kIdmapCacheDir));
+
+  ASSERT_TRUE(UidHasWriteAccessToPath(AID_ROOT, tmp_path));
+  ASSERT_TRUE(UidHasWriteAccessToPath(AID_ROOT, cache_path));
+  ASSERT_TRUE(UidHasWriteAccessToPath(AID_ROOT, sneaky_cache_path));
+
+  ASSERT_TRUE(UidHasWriteAccessToPath(AID_SYSTEM, tmp_path));
+  ASSERT_TRUE(UidHasWriteAccessToPath(AID_SYSTEM, cache_path));
+  ASSERT_TRUE(UidHasWriteAccessToPath(AID_SYSTEM, sneaky_cache_path));
+
+  constexpr const uid_t AID_SOME_APP = AID_SYSTEM + 1;
+  ASSERT_TRUE(UidHasWriteAccessToPath(AID_SOME_APP, tmp_path));
+  ASSERT_FALSE(UidHasWriteAccessToPath(AID_SOME_APP, cache_path));
+  ASSERT_FALSE(UidHasWriteAccessToPath(AID_SOME_APP, sneaky_cache_path));
+}
+#endif
+
 }  // namespace android::idmap2::utils
diff --git a/cmds/idmap2/tests/Idmap2BinaryTests.cpp b/cmds/idmap2/tests/Idmap2BinaryTests.cpp
index 4334fa6..c550eaf 100644
--- a/cmds/idmap2/tests/Idmap2BinaryTests.cpp
+++ b/cmds/idmap2/tests/Idmap2BinaryTests.cpp
@@ -38,6 +38,7 @@
 #include "gtest/gtest.h"
 
 #include "androidfw/PosixUtils.h"
+#include "private/android_filesystem_config.h"
 
 #include "idmap2/FileUtils.h"
 #include "idmap2/Idmap.h"
@@ -69,9 +70,23 @@
     ASSERT_NO_FATAL_FAILURE(AssertIdmap(idmap_ref, target_apk_path, overlay_apk_path)); \
   } while (0)
 
+#ifdef __ANDROID__
+#define SKIP_TEST_IF_CANT_EXEC_IDMAP2           \
+  do {                                          \
+    const uid_t uid = getuid();                 \
+    if (uid != AID_ROOT && uid != AID_SYSTEM) { \
+      GTEST_SKIP();                             \
+    }                                           \
+  } while (0)
+#else
+#define SKIP_TEST_IF_CANT_EXEC_IDMAP2
+#endif
+
 }  // namespace
 
 TEST_F(Idmap2BinaryTests, Create) {
+  SKIP_TEST_IF_CANT_EXEC_IDMAP2;
+
   // clang-format off
   auto result = ExecuteBinary({"idmap2",
                                "create",
@@ -97,6 +112,8 @@
 }
 
 TEST_F(Idmap2BinaryTests, Dump) {
+  SKIP_TEST_IF_CANT_EXEC_IDMAP2;
+
   // clang-format off
   auto result = ExecuteBinary({"idmap2",
                                "create",
@@ -144,6 +161,8 @@
 }
 
 TEST_F(Idmap2BinaryTests, Scan) {
+  SKIP_TEST_IF_CANT_EXEC_IDMAP2;
+
   const std::string overlay_static_1_apk_path = GetTestDataPath() + "/overlay/overlay-static-1.apk";
   const std::string overlay_static_2_apk_path = GetTestDataPath() + "/overlay/overlay-static-2.apk";
   const std::string idmap_static_1_path =
@@ -236,6 +255,8 @@
 }
 
 TEST_F(Idmap2BinaryTests, Lookup) {
+  SKIP_TEST_IF_CANT_EXEC_IDMAP2;
+
   // clang-format off
   auto result = ExecuteBinary({"idmap2",
                                "create",
@@ -285,6 +306,8 @@
 }
 
 TEST_F(Idmap2BinaryTests, InvalidCommandLineOptions) {
+  SKIP_TEST_IF_CANT_EXEC_IDMAP2;
+
   const std::string invalid_target_apk_path = GetTestDataPath() + "/DOES-NOT-EXIST";
 
   // missing mandatory options
diff --git a/cmds/statsd/Android.bp b/cmds/statsd/Android.bp
index 59b2aa6..ca10482 100644
--- a/cmds/statsd/Android.bp
+++ b/cmds/statsd/Android.bp
@@ -74,7 +74,6 @@
         "src/external/SubsystemSleepStatePuller.cpp",
         "src/external/PowerStatsPuller.cpp",
         "src/external/ResourceHealthManagerPuller.cpp",
-        "src/external/ResourceThermalManagerPuller.cpp",
         "src/external/StatsPullerManager.cpp",
         "src/external/puller_util.cpp",
         "src/logd/LogEvent.cpp",
@@ -136,7 +135,6 @@
         "android.hardware.power@1.0",
         "android.hardware.power@1.1",
         "android.hardware.power.stats@1.0",
-        "android.hardware.thermal@2.0",
         "libpackagelistparser",
         "libsysutils",
         "libcutils",
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 820da55..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);
 
@@ -1097,6 +1116,30 @@
     return hardware::Void();
 }
 
+hardware::Return<void> StatsService::reportSpeechDspStat(
+        const SpeechDspStat& speechDspStat) {
+    LogEvent event(getWallClockSec() * NS_PER_SEC, getElapsedRealtimeNs(), speechDspStat);
+    mProcessor->OnLogEvent(&event);
+
+    return hardware::Void();
+}
+
+hardware::Return<void> StatsService::reportVendorAtom(const VendorAtom& vendorAtom) {
+    std::string reverseDomainName = (std::string) vendorAtom.reverseDomainName;
+    if (vendorAtom.atomId < 100000 || vendorAtom.atomId >= 200000) {
+        ALOGE("Atom ID %ld is not a valid vendor atom ID", (long) vendorAtom.atomId);
+        return hardware::Void();
+    }
+    if (reverseDomainName.length() > 50) {
+        ALOGE("Vendor atom reverse domain name %s is too long.", reverseDomainName.c_str());
+        return hardware::Void();
+    }
+    LogEvent event(getWallClockSec() * NS_PER_SEC, getElapsedRealtimeNs(), vendorAtom);
+    mProcessor->OnLogEvent(&event);
+
+    return hardware::Void();
+}
+
 void StatsService::binderDied(const wp <IBinder>& who) {
     ALOGW("statscompanion service died");
     StatsdStats::getInstance().noteSystemServerRestart(getWallClockSec());
diff --git a/cmds/statsd/src/StatsService.h b/cmds/statsd/src/StatsService.h
index e9b3d4f..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,
@@ -205,6 +216,17 @@
     virtual Return<void> reportUsbPortOverheatEvent(
             const UsbPortOverheatEvent& usbPortOverheatEvent) override;
 
+    /**
+     * Binder call to get Speech DSP state atom.
+     */
+    virtual Return<void> reportSpeechDspStat(
+            const SpeechDspStat& speechDspStat) override;
+
+    /**
+     * Binder call to get vendor atom.
+     */
+    virtual Return<void> reportVendorAtom(const VendorAtom& vendorAtom) override;
+
     /** IBinder::DeathRecipient */
     virtual void binderDied(const wp<IBinder>& who) override;
 
diff --git a/cmds/statsd/src/atoms.proto b/cmds/statsd/src/atoms.proto
index 60b2e25..8e56bef 100644
--- a/cmds/statsd/src/atoms.proto
+++ b/cmds/statsd/src/atoms.proto
@@ -28,12 +28,14 @@
 import "frameworks/base/core/proto/android/bluetooth/hci/enums.proto";
 import "frameworks/base/core/proto/android/bluetooth/hfp/enums.proto";
 import "frameworks/base/core/proto/android/debug/enums.proto";
+import "frameworks/base/core/proto/android/hardware/biometrics/enums.proto";
 import "frameworks/base/core/proto/android/net/networkcapabilities.proto";
 import "frameworks/base/core/proto/android/os/enums.proto";
 import "frameworks/base/core/proto/android/server/connectivity/data_stall_event.proto";
 import "frameworks/base/core/proto/android/server/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";
 import "frameworks/base/core/proto/android/stats/enums.proto";
 import "frameworks/base/core/proto/android/stats/docsui/docsui_enums.proto";
 import "frameworks/base/core/proto/android/stats/launcher/launcher.proto";
@@ -145,9 +147,9 @@
         VibratorStateChanged vibrator_state_changed = 84;
         DeferredJobStatsReported deferred_job_stats_reported = 85;
         ThermalThrottlingStateChanged thermal_throttling = 86;
-        FingerprintAcquired fingerprint_acquired = 87;
-        FingerprintAuthenticated fingerprint_authenticated = 88;
-        FingerprintErrorOccurred fingerprint_error_occurred = 89;
+        BiometricAcquired biometric_acquired = 87;
+        BiometricAuthenticated biometric_authenticated = 88;
+        BiometricErrorOccurred biometric_error_occurred = 89;
         Notification notification = 90;
         BatteryHealthSnapshot battery_health_snapshot = 91;
         SlowIo slow_io = 92;
@@ -162,7 +164,7 @@
         // Consider removing this if it becomes a problem
         ServiceStateChanged service_state_changed = 99;
         ServiceLaunchReported service_launch_reported = 100;
-        PhenotypeFlagStateChanged phenotype_flag_state_changed = 101;
+        FlagFlipUpdateOccurred flag_flip_update_occurred = 101;
         BinaryPushStateChanged binary_push_state_changed = 102;
         DevicePolicyEvent device_policy_event = 103;
         DocsUIFileOperationCanceledReported docs_ui_file_op_canceled = 104;
@@ -206,10 +208,15 @@
         BroadcastDispatchLatencyReported broadcast_dispatch_latency_reported = 142;
         AttentionManagerServiceResultReported attention_manager_service_result_reported = 143;
         AdbConnectionChanged adb_connection_changed = 144;
+        SpeechDspStatReported speech_dsp_stat_reported = 145;
+        UsbContaminantReported usb_contaminant_reported = 146;
+        WatchdogRollbackOccurred watchdog_rollback_occurred = 147;
+        BiometricHalDeathReported biometric_hal_death_reported = 148;
+        BubbleUIChanged bubble_ui_changed = 149;
     }
 
     // Pulled events will start at field 10000.
-    // Next: 10043
+    // Next: 10048
     oneof pulled {
         WifiBytesTransfer wifi_bytes_transfer = 10000;
         WifiBytesTransferByFgBg wifi_bytes_transfer_by_fg_bg = 10001;
@@ -242,7 +249,7 @@
         CategorySize category_size = 10028;
         ProcStats proc_stats = 10029;
         BatteryVoltage battery_voltage = 10030;
-        NumFingerprints num_fingerprints = 10031;
+        NumBiometricsEnrolled num_fingerprints_enrolled = 10031;
         DiskIo disk_io = 10032;
         PowerProfile power_profile = 10033;
         ProcStatsPkgProc proc_stats_pkg_proc = 10034;
@@ -257,6 +264,9 @@
         BatteryLevel battery_level = 10043;
         BuildInformation build_information = 10044;
         BatteryCycleCount battery_cycle_count = 10045;
+        DebugElapsedClock debug_elapsed_clock = 10046;
+        DebugFailingElapsedClock debug_failing_elapsed_clock = 10047;
+        NumBiometricsEnrolled num_faces_enrolled = 10048;
     }
 
     // DO NOT USE field numbers above 100,000 in AOSP.
@@ -1480,6 +1490,25 @@
     optional android.bluetooth.hci.StatusEnum reason_code = 9;
 }
 
+/**
+ * Logs when a module is rolled back by Watchdog.
+ *
+ * Logged from: Rollback Manager
+ */
+message WatchdogRollbackOccurred {
+    enum RollbackType {
+        UNKNOWN = 0;
+        ROLLBACK_INITIATE = 1;
+        ROLLBACK_SUCCESS = 2;
+        ROLLBACK_FAILURE = 3;
+    }
+    optional RollbackType rollback_type = 1;
+
+    optional string package_name = 2;
+
+    optional int32 package_version_code = 3;
+}
+
 
 /**
  * Logs when something is plugged into or removed from the USB-C connector.
@@ -2348,58 +2377,95 @@
 }
 
 /**
- * Logs when a fingerprint acquire event occurs.
+ * Logs when a biometric acquire event occurs.
  *
  * Logged from:
- *   frameworks/base/services/core/java/com/android/server/biometrics/fingerprint/FingerprintService.java
+ *   frameworks/base/services/core/java/com/android/server/biometrics
  */
-message FingerprintAcquired {
-    // The associated user. Eg: 0 for owners, 10+ for others.
-    // Defined in android/os/UserHandle.java
-    optional int32 user = 1;
-    // If this acquire is for a crypto fingerprint.
-    // e.g. Secure purchases, unlock password storage.
-    optional bool is_crypto = 2;
+message BiometricAcquired {
+    // Biometric modality that was acquired.
+    optional android.hardware.biometrics.ModalityEnum modality = 1;
+    // The associated user. Eg: 0 for owners, 10+ for others. Defined in android/os/UserHandle.java.
+    optional int32 user = 2;
+    // If this acquire is for a crypto operation. e.g. Secure purchases, unlock password storage.
+    optional bool is_crypto = 3;
+    // Action that the device is performing. Acquired messages are only expected for enroll and
+    // authenticate. Other actions may indicate an error.
+    optional android.hardware.biometrics.ActionEnum action = 4;
+    // The client that this acquisition was received for.
+    optional android.hardware.biometrics.ClientEnum client = 5;
+    // Acquired constants, e.g. ACQUIRED_GOOD. See constants defined by <Biometric>Manager.
+    optional int32 acquire_info = 6;
+    // Vendor-specific acquire info. Valid only if acquire_info == ACQUIRED_VENDOR.
+    optional int32 acquire_info_vendor = 7;
 }
 
 /**
- * Logs when a fingerprint authentication event occurs.
+ * Logs when a biometric authentication event occurs.
  *
  * Logged from:
- *   frameworks/base/services/core/java/com/android/server/biometrics/fingerprint/FingerprintService.java
+ *   frameworks/base/services/core/java/com/android/server/biometrics
  */
-message FingerprintAuthenticated {
-    // The associated user. Eg: 0 for owners, 10+ for others.
-    // Defined in android/os/UserHandle.java
-    optional int32 user = 1;
-    // If this authentication is for a crypto fingerprint.
-    // e.g. Secure purchases, unlock password storage.
-    optional bool is_crypto = 2;
-    // Whether or not this authentication was successful.
-    optional bool is_authenticated = 3;
-}
+message BiometricAuthenticated {
+    // Biometric modality that was used.
+    optional android.hardware.biometrics.ModalityEnum modality = 1;
+    // The associated user. Eg: 0 for owners, 10+ for others. Defined in android/os/UserHandle.java
+    optional int32 user = 2;
+    // If this authentication is for a crypto operation. e.g. Secure purchases, unlock password
+    // storage.
+    optional bool is_crypto = 3;
+    // The client that this acquisition was received for.
+    optional android.hardware.biometrics.ClientEnum client = 4;
 
-/**
- * Logs when a fingerprint error occurs.
- *
- * Logged from:
- *   frameworks/base/services/core/java/com/android/server/biometrics/fingerprint/FingerprintService.java
- */
-message FingerprintErrorOccurred {
-    // The associated user. Eg: 0 for owners, 10+ for others.
-    // Defined in android/os/UserHandle.java
-    optional int32 user = 1;
-    // If this error is for a crypto fingerprint.
-    // e.g. Secure purchases, unlock password storage.
-    optional bool is_crypto = 2;
-
-    enum Error {
+    enum State {
         UNKNOWN = 0;
-        LOCKOUT = 1;
-        PERMANENT_LOCKOUT = 2;
+        REJECTED = 1;
+        PENDING_CONFIRMATION = 2;
+        CONFIRMED = 3;
     }
-    // The type of error.
-    optional Error error = 3;
+
+    // State of the current auth attempt.
+    optional State state = 5;
+    // Time it took to authenticate. For BiometricPrompt where setRequireConfirmation(false) is
+    // specified and supported by the biometric modality, this is from the first ACQUIRED_GOOD to
+    // AUTHENTICATED. for setRequireConfirmation(true), this is from PENDING_CONFIRMATION to
+    // CONFIRMED.
+    optional int64 latency_millis = 6;
+}
+
+/**
+ * Logs when a biometric error occurs.
+ *
+ * Logged from:
+ *   frameworks/base/services/core/java/com/android/server/biometrics
+ */
+message BiometricErrorOccurred {
+    // Biometric modality that was used.
+    optional android.hardware.biometrics.ModalityEnum modality = 1;
+    // The associated user. Eg: 0 for owners, 10+ for others. Defined in android/os/UserHandle.java
+    optional int32 user = 2;
+    // If this error is for a crypto operation. e.g. Secure purchases, unlock password storage.
+    optional bool is_crypto = 3;
+    // Action that the device is performing.
+    optional android.hardware.biometrics.ActionEnum action = 4;
+    // The client that this acquisition was received for.
+    optional android.hardware.biometrics.ClientEnum client = 5;
+    // Error constants. See constants defined by <Biometric>Manager. Enums won't work since errors
+    // are unique to modality.
+    optional int32 error_info = 6;
+    // Vendor-specific error info. Valid only if acquire_info == ACQUIRED_VENDOR. These are defined
+    // by the vendor and not specified by the HIDL interface.
+    optional int32 error_info_vendor = 7;
+}
+
+/**
+ * Logs when a biometric HAL has crashed.
+ * Logged from:
+ *   frameworks/base/services/core/java/com/android/server/biometrics
+ */
+message BiometricHalDeathReported {
+    // Biometric modality.
+    optional android.hardware.biometrics.ModalityEnum modality = 1;
 }
 
 message Notification {
@@ -2473,40 +2539,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. */
@@ -3018,8 +3088,8 @@
 
 /**
  * Pulls battery coulomb counter, which is the remaining battery charge in uAh.
- * Pulled from:
- *   frameworks/base/cmds/statsd/src/external/ResourceHealthManagerPuller.cpp
+ *
+ * Pulled from StatsCompanionService.java
  */
 message RemainingBatteryCapacity {
     optional int32 charge_micro_ampere_hour = 1;
@@ -3338,14 +3408,14 @@
 /**
  * Pulls the number of fingerprints for each user.
  *
- * Pulled from StatsCompanionService, which queries FingerprintManager.
+ * Pulled from StatsCompanionService, which queries <Biometric>Manager.
  */
-message NumFingerprints {
+message NumBiometricsEnrolled {
     // The associated user. Eg: 0 for owners, 10+ for others.
     // Defined in android/os/UserHandle.java
     optional int32 user = 1;
     // Number of fingerprints registered to that user.
-    optional int32 num_fingerprints = 2;
+    optional int32 num_enrolled = 2;
 }
 
 message AggStats {
@@ -4521,3 +4591,111 @@
     // True if the 'always allow' option was selected for this system.
     optional bool always_allow = 4;
 }
+
+/*
+ * Logs the reported speech DSP status.
+ *
+ * Logged from:
+ *  Vendor audio implementation.
+ */
+message SpeechDspStatReported {
+    // The total Speech DSP uptime in milliseconds.
+    optional int32 total_uptime_millis = 1;
+    // The total Speech DSP downtime in milliseconds.
+    optional int32 total_downtime_millis = 2;
+    optional int32 total_crash_count = 3;
+    optional int32 total_recover_count = 4;
+}
+
+/**
+ * Logs USB connector contaminant status.
+ *
+ * Logged from: USB Service.
+ */
+message UsbContaminantReported {
+    optional string id = 1;
+    optional android.service.usb.ContaminantPresenceStatus status = 2;
+}
+
+/**
+ * This atom is for debugging purpose.
+ */
+message DebugElapsedClock {
+    // Monotically increasing value for each pull.
+    optional int64 pull_count = 1;
+    // Time from System.elapsedRealtime.
+    optional int64 elapsed_clock_millis = 2;
+    // Time from System.elapsedRealtime.
+    optional int64 same_elapsed_clock_millis = 3;
+    // Diff between current elapsed time and elapsed time from previous pull.
+    optional int64 elapsed_clock_diff_millis = 4;
+
+    enum Type {
+      TYPE_UNKNOWN = 0;
+      ALWAYS_PRESENT = 1;
+      PRESENT_ON_ODD_PULLS = 2;
+    }
+    // Type of behavior for the pulled data.
+    optional Type type = 5;
+}
+
+/**
+ * This atom is for debugging purpose.
+ */
+message DebugFailingElapsedClock {
+    // Monotically increasing value for each pull.
+    optional int64 pull_count = 1;
+    // Time from System.elapsedRealtime.
+    optional int64 elapsed_clock_millis = 2;
+    // Time from System.elapsedRealtime.
+    optional int64 same_elapsed_clock_millis = 3;
+    // Diff between current elapsed time and elapsed time from previous pull.
+    optional int64 elapsed_clock_diff_millis = 4;
+}
+
+/** Logs System UI bubbles event changed.
+ *
+ * Logged from:
+ *     frameworks/base/packages/SystemUI/src/com/android/systemui/bubbles
+ */
+message BubbleUIChanged {
+
+    // The app package that is posting the bubble.
+    optional string package_name = 1;
+
+    // The notification channel that is posting the bubble.
+    optional string notification_channel = 2;
+
+    // The notification id associated with the posted bubble.
+    optional int32 notification_id = 3;
+
+    // The position of the bubble within the bubble stack.
+    optional int32 position = 4;
+
+    // The total number of bubbles within the bubble stack.
+    optional int32 total_number = 5;
+
+    // User interactions with the bubble.
+    enum Action {
+        UNKNOWN = 0;
+        POSTED = 1;
+        UPDATED = 2;
+        EXPANDED = 3;
+        COLLAPSED = 4;
+        DISMISSED = 5;
+        STACK_DISMISSED = 6;
+        STACK_MOVED = 7;
+        HEADER_GO_TO_APP = 8;
+        HEADER_GO_TO_SETTINGS = 9;
+        PERMISSION_OPT_IN = 10;
+        PERMISSION_OPT_OUT = 11;
+        PERMISSION_IGNORED = 12;
+        SWIPE_LEFT = 13;
+        SWIPE_RIGHT = 14;
+    }
+    optional Action action = 6;
+
+    // Normalized screen position of the bubble stack. The range is between 0 and 1.
+    optional float normalized_x_position = 7;
+    optional float normalized_y_position = 8;
+}
diff --git a/cmds/statsd/src/config/ConfigManager.cpp b/cmds/statsd/src/config/ConfigManager.cpp
index 5fea90b..aa22333 100644
--- a/cmds/statsd/src/config/ConfigManager.cpp
+++ b/cmds/statsd/src/config/ConfigManager.cpp
@@ -128,6 +128,17 @@
     mConfigReceivers.erase(key);
 }
 
+void ConfigManager::SetActiveConfigsChangedReceiver(const int uid,
+                                                    const sp<IBinder>& intentSender) {
+    lock_guard<mutex> lock(mMutex);
+    mActiveConfigsChangedReceivers[uid] = intentSender;
+}
+
+void ConfigManager::RemoveActiveConfigsChangedReceiver(const int uid) {
+    lock_guard<mutex> lock(mMutex);
+    mActiveConfigsChangedReceivers.erase(uid);
+}
+
 void ConfigManager::RemoveConfig(const ConfigKey& key) {
     vector<sp<ConfigListener>> broadcastList;
     {
@@ -181,6 +192,11 @@
                 mConfigReceivers.erase(*it);
         }
 
+        auto itActiveConfigsChangedReceiver = mActiveConfigsChangedReceivers.find(uid);
+        if (itActiveConfigsChangedReceiver != mActiveConfigsChangedReceivers.end()) {
+            mActiveConfigsChangedReceivers.erase(itActiveConfigsChangedReceiver);
+        }
+
         mConfigs.erase(uidIt);
 
         for (const sp<ConfigListener>& listener : mListeners) {
@@ -213,6 +229,7 @@
         }
 
         mConfigReceivers.clear();
+        mActiveConfigsChangedReceivers.clear();
         for (const sp<ConfigListener>& listener : mListeners) {
             broadcastList.push_back(listener);
         }
@@ -250,6 +267,17 @@
     }
 }
 
+const sp<android::IBinder> ConfigManager::GetActiveConfigsChangedReceiver(const int uid) const {
+    lock_guard<mutex> lock(mMutex);
+
+    auto it = mActiveConfigsChangedReceivers.find(uid);
+    if (it == mActiveConfigsChangedReceivers.end()) {
+        return nullptr;
+    } else {
+        return it->second;
+    }
+}
+
 void ConfigManager::Dump(FILE* out) {
     lock_guard<mutex> lock(mMutex);
 
diff --git a/cmds/statsd/src/config/ConfigManager.h b/cmds/statsd/src/config/ConfigManager.h
index 122e669..c064a51 100644
--- a/cmds/statsd/src/config/ConfigManager.h
+++ b/cmds/statsd/src/config/ConfigManager.h
@@ -82,6 +82,23 @@
     void RemoveConfigReceiver(const ConfigKey& key);
 
     /**
+     * Sets the broadcast receiver that is notified whenever the list of active configs
+     * changes for this uid.
+     */
+    void SetActiveConfigsChangedReceiver(const int uid, const sp<IBinder>& intentSender);
+
+    /**
+     * Returns the broadcast receiver for active configs changed for this uid.
+     */
+
+    const sp<IBinder> GetActiveConfigsChangedReceiver(const int uid) const;
+
+    /**
+     * Erase any active configs changed broadcast receiver associated with this uid.
+     */
+    void RemoveActiveConfigsChangedReceiver(const int uid);
+
+    /**
      * A configuration was removed.
      *
      * Reports this to listeners.
@@ -130,6 +147,12 @@
     std::map<ConfigKey, sp<android::IBinder>> mConfigReceivers;
 
     /**
+     * Each uid can be subscribed by up to one receiver to notify that the list of active configs
+     * for this uid has changed. The receiver is specified as IBinder from PendingIntent.
+     */
+     std::map<int, sp<android::IBinder>> mActiveConfigsChangedReceivers;
+
+    /**
      * The ConfigListeners that will be told about changes.
      */
     std::vector<sp<ConfigListener>> mListeners;
diff --git a/cmds/statsd/src/external/ResourceThermalManagerPuller.cpp b/cmds/statsd/src/external/ResourceThermalManagerPuller.cpp
deleted file mode 100644
index 53709f1..0000000
--- a/cmds/statsd/src/external/ResourceThermalManagerPuller.cpp
+++ /dev/null
@@ -1,147 +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.
- */
-
-#define DEBUG false  // STOPSHIP if true
-#include "Log.h"
-
-#include <android/hardware/thermal/2.0/IThermal.h>
-#include "external/ResourceThermalManagerPuller.h"
-#include "external/StatsPuller.h"
-
-#include "ResourceThermalManagerPuller.h"
-#include "logd/LogEvent.h"
-#include "statslog.h"
-#include "stats_log_util.h"
-
-#include <chrono>
-
-using android::hardware::hidl_death_recipient;
-using android::hardware::hidl_vec;
-using android::hidl::base::V1_0::IBase;
-using ::android::hardware::thermal::V2_0::IThermal;
-using ::android::hardware::thermal::V2_0::Temperature;
-using ::android::hardware::thermal::V2_0::TemperatureType;
-using ::android::hardware::thermal::V1_0::ThermalStatus;
-using ::android::hardware::thermal::V1_0::ThermalStatusCode;
-using android::hardware::Return;
-using android::hardware::Void;
-
-using std::chrono::duration_cast;
-using std::chrono::nanoseconds;
-using std::chrono::system_clock;
-using std::make_shared;
-using std::shared_ptr;
-
-namespace android {
-namespace os {
-namespace statsd {
-
-bool getThermalHalLocked();
-sp<android::hardware::thermal::V2_0::IThermal> gThermalHal = nullptr;
-std::mutex gThermalHalMutex;
-
-struct ThermalHalDeathRecipient : virtual public hidl_death_recipient {
-      virtual void serviceDied(uint64_t cookie, const wp<IBase>& who) override {
-          std::lock_guard<std::mutex> lock(gThermalHalMutex);
-          ALOGE("ThermalHAL just died");
-          gThermalHal = nullptr;
-          getThermalHalLocked();
-      }
-};
-
-sp<ThermalHalDeathRecipient> gThermalHalDeathRecipient = nullptr;
-
-// The caller must be holding gThermalHalMutex.
-bool getThermalHalLocked() {
-    if (gThermalHal == nullptr) {
-            gThermalHal = IThermal::getService();
-            if (gThermalHal == nullptr) {
-                ALOGE("Unable to get Thermal service.");
-            } else {
-                if (gThermalHalDeathRecipient == nullptr) {
-                    gThermalHalDeathRecipient = new ThermalHalDeathRecipient();
-                }
-                hardware::Return<bool> linked = gThermalHal->linkToDeath(
-                    gThermalHalDeathRecipient, 0x451F /* cookie */);
-                if (!linked.isOk()) {
-                    ALOGE("Transaction error in linking to ThermalHAL death: %s",
-                            linked.description().c_str());
-                    gThermalHal = nullptr;
-                } else if (!linked) {
-                    ALOGW("Unable to link to ThermalHal death notifications");
-                    gThermalHal = nullptr;
-                } else {
-                    ALOGD("Link to death notification successful");
-                }
-            }
-    }
-    return gThermalHal != nullptr;
-}
-
-ResourceThermalManagerPuller::ResourceThermalManagerPuller() :
-        StatsPuller(android::util::TEMPERATURE) {
-}
-
-bool ResourceThermalManagerPuller::PullInternal(vector<shared_ptr<LogEvent>>* data) {
-    std::lock_guard<std::mutex> lock(gThermalHalMutex);
-    if (!getThermalHalLocked()) {
-        ALOGE("Thermal Hal not loaded");
-        return false;
-    }
-
-    int64_t wallClockTimestampNs = getWallClockNs();
-    int64_t elapsedTimestampNs = getElapsedRealtimeNs();
-
-    data->clear();
-    bool resultSuccess = true;
-
-    Return<void> ret = gThermalHal->getCurrentTemperatures(false, TemperatureType::SKIN,
-            [&](ThermalStatus status, const hidl_vec<Temperature>& temps) {
-        if (status.code != ThermalStatusCode::SUCCESS) {
-            ALOGE("Failed to get temperatures from ThermalHAL. Status: %d", status.code);
-            resultSuccess = false;
-            return;
-        }
-        if (mTagId == android::util::TEMPERATURE) {
-            for (size_t i = 0; i < temps.size(); ++i) {
-                auto ptr = make_shared<LogEvent>(android::util::TEMPERATURE,
-                        wallClockTimestampNs, elapsedTimestampNs);
-                ptr->write((static_cast<int>(temps[i].type)));
-                ptr->write(temps[i].name);
-                // Convert the temperature to an int.
-                int32_t temp = static_cast<int>(temps[i].value * 10);
-                ptr->write(temp);
-                ptr->init();
-                data->push_back(ptr);
-            }
-        } else {
-            ALOGE("Unsupported tag in ResourceThermalManagerPuller: %d", mTagId);
-        }
-    });
-    if (!ret.isOk()) {
-        ALOGE("getThermalHalLocked() failed: thermal HAL service not available. Description: %s",
-                ret.description().c_str());
-        if (ret.isDeadObject()) {
-            gThermalHal = nullptr;
-        }
-        return false;
-    }
-    return resultSuccess;
-}
-
-}  // namespace statsd
-}  // namespace os
-}  // namespace android
diff --git a/cmds/statsd/src/external/ResourceThermalManagerPuller.h b/cmds/statsd/src/external/ResourceThermalManagerPuller.h
deleted file mode 100644
index 5313792..0000000
--- a/cmds/statsd/src/external/ResourceThermalManagerPuller.h
+++ /dev/null
@@ -1,39 +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.
- */
-
-#pragma once
-
-#include <utils/String16.h>
-#include "StatsPuller.h"
-
-namespace android {
-namespace os {
-namespace statsd {
-
-/**
- * Reads IThermal.hal
- */
-class ResourceThermalManagerPuller : public StatsPuller {
-public:
-    ResourceThermalManagerPuller();
-
-private:
-    bool PullInternal(vector<std::shared_ptr<LogEvent>>* data) override;
-};
-
-}  // namespace statsd
-}  // namespace os
-}  // namespace android
\ No newline at end of file
diff --git a/cmds/statsd/src/external/StatsPullerManager.cpp b/cmds/statsd/src/external/StatsPullerManager.cpp
index 19a7389..ba7bcc4 100644
--- a/cmds/statsd/src/external/StatsPullerManager.cpp
+++ b/cmds/statsd/src/external/StatsPullerManager.cpp
@@ -28,7 +28,6 @@
 #include "../statscompanion_util.h"
 #include "PowerStatsPuller.h"
 #include "ResourceHealthManagerPuller.h"
-#include "ResourceThermalManagerPuller.h"
 #include "StatsCompanionServicePuller.h"
 #include "StatsPullerManager.h"
 #include "SubsystemSleepStatePuller.h"
@@ -150,7 +149,8 @@
           .puller =
                   new StatsCompanionServicePuller(android::util::PROCESS_MEMORY_HIGH_WATER_MARK)}},
         // temperature
-        {android::util::TEMPERATURE, {.puller = new ResourceThermalManagerPuller()}},
+        {android::util::TEMPERATURE,
+          {.puller = new StatsCompanionServicePuller(android::util::TEMPERATURE)}},
         // binder_calls
         {android::util::BINDER_CALLS,
          {.additiveFields = {4, 5, 6, 8, 12},
@@ -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/logd/LogEvent.cpp b/cmds/statsd/src/logd/LogEvent.cpp
index 78a75c5..eaba9be 100644
--- a/cmds/statsd/src/logd/LogEvent.cpp
+++ b/cmds/statsd/src/logd/LogEvent.cpp
@@ -267,6 +267,22 @@
 }
 
 LogEvent::LogEvent(int64_t wallClockTimestampNs, int64_t elapsedTimestampNs,
+                   const SpeechDspStat& speechDspStat) {
+    mLogdTimestampNs = wallClockTimestampNs;
+    mElapsedTimestampNs = elapsedTimestampNs;
+    mTagId = android::util::SPEECH_DSP_STAT_REPORTED;
+
+    mValues.push_back(FieldValue(Field(mTagId, getSimpleField(1)),
+                                 Value(speechDspStat.totalUptimeMillis)));
+    mValues.push_back(FieldValue(Field(mTagId, getSimpleField(2)),
+                                 Value(speechDspStat.totalDowntimeMillis)));
+    mValues.push_back(FieldValue(Field(mTagId, getSimpleField(3)),
+                                 Value(speechDspStat.totalCrashCount)));
+    mValues.push_back(FieldValue(Field(mTagId, getSimpleField(4)),
+                                 Value(speechDspStat.totalRecoverCount)));
+}
+
+LogEvent::LogEvent(int64_t wallClockTimestampNs, int64_t elapsedTimestampNs,
                    const BatteryCausedShutdown& batteryCausedShutdown) {
     mLogdTimestampNs = wallClockTimestampNs;
     mElapsedTimestampNs = elapsedTimestampNs;
@@ -294,6 +310,36 @@
                                  Value(usbPortOverheatEvent.timeToInactive)));
 }
 
+LogEvent::LogEvent(int64_t wallClockTimestampNs, int64_t elapsedTimestampNs,
+                   const VendorAtom& vendorAtom) {
+    mLogdTimestampNs = wallClockTimestampNs;
+    mElapsedTimestampNs = elapsedTimestampNs;
+    mTagId = vendorAtom.atomId;
+
+    mValues.push_back(
+            FieldValue(Field(mTagId, getSimpleField(1)), Value(vendorAtom.reverseDomainName)));
+    for (int i = 0; i < (int)vendorAtom.values.size(); i++) {
+        switch (vendorAtom.values[i].getDiscriminator()) {
+            case VendorAtom::Value::hidl_discriminator::intValue:
+                mValues.push_back(FieldValue(Field(mTagId, getSimpleField(i + 2)),
+                                             Value(vendorAtom.values[i].intValue())));
+                break;
+            case VendorAtom::Value::hidl_discriminator::longValue:
+                mValues.push_back(FieldValue(Field(mTagId, getSimpleField(i + 2)),
+                                             Value(vendorAtom.values[i].longValue())));
+                break;
+            case VendorAtom::Value::hidl_discriminator::floatValue:
+                mValues.push_back(FieldValue(Field(mTagId, getSimpleField(i + 2)),
+                                             Value(vendorAtom.values[i].floatValue())));
+                break;
+            case VendorAtom::Value::hidl_discriminator::stringValue:
+                mValues.push_back(FieldValue(Field(mTagId, getSimpleField(i + 2)),
+                                             Value(vendorAtom.values[i].stringValue())));
+                break;
+        }
+    }
+}
+
 LogEvent::LogEvent(int32_t tagId, int64_t timestampNs) : LogEvent(tagId, timestampNs, 0) {}
 
 LogEvent::LogEvent(int32_t tagId, int64_t timestampNs, int32_t uid) {
diff --git a/cmds/statsd/src/logd/LogEvent.h b/cmds/statsd/src/logd/LogEvent.h
index 3f47b7e..784376a 100644
--- a/cmds/statsd/src/logd/LogEvent.h
+++ b/cmds/statsd/src/logd/LogEvent.h
@@ -121,6 +121,12 @@
     explicit LogEvent(int64_t wallClockTimestampNs, int64_t elapsedTimestampNs,
                       const UsbPortOverheatEvent& usbPortOverheatEvent);
 
+    explicit LogEvent(int64_t wallClockTimestampNs, int64_t elapsedTimestampNs,
+                      const SpeechDspStat& speechDspStat);
+
+    explicit LogEvent(int64_t wallClockTimestampNs, int64_t elapsedTimestampNs,
+                      const VendorAtom& vendorAtom);
+
     ~LogEvent();
 
     /**
diff --git a/cmds/statsd/src/packages/UidMap.cpp b/cmds/statsd/src/packages/UidMap.cpp
index e9c43cd..5cf012638 100644
--- a/cmds/statsd/src/packages/UidMap.cpp
+++ b/cmds/statsd/src/packages/UidMap.cpp
@@ -548,6 +548,7 @@
                                                              {"AID_LMKD", 1069},
                                                              {"AID_LLKD", 1070},
                                                              {"AID_IORAPD", 1071},
+                                                             {"AID_NETWORK_STACK", 1073},
                                                              {"AID_SHELL", 2000},
                                                              {"AID_CACHE", 2001},
                                                              {"AID_DIAG", 2002}};
diff --git a/cmds/statsd/tools/localtools/src/com/android/statsd/shelltools/testdrive/TestDrive.java b/cmds/statsd/tools/localtools/src/com/android/statsd/shelltools/testdrive/TestDrive.java
index de818a8..d9aba61 100644
--- a/cmds/statsd/tools/localtools/src/com/android/statsd/shelltools/testdrive/TestDrive.java
+++ b/cmds/statsd/tools/localtools/src/com/android/statsd/shelltools/testdrive/TestDrive.java
@@ -55,7 +55,8 @@
         "AID_ROOT",
         "AID_BLUETOOTH",
         "AID_LMKD",
-        "com.android.managedprovisioning"
+        "com.android.managedprovisioning",
+        "AID_NETWORK_STACK"
     };
     private static final Logger LOGGER = Logger.getLogger(TestDrive.class.getName());
 
diff --git a/config/hiddenapi-greylist.txt b/config/hiddenapi-greylist.txt
index fc47f67..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
@@ -3518,7 +3517,7 @@
 Lcom/android/internal/telephony/SubscriptionController;->mLock:Ljava/lang/Object;
 Lcom/android/internal/telephony/SubscriptionController;->notifySubscriptionInfoChanged()V
 Lcom/android/internal/telephony/SubscriptionController;->setDefaultDataSubId(I)V
-Lcom/android/internal/telephony/SubscriptionController;->setDefaultFallbackSubId(I)V
+Lcom/android/internal/telephony/SubscriptionController;->setDefaultFallbackSubId(II)V
 Lcom/android/internal/telephony/SubscriptionController;->setDefaultSmsSubId(I)V
 Lcom/android/internal/telephony/SubscriptionController;->setDefaultVoiceSubId(I)V
 Lcom/android/internal/telephony/SubscriptionController;->setPlmnSpn(IZLjava/lang/String;ZLjava/lang/String;)Z
diff --git a/core/java/android/app/ActivityThread.java b/core/java/android/app/ActivityThread.java
index a3243a5..ee3d27c 100644
--- a/core/java/android/app/ActivityThread.java
+++ b/core/java/android/app/ActivityThread.java
@@ -77,9 +77,7 @@
 import android.hardware.display.DisplayManagerGlobal;
 import android.net.ConnectivityManager;
 import android.net.IConnectivityManager;
-import android.net.Network;
 import android.net.Proxy;
-import android.net.ProxyInfo;
 import android.net.Uri;
 import android.os.AsyncTask;
 import android.os.Binder;
@@ -1033,15 +1031,10 @@
             NetworkEventDispatcher.getInstance().onNetworkConfigurationChanged();
         }
 
-        public void setHttpProxy(String host, String port, String exclList, Uri pacFileUrl) {
+        public void updateHttpProxy() {
             final ConnectivityManager cm = ConnectivityManager.from(
                     getApplication() != null ? getApplication() : getSystemContext());
-            final Network network = cm.getBoundNetworkForProcess();
-            if (network != null) {
-                Proxy.setHttpProxySystemProperty(cm.getDefaultProxy());
-            } else {
-                Proxy.setHttpProxySystemProperty(host, port, exclList, pacFileUrl);
-            }
+            Proxy.setHttpProxySystemProperty(cm.getDefaultProxy());
         }
 
         public void processInBackground() {
@@ -5960,8 +5953,7 @@
             // crash if we can't get it.
             final IConnectivityManager service = IConnectivityManager.Stub.asInterface(b);
             try {
-                final ProxyInfo proxyInfo = service.getProxyForNetwork(null);
-                Proxy.setHttpProxySystemProperty(proxyInfo);
+                Proxy.setHttpProxySystemProperty(service.getProxyForNetwork(null));
             } catch (RemoteException e) {
                 Trace.traceEnd(Trace.TRACE_TAG_ACTIVITY_MANAGER);
                 throw e.rethrowFromSystemServer();
diff --git a/core/java/android/app/ActivityView.java b/core/java/android/app/ActivityView.java
index 4d3711a..4739867 100644
--- a/core/java/android/app/ActivityView.java
+++ b/core/java/android/app/ActivityView.java
@@ -23,6 +23,7 @@
 import android.annotation.NonNull;
 import android.annotation.UnsupportedAppUsage;
 import android.app.ActivityManager.StackInfo;
+import android.content.ComponentName;
 import android.content.Context;
 import android.content.Intent;
 import android.hardware.display.DisplayManager;
@@ -119,6 +120,7 @@
 
     /** Callback that notifies when the container is ready or destroyed. */
     public abstract static class StateCallback {
+
         /**
          * Called when the container is ready for launching activities. Calling
          * {@link #startActivity(Intent)} prior to this callback will result in an
@@ -127,6 +129,7 @@
          * @see #startActivity(Intent)
          */
         public abstract void onActivityViewReady(ActivityView view);
+
         /**
          * Called when the container can no longer launch activities. Calling
          * {@link #startActivity(Intent)} after this callback will result in an
@@ -135,11 +138,24 @@
          * @see #startActivity(Intent)
          */
         public abstract void onActivityViewDestroyed(ActivityView view);
+
+        /**
+         * Called when a task is created inside the container.
+         * This is a filtered version of {@link TaskStackListener}
+         */
+        public void onTaskCreated(int taskId, ComponentName componentName) { }
+
         /**
          * Called when a task is moved to the front of the stack inside the container.
          * This is a filtered version of {@link TaskStackListener}
          */
         public void onTaskMovedToFront(ActivityManager.StackInfo stackInfo) { }
+
+        /**
+         * Called when a task is about to be removed from the stack inside the container.
+         * This is a filtered version of {@link TaskStackListener}
+         */
+        public void onTaskRemovalStarted(int taskId) { }
     }
 
     /**
@@ -508,14 +524,45 @@
 
         @Override
         public void onTaskMovedToFront(int taskId) throws RemoteException {
-            if (mActivityViewCallback  != null) {
-                StackInfo stackInfo = getTopMostStackInfo();
-                // if StackInfo was null or unrelated to the "move to front" then there's no use
-                // notifying the callback
-                if (stackInfo != null
-                        && taskId == stackInfo.taskIds[stackInfo.taskIds.length - 1]) {
-                    mActivityViewCallback.onTaskMovedToFront(stackInfo);
-                }
+            if (mActivityViewCallback  == null) {
+                return;
+            }
+
+            StackInfo stackInfo = getTopMostStackInfo();
+            // if StackInfo was null or unrelated to the "move to front" then there's no use
+            // notifying the callback
+            if (stackInfo != null
+                    && taskId == stackInfo.taskIds[stackInfo.taskIds.length - 1]) {
+                mActivityViewCallback.onTaskMovedToFront(stackInfo);
+            }
+        }
+
+        @Override
+        public void onTaskCreated(int taskId, ComponentName componentName) throws RemoteException {
+            if (mActivityViewCallback  == null) {
+                return;
+            }
+
+            StackInfo stackInfo = getTopMostStackInfo();
+            // if StackInfo was null or unrelated to the task creation then there's no use
+            // notifying the callback
+            if (stackInfo != null
+                    && taskId == stackInfo.taskIds[stackInfo.taskIds.length - 1]) {
+                mActivityViewCallback.onTaskCreated(taskId, componentName);
+            }
+        }
+
+        @Override
+        public void onTaskRemovalStarted(int taskId) throws RemoteException {
+            if (mActivityViewCallback  == null) {
+                return;
+            }
+            StackInfo stackInfo = getTopMostStackInfo();
+            // if StackInfo was null or task is on a different display then there's no use
+            // notifying the callback
+            if (stackInfo != null
+                    && taskId == stackInfo.taskIds[stackInfo.taskIds.length - 1]) {
+                mActivityViewCallback.onTaskRemovalStarted(taskId);
             }
         }
 
diff --git a/core/java/android/app/AppOpsManager.java b/core/java/android/app/AppOpsManager.java
index ab2430c..364d3c9 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;
@@ -1712,6 +1713,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 +4476,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 +4842,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/IActivityTaskManager.aidl b/core/java/android/app/IActivityTaskManager.aidl
index cc6c999..db6ad3d 100644
--- a/core/java/android/app/IActivityTaskManager.aidl
+++ b/core/java/android/app/IActivityTaskManager.aidl
@@ -434,6 +434,11 @@
     void registerRemoteAnimationForNextActivityStart(in String packageName,
            in RemoteAnimationAdapter adapter);
 
+    /**
+     * Registers remote animations for a display.
+     */
+    void registerRemoteAnimationsForDisplay(int displayId, in RemoteAnimationDefinition definition);
+
     /** @see android.app.ActivityManager#alwaysShowUnsupportedCompileSdkWarning */
     void alwaysShowUnsupportedCompileSdkWarning(in ComponentName activity);
 
diff --git a/core/java/android/app/IApplicationThread.aidl b/core/java/android/app/IApplicationThread.aidl
index fcb6c14..c64fcf3 100644
--- a/core/java/android/app/IApplicationThread.aidl
+++ b/core/java/android/app/IApplicationThread.aidl
@@ -100,8 +100,7 @@
     void dumpActivity(in ParcelFileDescriptor fd, IBinder servicetoken, in String prefix,
             in String[] args);
     void clearDnsCache();
-    void setHttpProxy(in String proxy, in String port, in String exclList,
-            in Uri pacFileUrl);
+    void updateHttpProxy();
     void setCoreSettings(in Bundle coreSettings);
     void updatePackageCompatibilityInfo(in String pkg, in CompatibilityInfo info);
     void scheduleTrimMemory(int level);
diff --git a/core/java/android/app/KeyguardManager.java b/core/java/android/app/KeyguardManager.java
index 75c9054..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
@@ -118,15 +125,19 @@
     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.
      *
      * @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) {
+    public Intent createConfirmDeviceCredentialIntent(CharSequence title,
+            CharSequence description) {
         if (!isDeviceSecure()) return null;
         Intent intent = new Intent(ACTION_CONFIRM_DEVICE_CREDENTIAL);
         intent.putExtra(EXTRA_TITLE, title);
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 43614fe..c4b4b40 100644
--- a/core/java/android/app/NotificationManager.java
+++ b/core/java/android/app/NotificationManager.java
@@ -1137,9 +1137,6 @@
         }
     }
 
-    /**
-     * @hide
-     */
     public boolean isNotificationAssistantAccessGranted(ComponentName assistant) {
         INotificationManager service = getService();
         try {
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/admin/DevicePolicyManager.java b/core/java/android/app/admin/DevicePolicyManager.java
index 55a3acb..2514eee 100644
--- a/core/java/android/app/admin/DevicePolicyManager.java
+++ b/core/java/android/app/admin/DevicePolicyManager.java
@@ -3291,7 +3291,7 @@
      */
     public int getPasswordMaximumLength(int quality) {
         PackageManager pm = mContext.getPackageManager();
-        if (!pm.hasSystemFeature(PackageManager.FEATURE_DEVICE_ID_ATTESTATION)) {
+        if (!pm.hasSystemFeature(PackageManager.FEATURE_SECURE_LOCK_SCREEN)) {
             return 0;
         }
         // Kind-of arbitrary.
@@ -3980,6 +3980,10 @@
      */
     public static final int WIPE_EUICC = 0x0004;
 
+    /**
+     * Flag for {@link #wipeData(int)}: won't show reason for wiping to the user.
+     */
+    public static final int WIPE_SILENTLY = 0x0008;
 
     /**
      * Ask that all user data be wiped. If called as a secondary user, the user will be removed and
@@ -3991,9 +3995,10 @@
      * be able to call this method; if it has not, a security exception will be thrown.
      *
      * @param flags Bit mask of additional options: currently supported flags are
-     *            {@link #WIPE_EXTERNAL_STORAGE} and {@link #WIPE_RESET_PROTECTION_DATA}.
+     *            {@link #WIPE_EXTERNAL_STORAGE}, {@link #WIPE_RESET_PROTECTION_DATA},
+     *            {@link #WIPE_EUICC} and {@link #WIPE_SILENTLY}.
      * @throws SecurityException if the calling application does not own an active administrator
-     *             that uses {@link DeviceAdminInfo#USES_POLICY_WIPE_DATA}
+     *            that uses {@link DeviceAdminInfo#USES_POLICY_WIPE_DATA}
      */
     public void wipeData(int flags) {
         throwIfParentInstance("wipeData");
@@ -4013,16 +4018,21 @@
      * be able to call this method; if it has not, a security exception will be thrown.
      *
      * @param flags Bit mask of additional options: currently supported flags are
-     *            {@link #WIPE_EXTERNAL_STORAGE} and {@link #WIPE_RESET_PROTECTION_DATA}.
+     *            {@link #WIPE_EXTERNAL_STORAGE}, {@link #WIPE_RESET_PROTECTION_DATA} and
+     *            {@link #WIPE_EUICC}.
      * @param reason a string that contains the reason for wiping data, which can be
-     *            presented to the user. If the string is null or empty, user won't be notified.
+     *            presented to the user.
      * @throws SecurityException if the calling application does not own an active administrator
-     *             that uses {@link DeviceAdminInfo#USES_POLICY_WIPE_DATA}
-     * @throws IllegalArgumentException if the input reason string is null or empty.
+     *            that uses {@link DeviceAdminInfo#USES_POLICY_WIPE_DATA}
+     * @throws IllegalArgumentException if the input reason string is null or empty, or if
+     *            {@link #WIPE_SILENTLY} is set.
      */
-    public void wipeData(int flags, CharSequence reason) {
+    public void wipeData(int flags, @NonNull CharSequence reason) {
         throwIfParentInstance("wipeData");
-        wipeDataInternal(flags, reason != null ? reason.toString() : null);
+        Preconditions.checkNotNull(reason, "reason string is null");
+        Preconditions.checkStringNotEmpty(reason, "reason string is empty");
+        Preconditions.checkArgument((flags & WIPE_SILENTLY) == 0, "WIPE_SILENTLY cannot be set");
+        wipeDataInternal(flags, reason.toString());
     }
 
     /**
@@ -4033,7 +4043,7 @@
      * @see #wipeData(int, CharSequence)
      * @hide
      */
-    private void wipeDataInternal(int flags, String wipeReasonForUser) {
+    private void wipeDataInternal(int flags, @NonNull String wipeReasonForUser) {
         if (mService != null) {
             try {
                 mService.wipeDataWithReason(flags, wipeReasonForUser);
@@ -10428,23 +10438,28 @@
     }
 
     /**
-     * Whitelists a package that is allowed to access cross profile calendar APIs.
+     * Whitelists a set of packages that are allowed to access cross-profile calendar APIs.
      *
      * <p>Called by a profile owner of a managed profile.
      *
+     * <p>Calling with a null value for the set disables the restriction so that all packages
+     * are allowed to access cross-profile calendar APIs. Calling with an empty set disallows
+     * all packages from accessing cross-profile calendar APIs. If this method isn't called,
+     * no package will be allowed to access cross-profile calendar APIs by default.
+     *
      * @param admin which {@link DeviceAdminReceiver} this request is associated with.
-     * @param packageName name of the package to be whitelisted.
+     * @param packageNames set of packages to be whitelisted.
      * @throws SecurityException if {@code admin} is not a profile owner.
      *
-     * @see #removeCrossProfileCalendarPackage(ComponentName, String)
      * @see #getCrossProfileCalendarPackages(ComponentName)
      */
-    public void addCrossProfileCalendarPackage(@NonNull ComponentName admin,
-            @NonNull String packageName) {
-        throwIfParentInstance("addCrossProfileCalendarPackage");
+    public void setCrossProfileCalendarPackages(@NonNull ComponentName admin,
+            @Nullable Set<String> packageNames) {
+        throwIfParentInstance("setCrossProfileCalendarPackages");
         if (mService != null) {
             try {
-                mService.addCrossProfileCalendarPackage(admin, packageName);
+                mService.setCrossProfileCalendarPackages(admin, packageNames == null ? null
+                        : new ArrayList<>(packageNames));
             } catch (RemoteException e) {
                 throw e.rethrowFromSystemServer();
             }
@@ -10452,52 +10467,24 @@
     }
 
     /**
-     * Removes a package that was allowed to access cross profile calendar APIs
-     * from the whitelist.
-     *
-     * <p>Called by a profile owner of a managed profile.
-     *
-     * @param admin which {@link DeviceAdminReceiver} this request is associated with.
-     * @param packageName name of the package to be removed from the whitelist.
-     * @return {@code true} if the package is successfully removed from the whitelist,
-     * {@code false} otherwise.
-     * @throws SecurityException if {@code admin} is not a profile owner.
-     *
-     * @see #addCrossProfileCalendarPackage(ComponentName, String)
-     * @see #getCrossProfileCalendarPackages(ComponentName)
-     */
-    public boolean removeCrossProfileCalendarPackage(@NonNull ComponentName admin,
-            @NonNull String packageName) {
-        throwIfParentInstance("removeCrossProfileCalendarPackage");
-        if (mService != null) {
-            try {
-                return mService.removeCrossProfileCalendarPackage(admin, packageName);
-            } catch (RemoteException e) {
-                throw e.rethrowFromSystemServer();
-            }
-        }
-        return false;
-    }
-
-    /**
-     * Gets a set of package names that are whitelisted to access cross profile calendar APIs.
+     * Gets a set of package names that are whitelisted to access cross-profile calendar APIs.
      *
      * <p>Called by a profile owner of a managed profile.
      *
      * @param admin which {@link DeviceAdminReceiver} this request is associated with.
      * @return the set of names of packages that were previously whitelisted via
-     * {@link #addCrossProfileCalendarPackage(ComponentName, String)}, or an
+     * {@link #setCrossProfileCalendarPackages(ComponentName, Set)}, or an
      * empty set if none have been whitelisted.
      * @throws SecurityException if {@code admin} is not a profile owner.
      *
-     * @see #addCrossProfileCalendarPackage(ComponentName, String)
-     * @see #removeCrossProfileCalendarPackage(ComponentName, String)
+     * @see #setCrossProfileCalendarPackages(ComponentName, Set)
      */
-    public @NonNull Set<String> getCrossProfileCalendarPackages(@NonNull ComponentName admin) {
+    public @Nullable Set<String> getCrossProfileCalendarPackages(@NonNull ComponentName admin) {
         throwIfParentInstance("getCrossProfileCalendarPackages");
         if (mService != null) {
             try {
-                return new ArraySet<>(mService.getCrossProfileCalendarPackages(admin));
+                final List<String> packageNames = mService.getCrossProfileCalendarPackages(admin);
+                return packageNames == null ? null : new ArraySet<>(packageNames);
             } catch (RemoteException e) {
                 throw e.rethrowFromSystemServer();
             }
@@ -10506,22 +10493,21 @@
     }
 
     /**
-     * Returns if a package is whitelisted to access cross profile calendar APIs.
+     * Returns if a package is whitelisted to access cross-profile calendar APIs.
      *
      * <p>To query for a specific user, use
      * {@link Context#createPackageContextAsUser(String, int, UserHandle)} to create a context for
      * that user, and get a {@link DevicePolicyManager} from this context.
      *
      * @param packageName the name of the package
-     * @return {@code true} if the package is whitelisted to access cross profile calendar APIs.
+     * @return {@code true} if the package is whitelisted to access cross-profile calendar APIs.
      * {@code false} otherwise.
      *
-     * @see #addCrossProfileCalendarPackage(ComponentName, String)
-     * @see #removeCrossProfileCalendarPackage(ComponentName, String)
+     * @see #setCrossProfileCalendarPackages(ComponentName, Set)
      * @see #getCrossProfileCalendarPackages(ComponentName)
      * @hide
      */
-    public @NonNull boolean isPackageAllowedToAccessCalendar(@NonNull  String packageName) {
+    public boolean isPackageAllowedToAccessCalendar(@NonNull  String packageName) {
         throwIfParentInstance("isPackageAllowedToAccessCalendar");
         if (mService != null) {
             try {
@@ -10535,27 +10521,27 @@
     }
 
     /**
-     * Gets a set of package names that are whitelisted to access cross profile calendar APIs.
+     * Gets a set of package names that are whitelisted to access cross-profile calendar APIs.
      *
      * <p>To query for a specific user, use
      * {@link Context#createPackageContextAsUser(String, int, UserHandle)} to create a context for
      * that user, and get a {@link DevicePolicyManager} from this context.
      *
      * @return the set of names of packages that were previously whitelisted via
-     * {@link #addCrossProfileCalendarPackage(ComponentName, String)}, or an
+     * {@link #setCrossProfileCalendarPackages(ComponentName, Set)}, or an
      * empty set if none have been whitelisted.
      *
-     * @see #addCrossProfileCalendarPackage(ComponentName, String)
-     * @see #removeCrossProfileCalendarPackage(ComponentName, String)
+     * @see #setCrossProfileCalendarPackages(ComponentName, Set)
      * @see #getCrossProfileCalendarPackages(ComponentName)
      * @hide
      */
-    public @NonNull Set<String>  getCrossProfileCalendarPackages() {
+    public @Nullable Set<String> getCrossProfileCalendarPackages() {
         throwIfParentInstance("getCrossProfileCalendarPackages");
         if (mService != null) {
             try {
-                return new ArraySet<>(mService.getCrossProfileCalendarPackagesForUser(
-                        myUserId()));
+                final List<String> packageNames = mService.getCrossProfileCalendarPackagesForUser(
+                        myUserId());
+                return packageNames == null ? null : new ArraySet<>(packageNames);
             } catch (RemoteException e) {
                 throw e.rethrowFromSystemServer();
             }
diff --git a/core/java/android/app/admin/IDevicePolicyManager.aidl b/core/java/android/app/admin/IDevicePolicyManager.aidl
index 568becf..1751a91c 100644
--- a/core/java/android/app/admin/IDevicePolicyManager.aidl
+++ b/core/java/android/app/admin/IDevicePolicyManager.aidl
@@ -424,8 +424,7 @@
 
     void installUpdateFromFile(in ComponentName admin, in ParcelFileDescriptor updateFileDescriptor, in StartInstallingUpdateCallback listener);
 
-    void addCrossProfileCalendarPackage(in ComponentName admin, String packageName);
-    boolean removeCrossProfileCalendarPackage(in ComponentName admin, String packageName);
+    void setCrossProfileCalendarPackages(in ComponentName admin, in List<String> packageNames);
     List<String> getCrossProfileCalendarPackages(in ComponentName admin);
     boolean isPackageAllowedToAccessCalendarForUser(String packageName, int userHandle);
     List<String> getCrossProfileCalendarPackagesForUser(int userHandle);
diff --git a/core/java/android/app/contentsuggestions/ClassificationsRequest.java b/core/java/android/app/contentsuggestions/ClassificationsRequest.java
index 3f71518..9bb39e5 100644
--- a/core/java/android/app/contentsuggestions/ClassificationsRequest.java
+++ b/core/java/android/app/contentsuggestions/ClassificationsRequest.java
@@ -26,6 +26,11 @@
 import java.util.List;
 
 /**
+ * Request object used when asking {@link ContentSuggestionsManager} to classify content selections.
+ *
+ * <p>The request contains a list of {@link ContentSelection} objects to be classified along with
+ * implementation specific extras.
+ *
  * @hide
  */
 @SystemApi
@@ -44,14 +49,14 @@
     /**
      * Return request selections.
      */
-    public List<ContentSelection> getSelections() {
+    public @NonNull List<ContentSelection> getSelections() {
         return mSelections;
     }
 
     /**
-     * Return the request extras.
+     * Return the request extras or {@code null} if there are none.
      */
-    public Bundle getExtras() {
+    public @Nullable Bundle getExtras() {
         return mExtras;
     }
 
diff --git a/core/java/android/app/contentsuggestions/ContentClassification.java b/core/java/android/app/contentsuggestions/ContentClassification.java
index f18520f..2a00b40 100644
--- a/core/java/android/app/contentsuggestions/ContentClassification.java
+++ b/core/java/android/app/contentsuggestions/ContentClassification.java
@@ -23,6 +23,9 @@
 import android.os.Parcelable;
 
 /**
+ * Represents the classification of a content suggestion. The result of a
+ * {@link ClassificationsRequest} to {@link ContentSuggestionsManager}.
+ *
  * @hide
  */
 @SystemApi
@@ -32,6 +35,12 @@
     @NonNull
     private final Bundle mExtras;
 
+    /**
+     * Default constructor.
+     *
+     * @param classificationId implementation specific id for the selection /  classification.
+     * @param extras containing the classification data.
+     */
     public ContentClassification(@NonNull String classificationId, @NonNull Bundle extras) {
         mClassificationId = classificationId;
         mExtras = extras;
@@ -40,14 +49,14 @@
     /**
      * Return the classification id.
      */
-    public String getId() {
+    public @NonNull String getId() {
         return mClassificationId;
     }
 
     /**
      * Return the classification extras.
      */
-    public Bundle getExtras() {
+    public @NonNull Bundle getExtras() {
         return mExtras;
     }
 
diff --git a/core/java/android/app/contentsuggestions/ContentSelection.java b/core/java/android/app/contentsuggestions/ContentSelection.java
index a8917f7..16b4f3f 100644
--- a/core/java/android/app/contentsuggestions/ContentSelection.java
+++ b/core/java/android/app/contentsuggestions/ContentSelection.java
@@ -23,6 +23,9 @@
 import android.os.Parcelable;
 
 /**
+ * Represents a suggested selection within a set of on screen content. The result of a
+ * {@link SelectionsRequest} to {@link ContentSuggestionsManager}.
+ *
  * @hide
  */
 @SystemApi
@@ -32,6 +35,12 @@
     @NonNull
     private final Bundle mExtras;
 
+    /**
+     * Default constructor.
+     *
+     * @param selectionId implementation specific id for the selection.
+     * @param extras containing the data that represents the selection.
+     */
     public ContentSelection(@NonNull String selectionId, @NonNull Bundle extras) {
         mSelectionId = selectionId;
         mExtras = extras;
@@ -40,14 +49,14 @@
     /**
      * Return the selection id.
      */
-    public String getId() {
+    public @NonNull String getId() {
         return mSelectionId;
     }
 
     /**
      * Return the selection extras.
      */
-    public Bundle getExtras() {
+    public @NonNull Bundle getExtras() {
         return mExtras;
     }
 
diff --git a/core/java/android/app/contentsuggestions/SelectionsRequest.java b/core/java/android/app/contentsuggestions/SelectionsRequest.java
index 16f3e6b..e3c8bc5 100644
--- a/core/java/android/app/contentsuggestions/SelectionsRequest.java
+++ b/core/java/android/app/contentsuggestions/SelectionsRequest.java
@@ -25,6 +25,12 @@
 import android.os.Parcelable;
 
 /**
+ * The request object used to request content selections from {@link ContentSuggestionsManager}.
+ *
+ * <p>Selections are requested for a given taskId as specified by
+ * {@link android.app.ActivityManager} and optionally take an interest point that specifies the
+ * point on the screen that should be considered as the most important.
+ *
  * @hide
  */
 @SystemApi
@@ -49,16 +55,17 @@
     }
 
     /**
-     * Return the request point of interest.
+     * Return the request point of interest or {@code null} if there is no point of interest for
+     * this request.
      */
-    public Point getInterestPoint() {
+    public @Nullable Point getInterestPoint() {
         return mInterestPoint;
     }
 
     /**
-     * Return the request extras.
+     * Return the request extras or {@code null} if there aren't any.
      */
-    public Bundle getExtras() {
+    public @Nullable Bundle getExtras() {
         return mExtras;
     }
 
@@ -99,6 +106,11 @@
         private Point mInterestPoint;
         private Bundle mExtras;
 
+        /**
+         * Default constructor.
+         *
+         * @param taskId of the type used by {@link android.app.ActivityManager}
+         */
         public Builder(int taskId) {
             mTaskId = taskId;
         }
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/role/RoleManager.java b/core/java/android/app/role/RoleManager.java
index a6abe0b..ddd5313 100644
--- a/core/java/android/app/role/RoleManager.java
+++ b/core/java/android/app/role/RoleManager.java
@@ -172,6 +172,15 @@
     public static final String ROLE_CALL_COMPANION_APP = "android.app.role.CALL_COMPANION_APP";
 
     /**
+     * The name of the assistant app role.
+     *
+     * @hide
+     */
+    @SystemApi
+    @TestApi
+    public static final String ROLE_ASSISTANT = "android.app.role.ASSISTANT";
+
+    /**
      * The action used to request user approval of a role for an application.
      *
      * @hide
diff --git a/core/java/android/app/usage/IUsageStatsManager.aidl b/core/java/android/app/usage/IUsageStatsManager.aidl
index d2934b9..b1500c1 100644
--- a/core/java/android/app/usage/IUsageStatsManager.aidl
+++ b/core/java/android/app/usage/IUsageStatsManager.aidl
@@ -55,6 +55,9 @@
             long sessionThresholdTimeMs, in PendingIntent limitReachedCallbackIntent,
             in PendingIntent sessionEndCallbackIntent, String callingPackage);
     void unregisterUsageSessionObserver(int sessionObserverId, String callingPackage);
+    void registerAppUsageLimitObserver(int observerId, in String[] packages, long timeLimitMs,
+            in PendingIntent callback, String callingPackage);
+    void unregisterAppUsageLimitObserver(int observerId, String callingPackage);
     void reportUsageStart(in IBinder activity, String token, String callingPackage);
     void reportPastUsageStart(in IBinder activity, String token, long timeAgoMs,
             String callingPackage);
diff --git a/core/java/android/app/usage/UsageStatsManager.java b/core/java/android/app/usage/UsageStatsManager.java
index d2de887..51397a2 100644
--- a/core/java/android/app/usage/UsageStatsManager.java
+++ b/core/java/android/app/usage/UsageStatsManager.java
@@ -619,7 +619,7 @@
      * @param timeLimit The total time the set of apps can be in the foreground before the
      *                  callbackIntent is delivered. Must be at least one minute.
      * @param timeUnit The unit for time specified in {@code timeLimit}. Cannot be null.
-     * @param callbackIntent The PendingIntent that will be dispatched when the time limit is
+     * @param callbackIntent The PendingIntent that will be dispatched when the usage limit is
      *                       exceeded by the group of apps. The delivered Intent will also contain
      *                       the extras {@link #EXTRA_OBSERVER_ID}, {@link #EXTRA_TIME_LIMIT} and
      *                       {@link #EXTRA_TIME_USED}. Cannot be null.
@@ -682,14 +682,14 @@
      * @param sessionThresholdTimeUnit The unit for time specified in {@code sessionThreshold}.
      *                                 Cannot be null.
      * @param limitReachedCallbackIntent The {@link PendingIntent} that will be dispatched when the
-     *                                   time limit is exceeded by the group of apps. The delivered
-     *                                   Intent will also contain the extras {@link
+     *                                   usage limit is exceeded by the group of apps. The
+     *                                   delivered Intent will also contain the extras {@link
      *                                   #EXTRA_OBSERVER_ID}, {@link #EXTRA_TIME_LIMIT} and {@link
      *                                   #EXTRA_TIME_USED}. Cannot be null.
      * @param sessionEndCallbackIntent The {@link PendingIntent}  that will be dispatched when the
-     *                                 session has ended after the time limit has been exceeded. The
-     *                                 session is considered at its end after the {@code observed}
-     *                                 usage has stopped and an additional {@code
+     *                                 session has ended after the usage limit has been exceeded.
+     *                                 The session is considered at its end after the {@code
+     *                                 observed} usage has stopped and an additional {@code
      *                                 sessionThresholdTime} has passed. The delivered Intent will
      *                                 also contain the extras {@link #EXTRA_OBSERVER_ID} and {@link
      *                                 #EXTRA_TIME_USED}. Can be null.
@@ -736,6 +736,74 @@
     }
 
     /**
+     * Register a usage limit observer that receives a callback on the provided intent when the
+     * sum of usages of apps and tokens in the provided {@code observedEntities} array exceeds the
+     * {@code timeLimit} specified. The structure of a token is a {@link String} with the reporting
+     * package's name and a token that the calling app will use, separated by the forward slash
+     * character. Example: com.reporting.package/5OM3*0P4QU3-7OK3N
+     * <p>
+     * Registering an {@code observerId} that was already registered will override the previous one.
+     * No more than 1000 unique {@code observerId} may be registered by a single uid
+     * at any one time.
+     * A limit may be unregistered via {@link #unregisterAppUsageLimitObserver}
+     * <p>
+     * This method is similar to {@link #registerAppUsageObserver}, but the usage limit set here
+     * will be visible to the launcher so that it can report the limit to the user and how much
+     * of it is remaining.
+     * @see android.content.pm.LauncherApps#getAppUsageLimit
+     *
+     * @param observerId A unique id associated with the group of apps to be monitored. There can
+     *                  be multiple groups with common packages and different time limits.
+     * @param observedEntities The list of packages and token to observe for usage time. Cannot be
+     *                         null and must include at least one package or token.
+     * @param timeLimit The total time the set of apps can be in the foreground before the
+     *                  callbackIntent is delivered. Must be at least one minute.
+     * @param timeUnit The unit for time specified in {@code timeLimit}. Cannot be null.
+     * @param callbackIntent The PendingIntent that will be dispatched when the  usage limit is
+     *                       exceeded by the group of apps. The delivered Intent will also contain
+     *                       the extras {@link #EXTRA_OBSERVER_ID}, {@link #EXTRA_TIME_LIMIT} and
+     *                       {@link #EXTRA_TIME_USED}. Cannot be null.
+     * @throws SecurityException if the caller doesn't have both SUSPEND_APPS and OBSERVE_APP_USAGE
+     *                           permissions.
+     * @hide
+     */
+    @SystemApi
+    @RequiresPermission(allOf = {
+            android.Manifest.permission.SUSPEND_APPS,
+            android.Manifest.permission.OBSERVE_APP_USAGE})
+    public void registerAppUsageLimitObserver(int observerId, @NonNull String[] observedEntities,
+            long timeLimit, @NonNull TimeUnit timeUnit, @NonNull PendingIntent callbackIntent) {
+        try {
+            mService.registerAppUsageLimitObserver(observerId, observedEntities,
+                    timeUnit.toMillis(timeLimit), callbackIntent, mContext.getOpPackageName());
+        } catch (RemoteException e) {
+            throw e.rethrowFromSystemServer();
+        }
+    }
+
+    /**
+     * Unregister the app usage limit observer specified by the {@code observerId}.
+     * This will only apply to any observer registered by this application. Unregistering
+     * an observer that was already unregistered or never registered will have no effect.
+     *
+     * @param observerId The id of the observer that was previously registered.
+     * @throws SecurityException if the caller doesn't have both SUSPEND_APPS and OBSERVE_APP_USAGE
+     *                           permissions.
+     * @hide
+     */
+    @SystemApi
+    @RequiresPermission(allOf = {
+            android.Manifest.permission.SUSPEND_APPS,
+            android.Manifest.permission.OBSERVE_APP_USAGE})
+    public void unregisterAppUsageLimitObserver(int observerId) {
+        try {
+            mService.unregisterAppUsageLimitObserver(observerId, mContext.getOpPackageName());
+        } catch (RemoteException e) {
+            throw e.rethrowFromSystemServer();
+        }
+    }
+
+    /**
      * Report usage associated with a particular {@code token} has started. Tokens are app defined
      * strings used to represent usage of in-app features. Apps with the {@link
      * android.Manifest.permission#OBSERVE_APP_USAGE} permission can register time limit observers
@@ -743,6 +811,7 @@
      * and usage will be considered stopped if the activity stops or crashes.
      * @see #registerAppUsageObserver
      * @see #registerUsageSessionObserver
+     * @see #registerAppUsageLimitObserver
      *
      * @param activity The activity {@code token} is associated with.
      * @param token The token to report usage against.
@@ -766,6 +835,7 @@
      * {@code activity} and usage will be considered stopped if the activity stops or crashes.
      * @see #registerAppUsageObserver
      * @see #registerUsageSessionObserver
+     * @see #registerAppUsageLimitObserver
      *
      * @param activity The activity {@code token} is associated with.
      * @param token The token to report usage against.
diff --git a/core/java/android/app/usage/UsageStatsManagerInternal.java b/core/java/android/app/usage/UsageStatsManagerInternal.java
index d2d0cf9c..3d3c03a 100644
--- a/core/java/android/app/usage/UsageStatsManagerInternal.java
+++ b/core/java/android/app/usage/UsageStatsManagerInternal.java
@@ -20,6 +20,7 @@
 import android.app.usage.UsageStatsManager.StandbyBuckets;
 import android.content.ComponentName;
 import android.content.res.Configuration;
+import android.os.UserHandle;
 
 import java.util.List;
 import java.util.Set;
@@ -270,4 +271,40 @@
      * @param userId which user the app is associated with
      */
     public abstract void reportExemptedSyncStart(String packageName, @UserIdInt int userId);
+
+    /**
+     * Returns an object describing the app usage limit for the given package which was set via
+     * {@link UsageStatsManager#registerAppUsageLimitObserver}.
+     * If there are multiple limits that apply to the package, the one with the smallest
+     * time remaining will be returned.
+     *
+     * @param packageName name of the package whose app usage limit will be returned
+     * @param user the user associated with the limit
+     * @return an {@link AppUsageLimitData} object describing the app time limit containing
+     * the given package, with the smallest time remaining.
+     */
+    public abstract AppUsageLimitData getAppUsageLimit(String packageName, UserHandle user);
+
+    /** 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;
+            this.mTotalUsageLimit = totalUsageLimit;
+            this.mUsageRemaining = usageRemaining;
+        }
+
+        public boolean isGroupLimit() {
+            return mGroupLimit;
+        }
+        public long getTotalUsageLimit() {
+            return mTotalUsageLimit;
+        }
+        public long getUsageRemaining() {
+            return mUsageRemaining;
+        }
+    }
 }
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/BluetoothAdapter.java b/core/java/android/bluetooth/BluetoothAdapter.java
index 97bc079..ab8c196 100644
--- a/core/java/android/bluetooth/BluetoothAdapter.java
+++ b/core/java/android/bluetooth/BluetoothAdapter.java
@@ -1900,6 +1900,20 @@
     }
 
     /**
+     * Return true if Hearing Aid Profile is supported.
+     *
+     * @return true if phone supports Hearing Aid Profile
+     */
+    private boolean isHearingAidProfileSupported() {
+        try {
+            return mManagerService.isHearingAidProfileSupported();
+        } catch (RemoteException e) {
+            Log.e(TAG, "remote expection when calling isHearingAidProfileSupported", e);
+            return false;
+        }
+    }
+
+    /**
      * Get the maximum number of connected audio devices.
      *
      * @return the maximum number of connected audio devices
@@ -2051,6 +2065,11 @@
                             supportedProfiles.add(i);
                         }
                     }
+                } else {
+                    // Bluetooth is disabled. Just fill in known supported Profiles
+                    if (isHearingAidProfileSupported()) {
+                        supportedProfiles.add(BluetoothProfile.HEARING_AID);
+                    }
                 }
             }
         } catch (RemoteException e) {
@@ -2468,15 +2487,16 @@
      * Get the profile proxy object associated with the profile.
      *
      * <p>Profile can be one of {@link BluetoothProfile#HEADSET}, {@link BluetoothProfile#A2DP},
-     * {@link BluetoothProfile#GATT}, or {@link BluetoothProfile#GATT_SERVER}. Clients must
-     * implement {@link BluetoothProfile.ServiceListener} to get notified of the connection status
-     * and to get the proxy object.
+     * {@link BluetoothProfile#GATT}, {@link BluetoothProfile#HEARING_AID}, or {@link
+     * BluetoothProfile#GATT_SERVER}. Clients must implement {@link
+     * BluetoothProfile.ServiceListener} to get notified of the connection status and to get the
+     * proxy object.
      *
      * @param context Context of the application
      * @param listener The service Listener for connection callbacks.
      * @param profile The Bluetooth profile; either {@link BluetoothProfile#HEADSET},
-     * {@link BluetoothProfile#A2DP}. {@link BluetoothProfile#GATT} or
-     * {@link BluetoothProfile#GATT_SERVER}.
+     * {@link BluetoothProfile#A2DP}, {@link BluetoothProfile#GATT}, {@link
+     * BluetoothProfile#HEARING_AID} or {@link BluetoothProfile#GATT_SERVER}.
      * @return true on success, false on error
      */
     public boolean getProfileProxy(Context context, BluetoothProfile.ServiceListener listener,
@@ -2525,8 +2545,11 @@
             BluetoothHidDevice hidDevice = new BluetoothHidDevice(context, listener);
             return true;
         } else if (profile == BluetoothProfile.HEARING_AID) {
-            BluetoothHearingAid hearingAid = new BluetoothHearingAid(context, listener);
-            return true;
+            if (isHearingAidProfileSupported()) {
+                BluetoothHearingAid hearingAid = new BluetoothHearingAid(context, listener);
+                return true;
+            }
+            return false;
         } else {
             return false;
         }
@@ -3253,7 +3276,7 @@
      * @hide
      */
     @SystemApi
-    public abstract class MetadataListener {
+    public abstract static class MetadataListener {
         /**
          * Callback triggered if the metadata of {@link BluetoothDevice} registered in
          * {@link #registerMetadataListener}.
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/BluetoothDevice.java b/core/java/android/bluetooth/BluetoothDevice.java
index 17cf702..4d8dc35 100644
--- a/core/java/android/bluetooth/BluetoothDevice.java
+++ b/core/java/android/bluetooth/BluetoothDevice.java
@@ -532,6 +532,28 @@
             "android.bluetooth.device.action.CONNECTION_ACCESS_CANCEL";
 
     /**
+     * Intent to broadcast silence mode changed.
+     * Alway contains the extra field {@link #EXTRA_DEVICE}
+     * Alway contains the extra field {@link #EXTRA_SILENCE_ENABLED}
+     *
+     * @hide
+     */
+    @SdkConstant(SdkConstantType.BROADCAST_INTENT_ACTION)
+    @SystemApi
+    public static final String ACTION_SILENCE_MODE_CHANGED =
+            "android.bluetooth.device.action.SILENCE_MODE_CHANGED";
+
+    /**
+     * Used as an extra field in {@link #ACTION_SILENCE_MODE_CHANGED} intent,
+     * contains whether device is in silence mode as boolean.
+     *
+     * @hide
+     */
+    @SystemApi
+    public static final String EXTRA_SILENCE_ENABLED =
+            "android.bluetooth.device.extra.SILENCE_ENABLED";
+
+    /**
      * Used as an extra field in {@link #ACTION_CONNECTION_ACCESS_REQUEST} intent.
      *
      * @hide
@@ -1592,6 +1614,70 @@
     }
 
     /**
+     * Set the Bluetooth device silence mode.
+     *
+     * When the {@link BluetoothDevice} enters silence mode, and the {@link BluetoothDevice}
+     * is an active device (for A2DP or HFP), the active device for that profile
+     * will be set to null.
+     * If the {@link BluetoothDevice} exits silence mode while the A2DP or HFP
+     * active device is null, the {@link BluetoothDevice} will be set as the
+     * active device for that profile.
+     * If the {@link BluetoothDevice} is disconnected, it exits silence mode.
+     * If the {@link BluetoothDevice} is set as the active device for A2DP or
+     * HFP, while silence mode is enabled, then the device will exit silence mode.
+     * If the {@link BluetoothDevice} is in silence mode, AVRCP position change
+     * event and HFP AG indicators will be disabled.
+     * If the {@link BluetoothDevice} is not connected with A2DP or HFP, it cannot
+     * enter silence mode.
+     *
+     * <p> Requires {@link android.Manifest.permission#BLUETOOTH_PRIVILEGED}.
+     *
+     * @param silence true to enter silence mode, false to exit
+     * @return true on success, false on error.
+     * @hide
+     */
+    @SystemApi
+    @RequiresPermission(android.Manifest.permission.BLUETOOTH_PRIVILEGED)
+    public boolean setSilenceMode(boolean silence) {
+        final IBluetooth service = sService;
+        if (service == null) {
+            return false;
+        }
+        try {
+            if (getSilenceMode() == silence) {
+                return true;
+            }
+            return service.setSilenceMode(this, silence);
+        } catch (RemoteException e) {
+            Log.e(TAG, "setSilenceMode fail", e);
+            return false;
+        }
+    }
+
+    /**
+     * Get the device silence mode status
+     *
+     * <p> Requires {@link android.Manifest.permission#BLUETOOTH_PRIVILEGED}.
+     *
+     * @return true on device in silence mode, otherwise false.
+     * @hide
+     */
+    @SystemApi
+    @RequiresPermission(android.Manifest.permission.BLUETOOTH_PRIVILEGED)
+    public boolean getSilenceMode() {
+        final IBluetooth service = sService;
+        if (service == null) {
+            return false;
+        }
+        try {
+            return service.getSilenceMode(this);
+        } catch (RemoteException e) {
+            Log.e(TAG, "getSilenceMode fail", e);
+            return false;
+        }
+    }
+
+    /**
      * Sets whether the phonebook access is allowed to this device.
      * <p>Requires {@link android.Manifest.permission#BLUETOOTH_PRIVILEGED}.
      *
diff --git a/core/java/android/bluetooth/BluetoothHearingAid.java b/core/java/android/bluetooth/BluetoothHearingAid.java
index 6ed7942..82cc1bc 100644
--- a/core/java/android/bluetooth/BluetoothHearingAid.java
+++ b/core/java/android/bluetooth/BluetoothHearingAid.java
@@ -39,15 +39,14 @@
 import java.util.concurrent.locks.ReentrantReadWriteLock;
 
 /**
- * This class provides the public APIs to control the Bluetooth Hearing Aid
- * profile.
+ * This class provides the public APIs to control the Hearing Aid profile.
  *
  * <p>BluetoothHearingAid is a proxy object for controlling the Bluetooth Hearing Aid
  * Service via IPC. Use {@link BluetoothAdapter#getProfileProxy} to get
  * the BluetoothHearingAid proxy object.
  *
- * <p> Each method is protected with its appropriate permission.
- * @hide
+ * <p> Android only supports one set of connected Bluetooth Hearing Aid device at a time. Each
+ * method is protected with its appropriate permission.
  */
 public final class BluetoothHearingAid implements BluetoothProfile {
     private static final String TAG = "BluetoothHearingAid";
@@ -56,7 +55,8 @@
 
     /**
      * Intent used to broadcast the change in connection state of the Hearing Aid
-     * profile.
+     * profile. Please note that in the binaural case, there will be two different LE devices for
+     * the left and right side and each device will have their own connection state changes.S
      *
      * <p>This intent will have 3 extras:
      * <ul>
@@ -77,27 +77,6 @@
             "android.bluetooth.hearingaid.profile.action.CONNECTION_STATE_CHANGED";
 
     /**
-     * Intent used to broadcast the change in the Playing state of the Hearing Aid
-     * profile.
-     *
-     * <p>This intent will have 3 extras:
-     * <ul>
-     * <li> {@link #EXTRA_STATE} - The current state of the profile. </li>
-     * <li> {@link #EXTRA_PREVIOUS_STATE}- The previous state of the profile. </li>
-     * <li> {@link BluetoothDevice#EXTRA_DEVICE} - The remote device. </li>
-     * </ul>
-     *
-     * <p>{@link #EXTRA_STATE} or {@link #EXTRA_PREVIOUS_STATE} can be any of
-     * {@link #STATE_PLAYING}, {@link #STATE_NOT_PLAYING},
-     *
-     * <p>Requires {@link android.Manifest.permission#BLUETOOTH} permission to
-     * receive.
-     */
-    @SdkConstant(SdkConstantType.BROADCAST_INTENT_ACTION)
-    public static final String ACTION_PLAYING_STATE_CHANGED =
-            "android.bluetooth.hearingaid.profile.action.PLAYING_STATE_CHANGED";
-
-    /**
      * Intent used to broadcast the selection of a connected device as active.
      *
      * <p>This intent will have one extra:
@@ -108,6 +87,8 @@
      *
      * <p>Requires {@link android.Manifest.permission#BLUETOOTH} permission to
      * receive.
+     *
+     * @hide
      */
     @SdkConstant(SdkConstantType.BROADCAST_INTENT_ACTION)
     @UnsupportedAppUsage
@@ -115,32 +96,38 @@
             "android.bluetooth.hearingaid.profile.action.ACTIVE_DEVICE_CHANGED";
 
     /**
-     * Hearing Aid device is streaming music. This state can be one of
-     * {@link #EXTRA_STATE} or {@link #EXTRA_PREVIOUS_STATE} of
-     * {@link #ACTION_PLAYING_STATE_CHANGED} intent.
+     * This device represents Left Hearing Aid.
+     *
+     * @hide
      */
-    public static final int STATE_PLAYING = 10;
-
-    /**
-     * Hearing Aid device is NOT streaming music. This state can be one of
-     * {@link #EXTRA_STATE} or {@link #EXTRA_PREVIOUS_STATE} of
-     * {@link #ACTION_PLAYING_STATE_CHANGED} intent.
-     */
-    public static final int STATE_NOT_PLAYING = 11;
-
-    /** This device represents Left Hearing Aid. */
     public static final int SIDE_LEFT = IBluetoothHearingAid.SIDE_LEFT;
 
-    /** This device represents Right Hearing Aid. */
+    /**
+     * This device represents Right Hearing Aid.
+     *
+     * @hide
+     */
     public static final int SIDE_RIGHT = IBluetoothHearingAid.SIDE_RIGHT;
 
-    /** This device is Monaural. */
+    /**
+     * This device is Monaural.
+     *
+     * @hide
+     */
     public static final int MODE_MONAURAL = IBluetoothHearingAid.MODE_MONAURAL;
 
-    /** This device is Binaural (should receive only left or right audio). */
+    /**
+     * This device is Binaural (should receive only left or right audio).
+     *
+     * @hide
+     */
     public static final int MODE_BINAURAL = IBluetoothHearingAid.MODE_BINAURAL;
 
-    /** Can't read ClientID for this device */
+    /**
+     * Indicates the HiSyncID could not be read and is unavailable.
+     *
+     * @hide
+     */
     public static final long HI_SYNC_ID_INVALID = IBluetoothHearingAid.HI_SYNC_ID_INVALID;
 
     private Context mContext;
@@ -236,12 +223,6 @@
         }
     }
 
-    @Override
-    public void finalize() {
-        // The empty finalize needs to be kept or the
-        // cts signature tests would fail.
-    }
-
     /**
      * Initiate connection to a profile of the remote bluetooth device.
      *
@@ -538,10 +519,6 @@
                 return "connected";
             case STATE_DISCONNECTING:
                 return "disconnecting";
-            case STATE_PLAYING:
-                return "playing";
-            case STATE_NOT_PLAYING:
-                return "not playing";
             default:
                 return "<unknown state " + state + ">";
         }
diff --git a/core/java/android/bluetooth/BluetoothProfile.java b/core/java/android/bluetooth/BluetoothProfile.java
index 3c87c73..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
      */
@@ -185,7 +198,6 @@
     /**
      * Hearing Aid Device
      *
-     * @hide
      */
     int HEARING_AID = 21;
 
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 b46203c..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
@@ -3047,6 +3046,13 @@
     @SdkConstant(SdkConstantType.BROADCAST_INTENT_ACTION)
     public static final String ACTION_MEDIA_SCANNER_SCAN_FILE = "android.intent.action.MEDIA_SCANNER_SCAN_FILE";
 
+    /**
+     * Broadcast Action:  Request the media scanner to scan a storage volume and add it to the media database.
+     * The path to the storage volume is contained in the Intent.mData field.
+     */
+    @SdkConstant(SdkConstantType.BROADCAST_INTENT_ACTION)
+    public static final String ACTION_MEDIA_SCANNER_SCAN_VOLUME = "android.intent.action.MEDIA_SCANNER_SCAN_VOLUME";
+
    /**
      * Broadcast Action:  The "Media Button" was pressed.  Includes a single
      * extra field, {@link #EXTRA_KEY_EVENT}, containing the key event that
@@ -9969,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);
         }
@@ -10016,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/ActivityInfo.java b/core/java/android/content/pm/ActivityInfo.java
index 3cfbe0c..47034a6 100644
--- a/core/java/android/content/pm/ActivityInfo.java
+++ b/core/java/android/content/pm/ActivityInfo.java
@@ -508,7 +508,7 @@
     /**
      * Bit in {@link #privateFlags} indicating if the activity should be shown when locked in case
      * an activity behind this can also be shown when locked.
-     * See android.R.attr#inheritShowWhenLocked
+     * See {@link android.R.attr#inheritShowWhenLocked}.
      * @hide
      */
     public static final int FLAG_INHERIT_SHOW_WHEN_LOCKED = 0x1;
diff --git a/core/java/android/content/pm/ApplicationInfo.java b/core/java/android/content/pm/ApplicationInfo.java
index 576466f..5d6d144 100644
--- a/core/java/android/content/pm/ApplicationInfo.java
+++ b/core/java/android/content/pm/ApplicationInfo.java
@@ -1955,6 +1955,11 @@
         return (privateFlags & ApplicationInfo.PRIVATE_FLAG_PRODUCT_SERVICES) != 0;
     }
 
+    /** @hide */
+    public boolean isCodeIntegrityPreferred() {
+        return (privateFlags & PRIVATE_FLAG_PREFER_CODE_INTEGRITY) != 0;
+    }
+
     /**
      * Returns whether or not this application was installed as a virtual preload.
      */
diff --git a/core/java/android/content/pm/ILauncherApps.aidl b/core/java/android/content/pm/ILauncherApps.aidl
index db2b6fd..d1bc377 100644
--- a/core/java/android/content/pm/ILauncherApps.aidl
+++ b/core/java/android/content/pm/ILauncherApps.aidl
@@ -23,6 +23,7 @@
 import android.content.pm.ActivityInfo;
 import android.content.pm.ApplicationInfo;
 import android.content.pm.IOnAppsChangedListener;
+import android.content.pm.LauncherApps;
 import android.content.pm.ParceledListSlice;
 import android.content.pm.ResolveInfo;
 import android.content.pm.ShortcutInfo;
@@ -56,6 +57,9 @@
     ApplicationInfo getApplicationInfo(
             String callingPackage, String packageName, int flags, in UserHandle user);
 
+    LauncherApps.AppUsageLimit getAppUsageLimit(String callingPackage, String packageName,
+            in UserHandle user);
+
     ParceledListSlice getShortcuts(String callingPackage, long changedSince, String packageName,
             in List shortcutIds, in ComponentName componentName, int flags, in UserHandle user);
     void pinShortcuts(String callingPackage, String packageName, in List<String> shortcutIds,
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.aidl b/core/java/android/content/pm/LauncherApps.aidl
new file mode 100644
index 0000000..1d98ad1
--- /dev/null
+++ b/core/java/android/content/pm/LauncherApps.aidl
@@ -0,0 +1,19 @@
+/**
+ * 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.content.pm;
+
+parcelable LauncherApps.AppUsageLimit;
diff --git a/core/java/android/content/pm/LauncherApps.java b/core/java/android/content/pm/LauncherApps.java
index 766c566..89630e1 100644
--- a/core/java/android/content/pm/LauncherApps.java
+++ b/core/java/android/content/pm/LauncherApps.java
@@ -758,6 +758,27 @@
     }
 
     /**
+     * Returns an object describing the app usage limit for the given package.
+     * If there are multiple limits that apply to the package, the one with the smallest
+     * time remaining will be returned.
+     *
+     * @param packageName name of the package whose app usage limit will be returned
+     * @param user the user of the package
+     *
+     * @return an {@link AppUsageLimit} object describing the app time limit containing
+     * the given package with the smallest time remaining, or {@code null} if none exist.
+     * @throws SecurityException when the caller is not the active launcher.
+     */
+    @Nullable
+    public LauncherApps.AppUsageLimit getAppUsageLimit(String packageName, UserHandle user) {
+        try {
+            return mService.getAppUsageLimit(mContext.getPackageName(), packageName, user);
+        } catch (RemoteException re) {
+            throw re.rethrowFromSystemServer();
+        }
+    }
+
+    /**
      * Checks if the activity exists and it enabled for a profile.
      *
      * @param component The activity to check.
@@ -1632,4 +1653,86 @@
             return 0;
         }
     }
+
+    /**
+     * 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.
+     *
+     * @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;
+            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
+         */
+        public long getTotalUsageLimit() {
+            return mTotalUsageLimit;
+        }
+
+        /**
+         * Returns the usage remaining in milliseconds for an app or the group of apps
+         * this limit refers to.
+         *
+         * @return the usage remaining in milliseconds
+         */
+        public long getUsageRemaining() {
+            return mUsageRemaining;
+        }
+
+        private AppUsageLimit(Parcel source) {
+            mGroupLimit = source.readBoolean();
+            mTotalUsageLimit = source.readLong();
+            mUsageRemaining = source.readLong();
+        }
+
+        public static final Creator<AppUsageLimit> CREATOR = new Creator<AppUsageLimit>() {
+            @Override
+            public AppUsageLimit createFromParcel(Parcel source) {
+                return new AppUsageLimit(source);
+            }
+
+            @Override
+            public AppUsageLimit[] newArray(int size) {
+                return new AppUsageLimit[size];
+            }
+        };
+
+        @Override
+        public int describeContents() {
+            return 0;
+        }
+
+        @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/OrgApacheHttpLegacyUpdater.java b/core/java/android/content/pm/OrgApacheHttpLegacyUpdater.java
index 81e4105..7790067 100644
--- a/core/java/android/content/pm/OrgApacheHttpLegacyUpdater.java
+++ b/core/java/android/content/pm/OrgApacheHttpLegacyUpdater.java
@@ -25,12 +25,6 @@
  * Updates a package to ensure that if it targets < P that the org.apache.http.legacy library is
  * included by default.
  *
- * <p>This is separated out so that it can be conditionally included at build time depending on
- * whether org.apache.http.legacy is on the bootclasspath or not. In order to include this at
- * build time, and remove org.apache.http.legacy from the bootclasspath pass
- * REMOVE_OAHL_FROM_BCP=true on the build command line, otherwise this class will not be included
- * and the
- *
  * @hide
  */
 @VisibleForTesting
diff --git a/core/java/android/content/pm/PackageBackwardCompatibility.java b/core/java/android/content/pm/PackageBackwardCompatibility.java
index 03eefed..b19196a 100644
--- a/core/java/android/content/pm/PackageBackwardCompatibility.java
+++ b/core/java/android/content/pm/PackageBackwardCompatibility.java
@@ -45,13 +45,9 @@
     static {
         final List<PackageSharedLibraryUpdater> packageUpdaters = new ArrayList<>();
 
-        // Attempt to load and add the optional updater that will only be available when
-        // REMOVE_OAHL_FROM_BCP=true. If that could not be found then add the default updater that
-        // will remove any references to org.apache.http.library from the package so that it does
-        // not try and load the library when it is on the bootclasspath.
-        boolean bootClassPathContainsOAHL = !addOptionalUpdater(packageUpdaters,
-                "android.content.pm.OrgApacheHttpLegacyUpdater",
-                RemoveUnnecessaryOrgApacheHttpLegacyLibrary::new);
+        // Automatically add the org.apache.http.legacy library to the app classpath if the app
+        // targets < P.
+        packageUpdaters.add(new OrgApacheHttpLegacyUpdater());
 
         packageUpdaters.add(new AndroidHidlUpdater());
 
@@ -70,7 +66,7 @@
         PackageSharedLibraryUpdater[] updaterArray = packageUpdaters
                 .toArray(new PackageSharedLibraryUpdater[0]);
         INSTANCE = new PackageBackwardCompatibility(
-                bootClassPathContainsOAHL, bootClassPathContainsATB, updaterArray);
+                bootClassPathContainsATB, updaterArray);
     }
 
     /**
@@ -116,15 +112,12 @@
         return INSTANCE;
     }
 
-    private final boolean mBootClassPathContainsOAHL;
-
     private final boolean mBootClassPathContainsATB;
 
     private final PackageSharedLibraryUpdater[] mPackageUpdaters;
 
-    public PackageBackwardCompatibility(boolean bootClassPathContainsOAHL,
+    public PackageBackwardCompatibility(
             boolean bootClassPathContainsATB, PackageSharedLibraryUpdater[] packageUpdaters) {
-        this.mBootClassPathContainsOAHL = bootClassPathContainsOAHL;
         this.mBootClassPathContainsATB = bootClassPathContainsATB;
         this.mPackageUpdaters = packageUpdaters;
     }
@@ -148,14 +141,6 @@
     }
 
     /**
-     * True if the org.apache.http.legacy is on the bootclasspath, false otherwise.
-     */
-    @VisibleForTesting
-    public static boolean bootClassPathContainsOAHL() {
-        return INSTANCE.mBootClassPathContainsOAHL;
-    }
-
-    /**
      * True if the android.test.base is on the bootclasspath, false otherwise.
      */
     @VisibleForTesting
diff --git a/core/java/android/content/pm/PackageInstaller.java b/core/java/android/content/pm/PackageInstaller.java
index 94b7c45..2dc014c 100644
--- a/core/java/android/content/pm/PackageInstaller.java
+++ b/core/java/android/content/pm/PackageInstaller.java
@@ -1543,6 +1543,13 @@
             this.isStaged = true;
         }
 
+        /**
+         * Set this session to be installing an APEX package.
+         */
+        public void setInstallAsApex() {
+            installFlags |= PackageManager.INSTALL_APEX;
+        }
+
         /** {@hide} */
         public void dump(IndentingPrintWriter pw) {
             pw.printPair("mode", mode);
@@ -1703,6 +1710,7 @@
         /** {@hide} */
         public boolean isSessionFailed;
         private int mStagedSessionErrorCode;
+        private String mStagedSessionErrorMessage;
 
         /** {@hide} */
         @UnsupportedAppUsage
@@ -1742,6 +1750,7 @@
             isSessionReady = source.readBoolean();
             isSessionFailed = source.readBoolean();
             mStagedSessionErrorCode = source.readInt();
+            mStagedSessionErrorMessage = source.readString();
         }
 
         /**
@@ -2059,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
@@ -2099,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 9e2f316..783ee64 100644
--- a/core/java/android/content/pm/PackageManager.java
+++ b/core/java/android/content/pm/PackageManager.java
@@ -2223,6 +2223,13 @@
     public static final String FEATURE_TELEPHONY_MBMS = "android.hardware.telephony.mbms";
 
     /**
+     * Feature for {@link #getSystemAvailableFeatures} and {@link #hasSystemFeature}: The device
+     * supports attaching to IMS implementations using the ImsService API in telephony.
+     */
+    @SdkConstant(SdkConstantType.FEATURE)
+    public static final String FEATURE_TELEPHONY_IMS = "android.hardware.telephony.ims";
+
+    /**
      * Feature for {@link #getSystemAvailableFeatures} and
      * {@link #hasSystemFeature}: The device supports connecting to USB devices
      * as the USB host.
diff --git a/core/java/android/content/pm/PackageParser.java b/core/java/android/content/pm/PackageParser.java
index 88a240f..eb59cfc 100644
--- a/core/java/android/content/pm/PackageParser.java
+++ b/core/java/android/content/pm/PackageParser.java
@@ -8512,7 +8512,9 @@
             collectCerts ? PackageParser.PARSE_COLLECT_CERTIFICATES : 0);
 
         pi.packageName = apk.packageName;
+        ai.packageName = apk.packageName;
         pi.setLongVersionCode(apk.getLongVersionCode());
+        ai.setVersionCode(apk.getLongVersionCode());
 
         if (collectCerts) {
             if (apk.signingDetails.hasPastSigningCertificates()) {
diff --git a/core/java/android/content/pm/RegisteredServicesCache.java b/core/java/android/content/pm/RegisteredServicesCache.java
index 6e519c1..a8c3b88 100644
--- a/core/java/android/content/pm/RegisteredServicesCache.java
+++ b/core/java/android/content/pm/RegisteredServicesCache.java
@@ -475,14 +475,6 @@
         final List<ResolveInfo> resolveInfos = queryIntentServices(userId);
         for (ResolveInfo resolveInfo : resolveInfos) {
             try {
-                // if this package is not one of those changedUids, we don't need to scan it,
-                // since nothing in it changed, so save a call to parseServiceInfo, which
-                // can cause a large amount of the package apk to be loaded into memory.
-                // if this is the initial scan, changedUids will be null, and containsUid will
-                // trivially return true, and will call parseServiceInfo
-                if (!containsUid(changedUids, resolveInfo.serviceInfo.applicationInfo.uid)) {
-                    continue;
-                }
                 ServiceInfo<V> info = parseServiceInfo(resolveInfo);
                 if (info == null) {
                     Log.w(TAG, "Unable to load service info " + resolveInfo.toString());
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/permission/IRuntimePermissionPresenter.aidl b/core/java/android/content/pm/permission/IRuntimePermissionPresenter.aidl
index 2faf3ad..4f4c34b 100644
--- a/core/java/android/content/pm/permission/IRuntimePermissionPresenter.aidl
+++ b/core/java/android/content/pm/permission/IRuntimePermissionPresenter.aidl
@@ -27,5 +27,4 @@
  */
 oneway interface IRuntimePermissionPresenter {
     void getAppPermissions(String packageName, in RemoteCallback callback);
-    void revokeRuntimePermission(String packageName, String permissionName);
 }
diff --git a/core/java/android/content/rollback/IRollbackManager.aidl b/core/java/android/content/rollback/IRollbackManager.aidl
index 420bcb6..226d76a 100644
--- a/core/java/android/content/rollback/IRollbackManager.aidl
+++ b/core/java/android/content/rollback/IRollbackManager.aidl
@@ -17,20 +17,16 @@
 package android.content.rollback;
 
 import android.content.pm.ParceledListSlice;
-import android.content.pm.StringParceledListSlice;
 import android.content.rollback.RollbackInfo;
 import android.content.IntentSender;
 
 /** {@hide} */
 interface IRollbackManager {
 
-    RollbackInfo getAvailableRollback(String packageName);
-
-    StringParceledListSlice getPackagesWithAvailableRollbacks();
-
+    ParceledListSlice getAvailableRollbacks();
     ParceledListSlice getRecentlyExecutedRollbacks();
 
-    void executeRollback(in RollbackInfo rollback, String callerPackageName,
+    void commitRollback(int rollbackId, String callerPackageName,
             in IntentSender statusReceiver);
 
     // Exposed for use from the system server only. Callback from the package
diff --git a/core/java/android/content/rollback/RollbackInfo.java b/core/java/android/content/rollback/RollbackInfo.java
index 0803a7c..812f995 100644
--- a/core/java/android/content/rollback/RollbackInfo.java
+++ b/core/java/android/content/rollback/RollbackInfo.java
@@ -17,9 +17,12 @@
 package android.content.rollback;
 
 import android.annotation.SystemApi;
+import android.content.pm.PackageInstaller;
 import android.os.Parcel;
 import android.os.Parcelable;
 
+import java.util.List;
+
 /**
  * Information about a set of packages that can be, or already have been
  * rolled back together.
@@ -34,25 +37,17 @@
      */
     private final int mRollbackId;
 
-    /**
-     * The package that needs to be rolled back.
-     */
-    public final PackageRollbackInfo targetPackage;
-
-    // TODO: Add a list of additional packages rolled back due to atomic
-    // install dependencies when rollback of atomic installs is supported.
-    // TODO: Add a flag to indicate if reboot is required, when rollback of
-    // staged installs is supported.
+    private final List<PackageRollbackInfo> mPackages;
 
     /** @hide */
-    public RollbackInfo(int rollbackId, PackageRollbackInfo targetPackage) {
+    public RollbackInfo(int rollbackId, List<PackageRollbackInfo> packages) {
         this.mRollbackId = rollbackId;
-        this.targetPackage = targetPackage;
+        this.mPackages = packages;
     }
 
     private RollbackInfo(Parcel in) {
         mRollbackId = in.readInt();
-        targetPackage = PackageRollbackInfo.CREATOR.createFromParcel(in);
+        mPackages = in.createTypedArrayList(PackageRollbackInfo.CREATOR);
     }
 
     /**
@@ -62,6 +57,31 @@
         return mRollbackId;
     }
 
+    /**
+     * Returns the list of package that are rolled back.
+     */
+    public List<PackageRollbackInfo> getPackages() {
+        return mPackages;
+    }
+
+    /**
+     * Returns true if this rollback requires reboot to take effect after
+     * being committed.
+     */
+    public boolean isStaged() {
+        // TODO: Support rollback of staged installs.
+        return false;
+    }
+
+    /**
+     * Returns the session ID for the committed rollback for staged rollbacks.
+     * Only applicable for rollbacks that have been committed.
+     */
+    public int getSessionId() {
+        // TODO: Support rollback of staged installs.
+        return PackageInstaller.SessionInfo.INVALID_ID;
+    }
+
     @Override
     public int describeContents() {
         return 0;
@@ -70,7 +90,7 @@
     @Override
     public void writeToParcel(Parcel out, int flags) {
         out.writeInt(mRollbackId);
-        targetPackage.writeToParcel(out, flags);
+        out.writeTypedList(mPackages);
     }
 
     public static final Parcelable.Creator<RollbackInfo> CREATOR =
diff --git a/core/java/android/content/rollback/RollbackManager.java b/core/java/android/content/rollback/RollbackManager.java
index c1c0bc1..f8abcfc 100644
--- a/core/java/android/content/rollback/RollbackManager.java
+++ b/core/java/android/content/rollback/RollbackManager.java
@@ -17,7 +17,6 @@
 package android.content.rollback;
 
 import android.annotation.NonNull;
-import android.annotation.Nullable;
 import android.annotation.RequiresPermission;
 import android.annotation.SystemApi;
 import android.annotation.SystemService;
@@ -50,55 +49,26 @@
     }
 
     /**
-     * Returns the rollback currently available to be executed for the given
-     * package.
-     * <p>
-     * The returned RollbackInfo describes what packages would be rolled back,
-     * including package version codes before and after rollback. The rollback
-     * can be initiated using {@link #executeRollback(RollbackInfo,IntentSender)}.
-     * <p>
-     * TODO: What if there is no package installed on device for packageName?
+     * Returns a list of all currently available rollbacks.
      *
-     * @param packageName name of the package to get the availble RollbackInfo for.
-     * @return the rollback available for the package, or null if no rollback
-     *         is available for the package.
      * @throws SecurityException if the caller does not have the
      *            MANAGE_ROLLBACKS permission.
      */
     @RequiresPermission(android.Manifest.permission.MANAGE_ROLLBACKS)
-    public @Nullable RollbackInfo getAvailableRollback(@NonNull String packageName) {
+    public List<RollbackInfo> getAvailableRollbacks() {
         try {
-            return mBinder.getAvailableRollback(packageName);
+            return mBinder.getAvailableRollbacks().getList();
         } catch (RemoteException e) {
             throw e.rethrowFromSystemServer();
         }
     }
 
     /**
-     * Gets the names of packages that are available for rollback.
-     * Call {@link #getAvailableRollback(String)} to get more information
-     * about the rollback available for a particular package.
-     *
-     * @return the names of packages that are available for rollback.
-     * @throws SecurityException if the caller does not have the
-     *            MANAGE_ROLLBACKS permission.
-     */
-    @RequiresPermission(android.Manifest.permission.MANAGE_ROLLBACKS)
-    public @NonNull List<String> getPackagesWithAvailableRollbacks() {
-        try {
-            return mBinder.getPackagesWithAvailableRollbacks().getList();
-        } catch (RemoteException e) {
-            throw e.rethrowFromSystemServer();
-        }
-    }
-
-
-    /**
-     * Gets the list of all recently executed rollbacks.
+     * Gets the list of all recently committed rollbacks.
      * This is for the purposes of preventing re-install of a bad version of a
-     * package.
+     * package and monitoring the status of a staged rollback.
      * <p>
-     * Returns an empty list if there are no recently executed rollbacks.
+     * Returns an empty list if there are no recently committed rollbacks.
      * <p>
      * To avoid having to keep around complete rollback history forever on a
      * device, the returned list of rollbacks is only guaranteed to include
@@ -107,12 +77,12 @@
      * (without the possibility of rollback) to a higher version code than was
      * rolled back from.
      *
-     * @return the recently executed rollbacks
+     * @return the recently committed rollbacks
      * @throws SecurityException if the caller does not have the
      *            MANAGE_ROLLBACKS permission.
      */
     @RequiresPermission(android.Manifest.permission.MANAGE_ROLLBACKS)
-    public @NonNull List<RollbackInfo> getRecentlyExecutedRollbacks() {
+    public @NonNull List<RollbackInfo> getRecentlyCommittedRollbacks() {
         try {
             return mBinder.getRecentlyExecutedRollbacks().getList();
         } catch (RemoteException e) {
@@ -121,28 +91,26 @@
     }
 
     /**
-     * Execute the given rollback, rolling back all versions of the packages
-     * to the last good versions previously installed on the device as
-     * specified in the given rollback object. The rollback will fail if any
-     * of the installed packages or available rollbacks are inconsistent with
-     * the versions specified in the given rollback object, which can happen
-     * if a package has been updated or a rollback expired since the rollback
-     * object was retrieved from {@link #getAvailableRollback(String)}.
+     * Commit the rollback with given id, rolling back all versions of the
+     * packages to the last good versions previously installed on the device
+     * as specified in the corresponding RollbackInfo object. The
+     * rollback will fail if any of the installed packages or available
+     * rollbacks are inconsistent with the versions specified in the given
+     * rollback object, which can happen if a package has been updated or a
+     * rollback expired since the rollback object was retrieved from
+     * {@link #getAvailableRollbacks()}.
      * <p>
      * TODO: Specify the returns status codes.
-     * TODO: What happens in case reboot is required for the rollback to take
-     * effect for staged installs?
      *
-     * @param rollback to execute
+     * @param rollbackId ID of the rollback to commit
      * @param statusReceiver where to deliver the results
      * @throws SecurityException if the caller does not have the
      *            MANAGE_ROLLBACKS permission.
      */
     @RequiresPermission(android.Manifest.permission.MANAGE_ROLLBACKS)
-    public void executeRollback(@NonNull RollbackInfo rollback,
-            @NonNull IntentSender statusReceiver) {
+    public void commitRollback(int rollbackId, @NonNull IntentSender statusReceiver) {
         try {
-            mBinder.executeRollback(rollback, mCallerPackageName, statusReceiver);
+            mBinder.commitRollback(rollbackId, mCallerPackageName, statusReceiver);
         } catch (RemoteException e) {
             throw e.rethrowFromSystemServer();
         }
diff --git a/core/java/android/database/AbstractCursor.java b/core/java/android/database/AbstractCursor.java
index 8bcaa45..b44458a 100644
--- a/core/java/android/database/AbstractCursor.java
+++ b/core/java/android/database/AbstractCursor.java
@@ -16,17 +16,20 @@
 
 package android.database;
 
+import android.annotation.NonNull;
 import android.annotation.UnsupportedAppUsage;
 import android.content.ContentResolver;
 import android.net.Uri;
 import android.os.Build;
 import android.os.Bundle;
-import android.os.UserHandle;
 import android.util.Log;
 
+import com.android.internal.util.Preconditions;
+
 import java.lang.ref.WeakReference;
 import java.util.Arrays;
 import java.util.HashMap;
+import java.util.List;
 import java.util.Map;
 
 
@@ -72,6 +75,7 @@
 
     @UnsupportedAppUsage
     private Uri mNotifyUri;
+    private List<Uri> mNotifyUris;
 
     private final Object mSelfObserverLock = new Object();
     private ContentObserver mSelfObserver;
@@ -155,7 +159,11 @@
     @Override
     public boolean requery() {
         if (mSelfObserver != null && mSelfObserverRegistered == false) {
-            mContentResolver.registerContentObserver(mNotifyUri, true, mSelfObserver);
+            final int size = mNotifyUris.size();
+            for (int i = 0; i < size; ++i) {
+                final Uri notifyUri = mNotifyUris.get(i);
+                mContentResolver.registerContentObserver(notifyUri, true, mSelfObserver);
+            }
             mSelfObserverRegistered = true;
         }
         mDataSetObservable.notifyChanged();
@@ -384,8 +392,12 @@
     protected void onChange(boolean selfChange) {
         synchronized (mSelfObserverLock) {
             mContentObservable.dispatchChange(selfChange, null);
-            if (mNotifyUri != null && selfChange) {
-                mContentResolver.notifyChange(mNotifyUri, mSelfObserver);
+            if (mNotifyUris != null && selfChange) {
+                final int size = mNotifyUris.size();
+                for (int i = 0; i < size; ++i) {
+                    final Uri notifyUri = mNotifyUris.get(i);
+                    mContentResolver.notifyChange(notifyUri, mSelfObserver);
+                }
             }
         }
     }
@@ -399,19 +411,33 @@
      */
     @Override
     public void setNotificationUri(ContentResolver cr, Uri notifyUri) {
-        setNotificationUri(cr, notifyUri, cr.getUserId());
+        setNotificationUris(cr, Arrays.asList(notifyUri));
+    }
+
+    @Override
+    public void setNotificationUris(@NonNull ContentResolver cr, @NonNull List<Uri> notifyUris) {
+        Preconditions.checkNotNull(cr);
+        Preconditions.checkNotNull(notifyUris);
+
+        setNotificationUris(cr, notifyUris, cr.getUserId());
     }
 
     /** @hide - set the notification uri but with an observer for a particular user's view */
-    public void setNotificationUri(ContentResolver cr, Uri notifyUri, int userHandle) {
+    public void setNotificationUris(ContentResolver cr, List<Uri> notifyUris, int userHandle) {
         synchronized (mSelfObserverLock) {
-            mNotifyUri = notifyUri;
+            mNotifyUris = notifyUris;
+            mNotifyUri = mNotifyUris.get(0);
             mContentResolver = cr;
             if (mSelfObserver != null) {
                 mContentResolver.unregisterContentObserver(mSelfObserver);
             }
             mSelfObserver = new SelfContentObserver(this);
-            mContentResolver.registerContentObserver(mNotifyUri, true, mSelfObserver, userHandle);
+            final int size = mNotifyUris.size();
+            for (int i = 0; i < size; ++i) {
+                final Uri notifyUri = mNotifyUris.get(i);
+                mContentResolver.registerContentObserver(
+                        notifyUri, true, mSelfObserver, userHandle);
+            }
             mSelfObserverRegistered = true;
         }
     }
@@ -424,6 +450,13 @@
     }
 
     @Override
+    public List<Uri> getNotificationUris() {
+        synchronized (mSelfObserverLock) {
+            return mNotifyUris;
+        }
+    }
+
+    @Override
     public boolean getWantsAllOnMoveCalls() {
         return false;
     }
diff --git a/core/java/android/database/Cursor.java b/core/java/android/database/Cursor.java
index 5a4280e..1379138 100644
--- a/core/java/android/database/Cursor.java
+++ b/core/java/android/database/Cursor.java
@@ -16,11 +16,14 @@
 
 package android.database;
 
+import android.annotation.NonNull;
 import android.content.ContentResolver;
 import android.net.Uri;
 import android.os.Bundle;
 
 import java.io.Closeable;
+import java.util.Arrays;
+import java.util.List;
 
 /**
  * This interface provides random read-write access to the result set returned
@@ -421,7 +424,10 @@
     /**
      * Register to watch a content URI for changes. This can be the URI of a specific data row (for 
      * example, "content://my_provider_type/23"), or a a generic URI for a content type.
-     * 
+     *
+     * <p>Calling this overrides any previous call to
+     * {@link #setNotificationUris(ContentResolver, List)}.
+     *
      * @param cr The content resolver from the caller's context. The listener attached to 
      * this resolver will be notified.
      * @param uri The content URI to watch.
@@ -429,6 +435,24 @@
     void setNotificationUri(ContentResolver cr, Uri uri);
 
     /**
+     * Similar to {@link #setNotificationUri(ContentResolver, Uri)}, except this version allows
+     * to watch multiple content URIs for changes.
+     *
+     * <p>If this is not implemented, this is equivalent to calling
+     * {@link #setNotificationUri(ContentResolver, Uri)} with the first URI in {@code uris}.
+     *
+     * <p>Calling this overrides any previous call to
+     * {@link #setNotificationUri(ContentResolver, Uri)}.
+     *
+     * @param cr The content resolver from the caller's context. The listener attached to
+     * this resolver will be notified.
+     * @param uris The content URIs to watch.
+     */
+    default void setNotificationUris(@NonNull ContentResolver cr, @NonNull List<Uri> uris) {
+        setNotificationUri(cr, uris.get(0));
+    }
+
+    /**
      * Return the URI at which notifications of changes in this Cursor's data
      * will be delivered, as previously set by {@link #setNotificationUri}.
      * @return Returns a URI that can be used with
@@ -439,6 +463,22 @@
     Uri getNotificationUri();
 
     /**
+     * Return the URIs at which notifications of changes in this Cursor's data
+     * will be delivered, as previously set by {@link #setNotificationUris}.
+     *
+     * <p>If this is not implemented, this is equivalent to calling {@link #getNotificationUri()}.
+     *
+     * @return Returns URIs that can be used with
+     * {@link ContentResolver#registerContentObserver(android.net.Uri, boolean, ContentObserver)
+     * ContentResolver.registerContentObserver} to find out about changes to this Cursor's
+     * data. May be null if no notification URI has been set.
+     */
+    default List<Uri> getNotificationUris() {
+        final Uri notifyUri = getNotificationUri();
+        return notifyUri == null ? null : Arrays.asList(notifyUri);
+    }
+
+    /**
      * onMove() will only be called across processes if this method returns true.
      * @return whether all cursor movement should result in a call to onMove().
      */
diff --git a/core/java/android/database/CursorWrapper.java b/core/java/android/database/CursorWrapper.java
index 0d27dfb..c9cafaf 100644
--- a/core/java/android/database/CursorWrapper.java
+++ b/core/java/android/database/CursorWrapper.java
@@ -21,6 +21,8 @@
 import android.net.Uri;
 import android.os.Bundle;
 
+import java.util.List;
+
 /**
  * Wrapper class for Cursor that delegates all calls to the actual cursor object.  The primary
  * use for this class is to extend a cursor while overriding only a subset of its methods.
@@ -241,11 +243,21 @@
     }
 
     @Override
+    public void setNotificationUris(ContentResolver cr, List<Uri> uris) {
+        mCursor.setNotificationUris(cr, uris);
+    }
+
+    @Override
     public Uri getNotificationUri() {
         return mCursor.getNotificationUri();
     }
 
     @Override
+    public List<Uri> getNotificationUris() {
+        return mCursor.getNotificationUris();
+    }
+
+    @Override
     public void unregisterContentObserver(ContentObserver observer) {
         mCursor.unregisterContentObserver(observer);
     }
diff --git a/core/java/android/hardware/biometrics/BiometricFaceConstants.java b/core/java/android/hardware/biometrics/BiometricFaceConstants.java
index 9d37d99..209afb8 100644
--- a/core/java/android/hardware/biometrics/BiometricFaceConstants.java
+++ b/core/java/android/hardware/biometrics/BiometricFaceConstants.java
@@ -252,12 +252,55 @@
     public static final int FACE_ACQUIRED_TOO_SIMILAR = 15;
 
     /**
+     * The magnitude of the pan angle of the user’s face with respect to the sensor’s
+     * capture plane is too high.
+     *
+     * The pan angle is defined as the angle swept out by the user’s face turning
+     * their neck left and right. The pan angle would be zero if the user faced the
+     * camera directly.
+     *
+     * The user should be informed to look more directly at the camera.
+     */
+    public static final int FACE_ACQUIRED_PAN_TOO_EXTREME = 16;
+
+    /**
+     * The magnitude of the tilt angle of the user’s face with respect to the sensor’s
+     * capture plane is too high.
+     *
+     * The tilt angle is defined as the angle swept out by the user’s face looking up
+     * and down. The pan angle would be zero if the user faced the camera directly.
+     *
+     * The user should be informed to look more directly at the camera.
+     */
+    public static final int FACE_ACQUIRED_TILT_TOO_EXTREME = 17;
+
+    /**
+     * The magnitude of the roll angle of the user’s face with respect to the sensor’s
+     * capture plane is too high.
+     *
+     * The roll angle is defined as the angle swept out by the user’s face tilting their head
+     * towards their shoulders to the left and right. The pan angle would be zero if the user
+     * faced the camera directly.
+     *
+     * The user should be informed to look more directly at the camera.
+     */
+    public static final int FACE_ACQUIRED_ROLL_TOO_EXTREME = 18;
+
+    /**
+     * The user’s face has been obscured by some object.
+     *
+     * The user should be informed to remove any objects from the line of sight from
+     * the sensor to the user’s face.
+     */
+    public static final int FACE_ACQUIRED_FACE_OBSCURED = 19;
+
+    /**
      * Hardware vendors may extend this list if there are conditions that do not fall under one of
      * the above categories. Vendors are responsible for providing error strings for these errors.
      *
      * @hide
      */
-    public static final int FACE_ACQUIRED_VENDOR = 16;
+    public static final int FACE_ACQUIRED_VENDOR = 20;
 
     /**
      * @hide
diff --git a/core/java/android/hardware/biometrics/BiometricManager.java b/core/java/android/hardware/biometrics/BiometricManager.java
index 8aac1bf..5afe1a9 100644
--- a/core/java/android/hardware/biometrics/BiometricManager.java
+++ b/core/java/android/hardware/biometrics/BiometricManager.java
@@ -171,5 +171,39 @@
             Slog.w(TAG, "resetTimeout(): Service not connected");
         }
     }
+
+    /**
+     * TODO(b/123378871): Remove when moved.
+     * @hide
+     */
+    @RequiresPermission(USE_BIOMETRIC_INTERNAL)
+    public void onConfirmDeviceCredentialSuccess() {
+        if (mService != null) {
+            try {
+                mService.onConfirmDeviceCredentialSuccess();
+            } catch (RemoteException e) {
+                throw e.rethrowFromSystemServer();
+            }
+        } else {
+            Slog.w(TAG, "onConfirmDeviceCredentialSuccess(): Service not connected");
+        }
+    }
+
+    /**
+     * TODO(b/123378871): Remove when moved.
+     * @hide
+     */
+    @RequiresPermission(USE_BIOMETRIC_INTERNAL)
+    public void onConfirmDeviceCredentialError(int error, String message) {
+        if (mService != null) {
+            try {
+                mService.onConfirmDeviceCredentialError(error, message);
+            } catch (RemoteException e) {
+                throw e.rethrowFromSystemServer();
+            }
+        } else {
+            Slog.w(TAG, "onConfirmDeviceCredentialError(): Service not connected");
+        }
+    }
 }
 
diff --git a/core/java/android/hardware/biometrics/BiometricPrompt.java b/core/java/android/hardware/biometrics/BiometricPrompt.java
index c69b68e4..ec62aba 100644
--- a/core/java/android/hardware/biometrics/BiometricPrompt.java
+++ b/core/java/android/hardware/biometrics/BiometricPrompt.java
@@ -77,6 +77,10 @@
      * @hide
      */
     public static final String KEY_REQUIRE_CONFIRMATION = "require_confirmation";
+    /**
+     * @hide
+     */
+    public static final String KEY_ENABLE_FALLBACK = "enable_fallback";
 
     /**
      * Error/help message will show for this amount of time.
@@ -242,6 +246,18 @@
         }
 
         /**
+         * The user will first be prompted to authenticate with biometrics, but also given the
+         * option to authenticate with their device PIN, pattern, or password.
+         * @param enable When true, the prompt will fall back to ask for the user's device
+         *               credentials (PIN, pattern, or password).
+         * @return
+         */
+        public Builder setEnableFallback(boolean enable) {
+            mBundle.putBoolean(KEY_ENABLE_FALLBACK, enable);
+            return this;
+        }
+
+        /**
          * Creates a {@link BiometricPrompt}.
          * @return a {@link BiometricPrompt}
          * @throws IllegalArgumentException if any of the required fields are not set.
@@ -250,11 +266,15 @@
             final CharSequence title = mBundle.getCharSequence(KEY_TITLE);
             final CharSequence negative = mBundle.getCharSequence(KEY_NEGATIVE_TEXT);
             final boolean useDefaultTitle = mBundle.getBoolean(KEY_USE_DEFAULT_TITLE);
+            final boolean enableFallback = mBundle.getBoolean(KEY_ENABLE_FALLBACK);
 
             if (TextUtils.isEmpty(title) && !useDefaultTitle) {
                 throw new IllegalArgumentException("Title must be set and non-empty");
-            } else if (TextUtils.isEmpty(negative)) {
+            } else if (TextUtils.isEmpty(negative) && !enableFallback) {
                 throw new IllegalArgumentException("Negative text must be set and non-empty");
+            } else if (!TextUtils.isEmpty(negative) && enableFallback) {
+                throw new IllegalArgumentException("Can't have both negative button behavior"
+                        + " and fallback enabled");
             }
             return new BiometricPrompt(mContext, mBundle, mPositiveButtonInfo, mNegativeButtonInfo);
         }
@@ -514,6 +534,9 @@
         if (callback == null) {
             throw new IllegalArgumentException("Must supply a callback");
         }
+        if (mBundle.getBoolean(KEY_ENABLE_FALLBACK)) {
+            throw new IllegalArgumentException("Fallback not supported with crypto");
+        }
         authenticateInternal(crypto, cancel, executor, callback, mContext.getUserId());
     }
 
diff --git a/core/java/android/hardware/biometrics/IBiometricService.aidl b/core/java/android/hardware/biometrics/IBiometricService.aidl
index de828f2..e4336d1 100644
--- a/core/java/android/hardware/biometrics/IBiometricService.aidl
+++ b/core/java/android/hardware/biometrics/IBiometricService.aidl
@@ -51,4 +51,12 @@
 
     // Reset the timeout when user authenticates with strong auth (e.g. PIN, pattern or password)
     void resetTimeout(in byte [] token);
+
+    // TODO(b/123378871): Remove when moved.
+    // CDCA needs to send results to BiometricService if it was invoked using BiometricPrompt's
+    // setEnableFallback method, since there's no way for us to intercept onActivityResult.
+    // CDCA is launched from BiometricService (startActivityAsUser) instead of *ForResult.
+    void onConfirmDeviceCredentialSuccess();
+    // TODO(b/123378871): Remove when moved.
+    void onConfirmDeviceCredentialError(int error, String message);
 }
diff --git a/core/java/android/hardware/camera2/CameraCharacteristics.java b/core/java/android/hardware/camera2/CameraCharacteristics.java
index 105ae68..9758308 100644
--- a/core/java/android/hardware/camera2/CameraCharacteristics.java
+++ b/core/java/android/hardware/camera2/CameraCharacteristics.java
@@ -3722,12 +3722,12 @@
 
     /**
      * <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
-     * can be tokenized using '\0' to generate list of physical camera ids.</p>
-     * <p>For example, if the physical camera ids of the logical camera are "2" and "3", the
+     * <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
+     * can be tokenized using '\0' to generate list of physical camera IDs.</p>
+     * <p>For example, if the physical camera IDs of the logical camera are "2" and "3", the
      * value of this tag will be ['2', '\0', '3', '\0'].</p>
-     * <p>The number of physical camera ids must be no less than 2.</p>
+     * <p>The number of physical camera IDs must be no less than 2.</p>
      * <p><b>Units</b>: UTF-8 null-terminated string</p>
      * <p><b>Optional</b> - This value may be {@code null} on some devices.</p>
      * <p><b>Limited capability</b> -
diff --git a/core/java/android/hardware/camera2/CameraMetadata.java b/core/java/android/hardware/camera2/CameraMetadata.java
index 402472a..6302aa5 100644
--- a/core/java/android/hardware/camera2/CameraMetadata.java
+++ b/core/java/android/hardware/camera2/CameraMetadata.java
@@ -428,6 +428,10 @@
      * <p>If this is supported, {@link CameraCharacteristics#SCALER_STREAM_CONFIGURATION_MAP android.scaler.streamConfigurationMap} will
      * additionally return a min frame duration that is greater than
      * zero for each supported size-format combination.</p>
+     * <p>For camera devices with LOGICAL_MULTI_CAMERA capability, when the underlying active
+     * physical camera switches, exposureTime, sensitivity, and lens properties may change
+     * even if AE/AF is locked. However, the overall auto exposure and auto focus experience
+     * for users will be consistent. Refer to LOGICAL_MULTI_CAMERA capability for details.</p>
      *
      * @see CaptureRequest#BLACK_LEVEL_LOCK
      * @see CaptureRequest#CONTROL_AE_LOCK
@@ -485,6 +489,10 @@
      * will accurately report the values applied by AWB in the result.</p>
      * <p>A given camera device may also support additional post-processing
      * controls, but this capability only covers the above list of controls.</p>
+     * <p>For camera devices with LOGICAL_MULTI_CAMERA capability, when underlying active
+     * physical camera switches, tonemap, white balance, and shading map may change even if
+     * awb is locked. However, the overall post-processing experience for users will be
+     * consistent. Refer to LOGICAL_MULTI_CAMERA capability for details.</p>
      *
      * @see CaptureRequest#COLOR_CORRECTION_ABERRATION_MODE
      * @see CameraCharacteristics#COLOR_CORRECTION_AVAILABLE_ABERRATION_MODES
@@ -847,7 +855,7 @@
      * </li>
      * <li>The SENSOR_INFO_TIMESTAMP_SOURCE of the logical device and physical devices must be
      *   the same.</li>
-     * <li>The logical camera device must be LIMITED or higher device.</li>
+     * <li>The logical camera must be LIMITED or higher device.</li>
      * </ul>
      * <p>Both the logical camera device and its underlying physical devices support the
      * mandatory stream combinations required for their device levels.</p>
@@ -867,13 +875,84 @@
      * <p>Using physical streams in place of a logical stream of the same size and format will
      * not slow down the frame rate of the capture, as long as the minimum frame duration
      * of the physical and logical streams are the same.</p>
+     * <p>A logical camera device's dynamic metadata may contain
+     * {@link CaptureResult#LOGICAL_MULTI_CAMERA_ACTIVE_PHYSICAL_ID android.logicalMultiCamera.activePhysicalId} to notify the application of the current
+     * active physical camera Id. An active physical camera is the physical camera from which
+     * the logical camera's main image data outputs (YUV or RAW) and metadata come from.
+     * In addition, this serves as an indication which physical camera is used to output to
+     * a RAW stream, or in case only physical cameras support RAW, which physical RAW stream
+     * the application should request.</p>
+     * <p>Logical camera's static metadata tags below describe the default active physical
+     * camera. An active physical camera is default if it's used when application directly
+     * uses requests built from a template. All templates will default to the same active
+     * physical camera.</p>
+     * <ul>
+     * <li>{@link CameraCharacteristics#SENSOR_INFO_SENSITIVITY_RANGE android.sensor.info.sensitivityRange}</li>
+     * <li>{@link CameraCharacteristics#SENSOR_INFO_COLOR_FILTER_ARRANGEMENT android.sensor.info.colorFilterArrangement}</li>
+     * <li>{@link CameraCharacteristics#SENSOR_INFO_EXPOSURE_TIME_RANGE android.sensor.info.exposureTimeRange}</li>
+     * <li>{@link CameraCharacteristics#SENSOR_INFO_MAX_FRAME_DURATION android.sensor.info.maxFrameDuration}</li>
+     * <li>{@link CameraCharacteristics#SENSOR_INFO_PHYSICAL_SIZE android.sensor.info.physicalSize}</li>
+     * <li>{@link CameraCharacteristics#SENSOR_INFO_WHITE_LEVEL android.sensor.info.whiteLevel}</li>
+     * <li>{@link CameraCharacteristics#SENSOR_INFO_LENS_SHADING_APPLIED android.sensor.info.lensShadingApplied}</li>
+     * <li>{@link CameraCharacteristics#SENSOR_REFERENCE_ILLUMINANT1 android.sensor.referenceIlluminant1}</li>
+     * <li>{@link CameraCharacteristics#SENSOR_REFERENCE_ILLUMINANT2 android.sensor.referenceIlluminant2}</li>
+     * <li>{@link CameraCharacteristics#SENSOR_CALIBRATION_TRANSFORM1 android.sensor.calibrationTransform1}</li>
+     * <li>{@link CameraCharacteristics#SENSOR_CALIBRATION_TRANSFORM2 android.sensor.calibrationTransform2}</li>
+     * <li>{@link CameraCharacteristics#SENSOR_COLOR_TRANSFORM1 android.sensor.colorTransform1}</li>
+     * <li>{@link CameraCharacteristics#SENSOR_COLOR_TRANSFORM2 android.sensor.colorTransform2}</li>
+     * <li>{@link CameraCharacteristics#SENSOR_FORWARD_MATRIX1 android.sensor.forwardMatrix1}</li>
+     * <li>{@link CameraCharacteristics#SENSOR_FORWARD_MATRIX2 android.sensor.forwardMatrix2}</li>
+     * <li>{@link CameraCharacteristics#SENSOR_BLACK_LEVEL_PATTERN android.sensor.blackLevelPattern}</li>
+     * <li>{@link CameraCharacteristics#SENSOR_MAX_ANALOG_SENSITIVITY android.sensor.maxAnalogSensitivity}</li>
+     * <li>{@link CameraCharacteristics#SENSOR_OPTICAL_BLACK_REGIONS android.sensor.opticalBlackRegions}</li>
+     * <li>{@link CameraCharacteristics#SENSOR_AVAILABLE_TEST_PATTERN_MODES android.sensor.availableTestPatternModes}</li>
+     * <li>{@link CameraCharacteristics#LENS_INFO_HYPERFOCAL_DISTANCE android.lens.info.hyperfocalDistance}</li>
+     * <li>{@link CameraCharacteristics#LENS_INFO_MINIMUM_FOCUS_DISTANCE android.lens.info.minimumFocusDistance}</li>
+     * <li>{@link CameraCharacteristics#LENS_INFO_FOCUS_DISTANCE_CALIBRATION android.lens.info.focusDistanceCalibration}</li>
+     * <li>{@link CameraCharacteristics#LENS_POSE_ROTATION android.lens.poseRotation}</li>
+     * <li>{@link CameraCharacteristics#LENS_POSE_TRANSLATION android.lens.poseTranslation}</li>
+     * <li>{@link CameraCharacteristics#LENS_INTRINSIC_CALIBRATION android.lens.intrinsicCalibration}</li>
+     * <li>{@link CameraCharacteristics#LENS_POSE_REFERENCE android.lens.poseReference}</li>
+     * <li>{@link CameraCharacteristics#LENS_DISTORTION android.lens.distortion}</li>
+     * </ul>
+     * <p>To maintain backward compatibility, the capture request and result metadata tags
+     * required for basic camera functionalities will be solely based on the
+     * logical camera capabiltity. Other request and result metadata tags, on the other
+     * hand, will be based on current active physical camera. For example, the physical
+     * cameras' sensor sensitivity and lens capability could be different from each other.
+     * So when the application manually controls sensor exposure time/gain, or does manual
+     * focus control, it must checks the current active physical camera's exposure, gain,
+     * and focus distance range.</p>
      *
      * @see CameraCharacteristics#LENS_DISTORTION
+     * @see CameraCharacteristics#LENS_INFO_FOCUS_DISTANCE_CALIBRATION
+     * @see CameraCharacteristics#LENS_INFO_HYPERFOCAL_DISTANCE
+     * @see CameraCharacteristics#LENS_INFO_MINIMUM_FOCUS_DISTANCE
      * @see CameraCharacteristics#LENS_INTRINSIC_CALIBRATION
      * @see CameraCharacteristics#LENS_POSE_REFERENCE
      * @see CameraCharacteristics#LENS_POSE_ROTATION
      * @see CameraCharacteristics#LENS_POSE_TRANSLATION
+     * @see CaptureResult#LOGICAL_MULTI_CAMERA_ACTIVE_PHYSICAL_ID
      * @see CameraCharacteristics#LOGICAL_MULTI_CAMERA_SENSOR_SYNC_TYPE
+     * @see CameraCharacteristics#SENSOR_AVAILABLE_TEST_PATTERN_MODES
+     * @see CameraCharacteristics#SENSOR_BLACK_LEVEL_PATTERN
+     * @see CameraCharacteristics#SENSOR_CALIBRATION_TRANSFORM1
+     * @see CameraCharacteristics#SENSOR_CALIBRATION_TRANSFORM2
+     * @see CameraCharacteristics#SENSOR_COLOR_TRANSFORM1
+     * @see CameraCharacteristics#SENSOR_COLOR_TRANSFORM2
+     * @see CameraCharacteristics#SENSOR_FORWARD_MATRIX1
+     * @see CameraCharacteristics#SENSOR_FORWARD_MATRIX2
+     * @see CameraCharacteristics#SENSOR_INFO_COLOR_FILTER_ARRANGEMENT
+     * @see CameraCharacteristics#SENSOR_INFO_EXPOSURE_TIME_RANGE
+     * @see CameraCharacteristics#SENSOR_INFO_LENS_SHADING_APPLIED
+     * @see CameraCharacteristics#SENSOR_INFO_MAX_FRAME_DURATION
+     * @see CameraCharacteristics#SENSOR_INFO_PHYSICAL_SIZE
+     * @see CameraCharacteristics#SENSOR_INFO_SENSITIVITY_RANGE
+     * @see CameraCharacteristics#SENSOR_INFO_WHITE_LEVEL
+     * @see CameraCharacteristics#SENSOR_MAX_ANALOG_SENSITIVITY
+     * @see CameraCharacteristics#SENSOR_OPTICAL_BLACK_REGIONS
+     * @see CameraCharacteristics#SENSOR_REFERENCE_ILLUMINANT1
+     * @see CameraCharacteristics#SENSOR_REFERENCE_ILLUMINANT2
      * @see CameraCharacteristics#REQUEST_AVAILABLE_CAPABILITIES
      */
     public static final int REQUEST_AVAILABLE_CAPABILITIES_LOGICAL_MULTI_CAMERA = 11;
diff --git a/core/java/android/hardware/camera2/CaptureRequest.java b/core/java/android/hardware/camera2/CaptureRequest.java
index 8ebaf2f..5f05cfb 100644
--- a/core/java/android/hardware/camera2/CaptureRequest.java
+++ b/core/java/android/hardware/camera2/CaptureRequest.java
@@ -2146,7 +2146,6 @@
     /**
      * <p>32 characters describing GPS algorithm to
      * include in EXIF.</p>
-     * <p><b>Units</b>: UTF-8 null-terminated string</p>
      * <p>This key is available on all devices.</p>
      * @hide
      */
diff --git a/core/java/android/hardware/camera2/CaptureResult.java b/core/java/android/hardware/camera2/CaptureResult.java
index 3d70c51..585c597 100644
--- a/core/java/android/hardware/camera2/CaptureResult.java
+++ b/core/java/android/hardware/camera2/CaptureResult.java
@@ -2470,7 +2470,6 @@
     /**
      * <p>32 characters describing GPS algorithm to
      * include in EXIF.</p>
-     * <p><b>Units</b>: UTF-8 null-terminated string</p>
      * <p>This key is available on all devices.</p>
      * @hide
      */
@@ -4638,6 +4637,23 @@
             new Key<Float>("android.reprocess.effectiveExposureFactor", float.class);
 
     /**
+     * <p>String containing the ID of the underlying active physical camera.</p>
+     * <p>The ID of the active physical camera that's backing the logical camera. All camera
+     * streams and metadata that are not physical camera specific will be originating from this
+     * physical camera. This must be one of valid physical IDs advertised in the physicalIds
+     * static tag.</p>
+     * <p>For a logical camera made up of physical cameras where each camera's lenses have
+     * different characteristics, the camera device may choose to switch between the physical
+     * cameras when application changes FOCAL_LENGTH or SCALER_CROP_REGION.
+     * At the time of lens switch, this result metadata reflects the new active physical camera
+     * ID.</p>
+     * <p><b>Optional</b> - This value may be {@code null} on some devices.</p>
+     */
+    @PublicKey
+    public static final Key<String> LOGICAL_MULTI_CAMERA_ACTIVE_PHYSICAL_ID =
+            new Key<String>("android.logicalMultiCamera.activePhysicalId", String.class);
+
+    /**
      * <p>Mode of operation for the lens distortion correction block.</p>
      * <p>The lens distortion correction block attempts to improve image quality by fixing
      * radial, tangential, or other geometric aberrations in the camera device's optics.  If
diff --git a/core/java/android/hardware/display/ColorDisplayManager.java b/core/java/android/hardware/display/ColorDisplayManager.java
index fa335c8b..27f0b04 100644
--- a/core/java/android/hardware/display/ColorDisplayManager.java
+++ b/core/java/android/hardware/display/ColorDisplayManager.java
@@ -23,16 +23,22 @@
 import android.annotation.RequiresPermission;
 import android.annotation.SystemApi;
 import android.annotation.SystemService;
+import android.content.ContentResolver;
 import android.content.Context;
+import android.metrics.LogMaker;
 import android.os.IBinder;
 import android.os.RemoteException;
 import android.os.ServiceManager;
 import android.os.ServiceManager.ServiceNotFoundException;
+import android.provider.Settings.Secure;
 
 import com.android.internal.R;
+import com.android.internal.logging.MetricsLogger;
+import com.android.internal.logging.nano.MetricsProto.MetricsEvent;
 
 import java.lang.annotation.Retention;
 import java.lang.annotation.RetentionPolicy;
+import java.time.LocalTime;
 
 /**
  * Manages the display's color transforms and modes.
@@ -81,7 +87,82 @@
     @SystemApi
     public static final int CAPABILITY_HARDWARE_ACCELERATION_PER_APP = 0x4;
 
+    /**
+     * @hide
+     */
+    @Retention(RetentionPolicy.SOURCE)
+    @IntDef({ AUTO_MODE_DISABLED, AUTO_MODE_CUSTOM_TIME, AUTO_MODE_TWILIGHT })
+    public @interface AutoMode {}
+
+    /**
+     * Auto mode value to prevent Night display from being automatically activated. It can still
+     * be activated manually via {@link #setNightDisplayActivated(boolean)}.
+     *
+     * @see #setNightDisplayAutoMode(int)
+     *
+     * @hide
+     */
+    @SystemApi
+    public static final int AUTO_MODE_DISABLED = 0;
+    /**
+     * Auto mode value to automatically activate Night display at a specific start and end time.
+     *
+     * @see #setNightDisplayAutoMode(int)
+     * @see #setNightDisplayCustomStartTime(LocalTime)
+     * @see #setNightDisplayCustomEndTime(LocalTime)
+     *
+     * @hide
+     */
+    @SystemApi
+    public static final int AUTO_MODE_CUSTOM_TIME = 1;
+    /**
+     * Auto mode value to automatically activate Night display from sunset to sunrise.
+     *
+     * @see #setNightDisplayAutoMode(int)
+     *
+     * @hide
+     */
+    @SystemApi
+    public static final int AUTO_MODE_TWILIGHT = 2;
+
+    /**
+     * @hide
+     */
+    @Retention(RetentionPolicy.SOURCE)
+    @IntDef({COLOR_MODE_NATURAL, COLOR_MODE_BOOSTED, COLOR_MODE_SATURATED, COLOR_MODE_AUTOMATIC})
+    public @interface ColorMode {}
+
+    /**
+     * Color mode with natural colors.
+     *
+     * @hide
+     * @see #setColorMode(int)
+     */
+    public static final int COLOR_MODE_NATURAL = 0;
+    /**
+     * Color mode with boosted colors.
+     *
+     * @hide
+     * @see #setColorMode(int)
+     */
+    public static final int COLOR_MODE_BOOSTED = 1;
+    /**
+     * Color mode with saturated colors.
+     *
+     * @hide
+     * @see #setColorMode(int)
+     */
+    public static final int COLOR_MODE_SATURATED = 2;
+    /**
+     * Color mode with automatic colors.
+     *
+     * @hide
+     * @see #setColorMode(int)
+     */
+    public static final int COLOR_MODE_AUTOMATIC = 3;
+
     private final ColorDisplayManagerInternal mManager;
+    private MetricsLogger mMetricsLogger;
 
     /**
      * @hide
@@ -91,6 +172,176 @@
     }
 
     /**
+     * (De)activates the night display transform.
+     *
+     * @hide
+     */
+    @RequiresPermission(android.Manifest.permission.CONTROL_DISPLAY_COLOR_TRANSFORMS)
+    public boolean setNightDisplayActivated(boolean activated) {
+        return mManager.setNightDisplayActivated(activated);
+    }
+
+    /**
+     * Returns whether the night display transform is currently active.
+     *
+     * @hide
+     */
+    public boolean isNightDisplayActivated() {
+        return mManager.isNightDisplayActivated();
+    }
+
+    /**
+     * Sets the color temperature of the night display transform.
+     *
+     * @hide
+     */
+    @RequiresPermission(android.Manifest.permission.CONTROL_DISPLAY_COLOR_TRANSFORMS)
+    public boolean setNightDisplayColorTemperature(int temperature) {
+        return mManager.setNightDisplayColorTemperature(temperature);
+    }
+
+    /**
+     * Gets the color temperature of the night display transform.
+     *
+     * @hide
+     */
+    public int getNightDisplayColorTemperature() {
+        return mManager.getNightDisplayColorTemperature();
+    }
+
+    /**
+     * Returns the current auto mode value controlling when Night display will be automatically
+     * activated. One of {@link #AUTO_MODE_DISABLED}, {@link #AUTO_MODE_CUSTOM_TIME}, or
+     * {@link #AUTO_MODE_TWILIGHT}.
+     *
+     * @hide
+     */
+    @SystemApi
+    @RequiresPermission(Manifest.permission.CONTROL_DISPLAY_COLOR_TRANSFORMS)
+    public @AutoMode int getNightDisplayAutoMode() {
+        return mManager.getNightDisplayAutoMode();
+    }
+
+    /**
+     * Returns the current auto mode value, without validation, or {@code 1} if the auto mode has
+     * never been set.
+     *
+     * @hide
+     */
+    public int getNightDisplayAutoModeRaw() {
+        return mManager.getNightDisplayAutoModeRaw();
+    }
+
+    /**
+     * Sets the current auto mode value controlling when Night display will be automatically
+     * activated. One of {@link #AUTO_MODE_DISABLED}, {@link #AUTO_MODE_CUSTOM_TIME}, or
+     * {@link #AUTO_MODE_TWILIGHT}.
+     *
+     * @param autoMode the new auto mode to use
+     * @return {@code true} if new auto mode was set successfully
+     *
+     * @hide
+     */
+    @SystemApi
+    @RequiresPermission(Manifest.permission.CONTROL_DISPLAY_COLOR_TRANSFORMS)
+    public boolean setNightDisplayAutoMode(@AutoMode int autoMode) {
+        if (autoMode != AUTO_MODE_DISABLED
+                && autoMode != AUTO_MODE_CUSTOM_TIME
+                && autoMode != AUTO_MODE_TWILIGHT) {
+            throw new IllegalArgumentException("Invalid autoMode: " + autoMode);
+        }
+        if (mManager.getNightDisplayAutoMode() != autoMode) {
+            getMetricsLogger().write(new LogMaker(
+                    MetricsEvent.ACTION_NIGHT_DISPLAY_AUTO_MODE_CHANGED)
+                    .setType(MetricsEvent.TYPE_ACTION)
+                    .setSubtype(autoMode));
+        }
+        return mManager.setNightDisplayAutoMode(autoMode);
+    }
+
+    /**
+     * Returns the local time when Night display will be automatically activated when using
+     * {@link ColorDisplayManager#AUTO_MODE_CUSTOM_TIME}.
+     *
+     * @hide
+     */
+    public @NonNull LocalTime getNightDisplayCustomStartTime() {
+        return mManager.getNightDisplayCustomStartTime().getLocalTime();
+    }
+
+    /**
+     * Sets the local time when Night display will be automatically activated when using
+     * {@link ColorDisplayManager#AUTO_MODE_CUSTOM_TIME}.
+     *
+     * @param startTime the local time to automatically activate Night display
+     * @return {@code true} if the new custom start time was set successfully
+     *
+     * @hide
+     */
+    @SystemApi
+    @RequiresPermission(Manifest.permission.CONTROL_DISPLAY_COLOR_TRANSFORMS)
+    public boolean setNightDisplayCustomStartTime(@NonNull LocalTime startTime) {
+        if (startTime == null) {
+            throw new IllegalArgumentException("startTime cannot be null");
+        }
+        getMetricsLogger().write(new LogMaker(
+                MetricsEvent.ACTION_NIGHT_DISPLAY_AUTO_MODE_CUSTOM_TIME_CHANGED)
+                .setType(MetricsEvent.TYPE_ACTION)
+                .setSubtype(0));
+        return mManager.setNightDisplayCustomStartTime(new Time(startTime));
+    }
+
+    /**
+     * Returns the local time when Night display will be automatically deactivated when using
+     * {@link #AUTO_MODE_CUSTOM_TIME}.
+     *
+     * @hide
+     */
+    public @NonNull LocalTime getNightDisplayCustomEndTime() {
+        return mManager.getNightDisplayCustomEndTime().getLocalTime();
+    }
+
+    /**
+     * Sets the local time when Night display will be automatically deactivated when using
+     * {@link #AUTO_MODE_CUSTOM_TIME}.
+     *
+     * @param endTime the local time to automatically deactivate Night display
+     * @return {@code true} if the new custom end time was set successfully
+     *
+     * @hide
+     */
+    @SystemApi
+    @RequiresPermission(Manifest.permission.CONTROL_DISPLAY_COLOR_TRANSFORMS)
+    public boolean setNightDisplayCustomEndTime(@NonNull LocalTime endTime) {
+        if (endTime == null) {
+            throw new IllegalArgumentException("endTime cannot be null");
+        }
+        getMetricsLogger().write(new LogMaker(
+                MetricsEvent.ACTION_NIGHT_DISPLAY_AUTO_MODE_CUSTOM_TIME_CHANGED)
+                .setType(MetricsEvent.TYPE_ACTION)
+                .setSubtype(1));
+        return mManager.setNightDisplayCustomEndTime(new Time(endTime));
+    }
+
+    /**
+     * Sets the current display color mode.
+     *
+     * @hide
+     */
+    public void setColorMode(int colorMode) {
+        mManager.setColorMode(colorMode);
+    }
+
+    /**
+     * Gets the current display color mode.
+     *
+     * @hide
+     */
+    public int getColorMode() {
+        return mManager.getColorMode();
+    }
+
+    /**
      * Returns whether the device has a wide color gamut display.
      *
      * @hide
@@ -138,6 +389,28 @@
     }
 
     /**
+     * Returns the minimum allowed color temperature (in Kelvin) to tint the display when
+     * activated.
+     *
+     * @hide
+     */
+    public static int getMinimumColorTemperature(Context context) {
+        return context.getResources()
+                .getInteger(R.integer.config_nightDisplayColorTemperatureMin);
+    }
+
+    /**
+     * Returns the maximum allowed color temperature (in Kelvin) to tint the display when
+     * activated.
+     *
+     * @hide
+     */
+    public static int getMaximumColorTemperature(Context context) {
+        return context.getResources()
+                .getInteger(R.integer.config_nightDisplayColorTemperatureMax);
+    }
+
+    /**
      * Returns {@code true} if display white balance is supported by the device.
      *
      * @hide
@@ -167,6 +440,25 @@
         return mManager.getTransformCapabilities();
     }
 
+    /**
+     * Returns whether accessibility transforms are currently enabled, which determines whether
+     * color modes are currently configurable for this device.
+     *
+     * @hide
+     */
+    public static boolean areAccessibilityTransformsEnabled(Context context) {
+        final ContentResolver cr = context.getContentResolver();
+        return Secure.getInt(cr, Secure.ACCESSIBILITY_DISPLAY_INVERSION_ENABLED, 0) == 1
+                || Secure.getInt(cr, Secure.ACCESSIBILITY_DISPLAY_DALTONIZER_ENABLED, 0) == 1;
+    }
+
+    private MetricsLogger getMetricsLogger() {
+        if (mMetricsLogger == null) {
+            mMetricsLogger = new MetricsLogger();
+        }
+        return mMetricsLogger;
+    }
+
     private static class ColorDisplayManagerInternal {
 
         private static ColorDisplayManagerInternal sInstance;
@@ -192,6 +484,94 @@
             }
         }
 
+        boolean isNightDisplayActivated() {
+            try {
+                return mCdm.isNightDisplayActivated();
+            } catch (RemoteException e) {
+                throw e.rethrowFromSystemServer();
+            }
+        }
+
+        boolean setNightDisplayActivated(boolean activated) {
+            try {
+                return mCdm.setNightDisplayActivated(activated);
+            } catch (RemoteException e) {
+                throw e.rethrowFromSystemServer();
+            }
+        }
+
+        int getNightDisplayColorTemperature() {
+            try {
+                return mCdm.getNightDisplayColorTemperature();
+            } catch (RemoteException e) {
+                throw e.rethrowFromSystemServer();
+            }
+        }
+
+        boolean setNightDisplayColorTemperature(int temperature) {
+            try {
+                return mCdm.setNightDisplayColorTemperature(temperature);
+            } catch (RemoteException e) {
+                throw e.rethrowFromSystemServer();
+            }
+        }
+
+        int getNightDisplayAutoMode() {
+            try {
+                return mCdm.getNightDisplayAutoMode();
+            } catch (RemoteException e) {
+                throw e.rethrowFromSystemServer();
+            }
+        }
+
+        int getNightDisplayAutoModeRaw() {
+            try {
+                return mCdm.getNightDisplayAutoModeRaw();
+            } catch (RemoteException e) {
+                throw e.rethrowFromSystemServer();
+            }
+        }
+
+        boolean setNightDisplayAutoMode(int autoMode) {
+            try {
+                return mCdm.setNightDisplayAutoMode(autoMode);
+            } catch (RemoteException e) {
+                throw e.rethrowFromSystemServer();
+            }
+        }
+
+        Time getNightDisplayCustomStartTime() {
+            try {
+                return mCdm.getNightDisplayCustomStartTime();
+            } catch (RemoteException e) {
+                throw e.rethrowFromSystemServer();
+            }
+        }
+
+        boolean setNightDisplayCustomStartTime(Time startTime) {
+            try {
+                return mCdm.setNightDisplayCustomStartTime(startTime);
+            } catch (RemoteException e) {
+                throw e.rethrowFromSystemServer();
+            }
+        }
+
+        Time getNightDisplayCustomEndTime() {
+            try {
+                return mCdm.getNightDisplayCustomEndTime();
+            } catch (RemoteException e) {
+                throw e.rethrowFromSystemServer();
+            }
+        }
+
+        boolean setNightDisplayCustomEndTime(Time endTime) {
+            try {
+                return mCdm.setNightDisplayCustomEndTime(endTime);
+            } catch (RemoteException e) {
+                throw e.rethrowFromSystemServer();
+            }
+        }
+
         boolean isDeviceColorManaged() {
             try {
                 return mCdm.isDeviceColorManaged();
@@ -216,6 +596,22 @@
             }
         }
 
+        int getColorMode() {
+            try {
+                return mCdm.getColorMode();
+            } catch (RemoteException e) {
+                throw e.rethrowFromSystemServer();
+            }
+        }
+
+        void setColorMode(int colorMode) {
+            try {
+                mCdm.setColorMode(colorMode);
+            } catch (RemoteException e) {
+                throw e.rethrowFromSystemServer();
+            }
+        }
+
         int getTransformCapabilities() {
             try {
                 return mCdm.getTransformCapabilities();
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/IColorDisplayManager.aidl b/core/java/android/hardware/display/IColorDisplayManager.aidl
index 53cb8db..30e76cf 100644
--- a/core/java/android/hardware/display/IColorDisplayManager.aidl
+++ b/core/java/android/hardware/display/IColorDisplayManager.aidl
@@ -16,6 +16,8 @@
 
 package android.hardware.display;
 
+import android.hardware.display.Time;
+
 /** @hide */
 interface IColorDisplayManager {
     boolean isDeviceColorManaged();
@@ -24,4 +26,19 @@
     boolean setAppSaturationLevel(String packageName, int saturationLevel);
 
     int getTransformCapabilities();
+
+    boolean isNightDisplayActivated();
+    boolean setNightDisplayActivated(boolean activated);
+    int getNightDisplayColorTemperature();
+    boolean setNightDisplayColorTemperature(int temperature);
+    int getNightDisplayAutoMode();
+    int getNightDisplayAutoModeRaw();
+    boolean setNightDisplayAutoMode(int autoMode);
+    Time getNightDisplayCustomStartTime();
+    boolean setNightDisplayCustomStartTime(in Time time);
+    Time getNightDisplayCustomEndTime();
+    boolean setNightDisplayCustomEndTime(in Time time);
+
+    int getColorMode();
+    void setColorMode(int colorMode);
 }
\ No newline at end of file
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/services/net/java/android/net/dhcp/DhcpClient.java b/core/java/android/hardware/display/Time.aidl
similarity index 61%
copy from services/net/java/android/net/dhcp/DhcpClient.java
copy to core/java/android/hardware/display/Time.aidl
index cddb91f..95cb563 100644
--- a/services/net/java/android/net/dhcp/DhcpClient.java
+++ b/core/java/android/hardware/display/Time.aidl
@@ -14,16 +14,6 @@
  * limitations under the License.
  */
 
-package android.net.dhcp;
+package android.hardware.display;
 
-/**
- * TODO: remove this class after migrating clients.
- */
-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;
-}
+parcelable Time;
\ No newline at end of file
diff --git a/core/java/android/hardware/display/Time.java b/core/java/android/hardware/display/Time.java
new file mode 100644
index 0000000..b943ac6
--- /dev/null
+++ b/core/java/android/hardware/display/Time.java
@@ -0,0 +1,77 @@
+/*
+ * Copyright (C) 2019 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.hardware.display;
+
+import android.os.Parcel;
+import android.os.Parcelable;
+
+import java.time.LocalTime;
+
+/**
+ * @hide
+ */
+public final class Time implements Parcelable {
+
+    private final int mHour;
+    private final int mMinute;
+    private final int mSecond;
+    private final int mNano;
+
+    public Time(LocalTime localTime) {
+        mHour = localTime.getHour();
+        mMinute = localTime.getMinute();
+        mSecond = localTime.getSecond();
+        mNano = localTime.getNano();
+    }
+
+    public Time(Parcel parcel) {
+        mHour = parcel.readInt();
+        mMinute = parcel.readInt();
+        mSecond = parcel.readInt();
+        mNano = parcel.readInt();
+    }
+
+    @Override
+    public int describeContents() {
+        return 0;
+    }
+
+    @Override
+    public void writeToParcel(Parcel parcel, int parcelableFlags) {
+        parcel.writeInt(mHour);
+        parcel.writeInt(mMinute);
+        parcel.writeInt(mSecond);
+        parcel.writeInt(mNano);
+    }
+
+    public LocalTime getLocalTime() {
+        return LocalTime.of(mHour, mMinute, mSecond, mNano);
+    }
+
+    public static final Parcelable.Creator<Time> CREATOR = new Parcelable.Creator<Time>() {
+
+        @Override
+        public Time createFromParcel(Parcel source) {
+            return new Time(source);
+        }
+
+        @Override
+        public Time[] newArray(int size) {
+            return new Time[size];
+        }
+    };
+}
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/hardware/usb/IUsbManager.aidl b/core/java/android/hardware/usb/IUsbManager.aidl
index edc3f94..299a00a 100644
--- a/core/java/android/hardware/usb/IUsbManager.aidl
+++ b/core/java/android/hardware/usb/IUsbManager.aidl
@@ -120,6 +120,9 @@
     /* Sets the port's current role. */
     void setPortRoles(in String portId, int powerRole, int dataRole);
 
+    /* Enable/disable contaminant detection */
+    void enableContaminantDetection(in String portId, boolean enable);
+
    /* Sets USB device connection handler. */
    void setUsbDeviceConnectionHandler(in ComponentName usbDeviceConnectionHandler);
 }
diff --git a/core/java/android/hardware/usb/ParcelableUsbPort.java b/core/java/android/hardware/usb/ParcelableUsbPort.java
index 7f7ba96..30388af 100644
--- a/core/java/android/hardware/usb/ParcelableUsbPort.java
+++ b/core/java/android/hardware/usb/ParcelableUsbPort.java
@@ -31,10 +31,21 @@
 public final class ParcelableUsbPort implements Parcelable {
     private final @NonNull String mId;
     private final int mSupportedModes;
+    private final int mSupportedContaminantProtectionModes;
+    private final boolean mSupportsEnableContaminantPresenceProtection;
+    private final boolean mSupportsEnableContaminantPresenceDetection;
 
-    private ParcelableUsbPort(@NonNull String id, int supportedModes) {
+    private ParcelableUsbPort(@NonNull String id, int supportedModes,
+            int supportedContaminantProtectionModes,
+            boolean supportsEnableContaminantPresenceProtection,
+            boolean supportsEnableContaminantPresenceDetection) {
         mId = id;
         mSupportedModes = supportedModes;
+        mSupportedContaminantProtectionModes = supportedContaminantProtectionModes;
+        mSupportsEnableContaminantPresenceProtection =
+                supportsEnableContaminantPresenceProtection;
+        mSupportsEnableContaminantPresenceDetection =
+                supportsEnableContaminantPresenceDetection;
     }
 
     /**
@@ -45,7 +56,10 @@
      * @return The parcelable version of the port
      */
     public static @NonNull ParcelableUsbPort of(@NonNull UsbPort port) {
-        return new ParcelableUsbPort(port.getId(), port.getSupportedModes());
+        return new ParcelableUsbPort(port.getId(), port.getSupportedModes(),
+                port.getSupportedContaminantProtectionModes(),
+                port.supportsEnableContaminantPresenceProtection(),
+                port.supportsEnableContaminantPresenceDetection());
     }
 
     /**
@@ -56,7 +70,9 @@
      * @return The UsbPort for this object
      */
     public @NonNull UsbPort getUsbPort(@NonNull UsbManager usbManager) {
-        return new UsbPort(usbManager, mId, mSupportedModes);
+        return new UsbPort(usbManager, mId, mSupportedModes, mSupportedContaminantProtectionModes,
+                mSupportsEnableContaminantPresenceProtection,
+                mSupportsEnableContaminantPresenceDetection);
     }
 
     @Override
@@ -68,6 +84,9 @@
     public void writeToParcel(Parcel dest, int flags) {
         dest.writeString(mId);
         dest.writeInt(mSupportedModes);
+        dest.writeInt(mSupportedContaminantProtectionModes);
+        dest.writeBoolean(mSupportsEnableContaminantPresenceProtection);
+        dest.writeBoolean(mSupportsEnableContaminantPresenceDetection);
     }
 
     public static final Creator<ParcelableUsbPort> CREATOR =
@@ -76,7 +95,14 @@
                 public ParcelableUsbPort createFromParcel(Parcel in) {
                     String id = in.readString();
                     int supportedModes = in.readInt();
-                    return new ParcelableUsbPort(id, supportedModes);
+                    int supportedContaminantProtectionModes = in.readInt();
+                    boolean supportsEnableContaminantPresenceProtection = in.readBoolean();
+                    boolean supportsEnableContaminantPresenceDetection = in.readBoolean();
+
+                    return new ParcelableUsbPort(id, supportedModes,
+                            supportedContaminantProtectionModes,
+                            supportsEnableContaminantPresenceProtection,
+                            supportsEnableContaminantPresenceDetection);
                 }
 
                 @Override
diff --git a/core/java/android/hardware/usb/UsbManager.java b/core/java/android/hardware/usb/UsbManager.java
index 6014478..eb148b9 100644
--- a/core/java/android/hardware/usb/UsbManager.java
+++ b/core/java/android/hardware/usb/UsbManager.java
@@ -854,6 +854,20 @@
     }
 
     /**
+     * Enables USB port contaminant detection algorithm.
+     *
+     * @hide
+     */
+    @RequiresPermission(Manifest.permission.MANAGE_USB)
+    void enableContaminantDetection(@NonNull UsbPort port, boolean enable) {
+        try {
+            mService.enableContaminantDetection(port.getId(), enable);
+        } catch (RemoteException e) {
+            throw e.rethrowFromSystemServer();
+        }
+    }
+
+    /**
      * Sets the component that will handle USB device connection.
      * <p>
      * Setting component allows to specify external USB host manager to handle use cases, where
diff --git a/core/java/android/hardware/usb/UsbPort.java b/core/java/android/hardware/usb/UsbPort.java
index 37154e4..c674480 100644
--- a/core/java/android/hardware/usb/UsbPort.java
+++ b/core/java/android/hardware/usb/UsbPort.java
@@ -16,6 +16,10 @@
 
 package android.hardware.usb;
 
+import static android.hardware.usb.UsbPortStatus.CONTAMINANT_DETECTION_DETECTED;
+import static android.hardware.usb.UsbPortStatus.CONTAMINANT_DETECTION_DISABLED;
+import static android.hardware.usb.UsbPortStatus.CONTAMINANT_DETECTION_NOT_DETECTED;
+import static android.hardware.usb.UsbPortStatus.CONTAMINANT_DETECTION_NOT_SUPPORTED;
 import static android.hardware.usb.UsbPortStatus.DATA_ROLE_DEVICE;
 import static android.hardware.usb.UsbPortStatus.DATA_ROLE_HOST;
 import static android.hardware.usb.UsbPortStatus.DATA_ROLE_NONE;
@@ -48,16 +52,21 @@
     private final String mId;
     private final int mSupportedModes;
     private final UsbManager mUsbManager;
+    private final int mSupportedContaminantProtectionModes;
+    private final boolean mSupportsEnableContaminantPresenceProtection;
+    private final boolean mSupportsEnableContaminantPresenceDetection;
 
     private static final int NUM_DATA_ROLES = Constants.PortDataRole.NUM_DATA_ROLES;
-
     /**
      * Points to the first power role in the IUsb HAL.
      */
     private static final int POWER_ROLE_OFFSET = Constants.PortPowerRole.NONE;
 
     /** @hide */
-    public UsbPort(@NonNull UsbManager usbManager, @NonNull String id, int supportedModes) {
+    public UsbPort(@NonNull UsbManager usbManager, @NonNull String id, int supportedModes,
+            int supportedContaminantProtectionModes,
+            boolean supportsEnableContaminantPresenceProtection,
+            boolean supportsEnableContaminantPresenceDetection) {
         Preconditions.checkNotNull(id);
         Preconditions.checkFlagsArgument(supportedModes,
                 MODE_DFP | MODE_UFP | MODE_AUDIO_ACCESSORY | MODE_DEBUG_ACCESSORY);
@@ -65,6 +74,11 @@
         mUsbManager = usbManager;
         mId = id;
         mSupportedModes = supportedModes;
+        mSupportedContaminantProtectionModes = supportedContaminantProtectionModes;
+        mSupportsEnableContaminantPresenceProtection =
+                supportsEnableContaminantPresenceProtection;
+        mSupportsEnableContaminantPresenceDetection =
+                supportsEnableContaminantPresenceDetection;
     }
 
     /**
@@ -93,6 +107,36 @@
         return mSupportedModes;
     }
 
+   /**
+     * Gets the supported port proctection modes when the port is contaminated.
+     * <p>
+     * The actual mode of the port is decided by the hardware
+     * </p>
+     *
+     * @hide
+     */
+    public int getSupportedContaminantProtectionModes() {
+        return mSupportedContaminantProtectionModes;
+    }
+
+   /**
+     * Tells if UsbService can enable/disable contaminant presence protection.
+     *
+     * @hide
+     */
+    public boolean supportsEnableContaminantPresenceProtection() {
+        return mSupportsEnableContaminantPresenceProtection;
+    }
+
+   /**
+     * Tells if UsbService can enable/disable contaminant presence detection.
+     *
+     * @hide
+     */
+    public boolean supportsEnableContaminantPresenceDetection() {
+        return mSupportsEnableContaminantPresenceDetection;
+    }
+
     /**
      * Gets the status of this USB port.
      *
@@ -131,6 +175,12 @@
     }
 
     /**
+     * @hide
+     **/
+    public void enableContaminantDetection(boolean enable) {
+        mUsbManager.enableContaminantDetection(this, enable);
+    }
+    /**
      * Combines one power and one data role together into a unique value with
      * exactly one bit set.  This can be used to efficiently determine whether
      * a combination of roles is supported by testing whether that bit is present
@@ -206,6 +256,22 @@
     }
 
     /** @hide */
+    public static String contaminantPresenceStatusToString(int contaminantPresenceStatus) {
+        switch (contaminantPresenceStatus) {
+            case CONTAMINANT_DETECTION_NOT_SUPPORTED:
+                return "not-supported";
+            case CONTAMINANT_DETECTION_DISABLED:
+                return "disabled";
+            case CONTAMINANT_DETECTION_DETECTED:
+                return "detected";
+            case CONTAMINANT_DETECTION_NOT_DETECTED:
+                return "not detected";
+            default:
+                return Integer.toString(contaminantPresenceStatus);
+        }
+    }
+
+    /** @hide */
     public static String roleCombinationsToString(int combo) {
         StringBuilder result = new StringBuilder();
         result.append("[");
@@ -264,6 +330,11 @@
 
     @Override
     public String toString() {
-        return "UsbPort{id=" + mId + ", supportedModes=" + modeToString(mSupportedModes) + "}";
+        return "UsbPort{id=" + mId + ", supportedModes=" + modeToString(mSupportedModes)
+                + "supportedContaminantProtectionModes=" + mSupportedContaminantProtectionModes
+                + "supportsEnableContaminantPresenceProtection="
+                + mSupportsEnableContaminantPresenceProtection
+                + "supportsEnableContaminantPresenceDetection="
+                + mSupportsEnableContaminantPresenceDetection;
     }
 }
diff --git a/core/java/android/hardware/usb/UsbPortStatus.java b/core/java/android/hardware/usb/UsbPortStatus.java
index d30201a..426dba8 100644
--- a/core/java/android/hardware/usb/UsbPortStatus.java
+++ b/core/java/android/hardware/usb/UsbPortStatus.java
@@ -39,6 +39,8 @@
     private final @UsbPowerRole int mCurrentPowerRole;
     private final @UsbDataRole int mCurrentDataRole;
     private final int mSupportedRoleCombinations;
+    private final @ContaminantProtectionStatus int mContaminantProtectionStatus;
+    private final @ContaminantDetectionStatus int mContaminantDetectionStatus;
 
     /**
      * Power role: This USB port does not have a power role.
@@ -131,6 +133,93 @@
     public static final int MODE_DEBUG_ACCESSORY =
             android.hardware.usb.V1_1.Constants.PortMode_1_1.DEBUG_ACCESSORY;
 
+   /**
+     * Contaminant presence detection not supported by the device.
+     * @hide
+     */
+    public static final int CONTAMINANT_DETECTION_NOT_SUPPORTED =
+            android.hardware.usb.V1_2.Constants.ContaminantDetectionStatus.NOT_SUPPORTED;
+
+    /**
+     * Contaminant presence detection supported but disabled.
+     * @hide
+     */
+    public static final int CONTAMINANT_DETECTION_DISABLED =
+            android.hardware.usb.V1_2.Constants.ContaminantDetectionStatus.DISABLED;
+
+    /**
+     * Contaminant presence enabled but not detected.
+     * @hide
+     */
+    public static final int CONTAMINANT_DETECTION_NOT_DETECTED =
+            android.hardware.usb.V1_2.Constants.ContaminantDetectionStatus.NOT_DETECTED;
+
+    /**
+     * Contaminant presence enabled and detected.
+     * @hide
+     */
+    public static final int CONTAMINANT_DETECTION_DETECTED =
+            android.hardware.usb.V1_2.Constants.ContaminantDetectionStatus.DETECTED;
+
+    /**
+     * Contaminant protection - No action performed upon detection of
+     * contaminant presence.
+     * @hide
+     */
+    public static final int CONTAMINANT_PROTECTION_NONE =
+            android.hardware.usb.V1_2.Constants.ContaminantProtectionStatus.NONE;
+
+    /**
+     * Contaminant protection - Port is forced to sink upon detection of
+     * contaminant presence.
+     * @hide
+     */
+    public static final int CONTAMINANT_PROTECTION_SINK =
+            android.hardware.usb.V1_2.Constants.ContaminantProtectionStatus.FORCE_SINK;
+
+    /**
+     * Contaminant protection - Port is forced to source upon detection of
+     * contaminant presence.
+     * @hide
+     */
+    public static final int CONTAMINANT_PROTECTION_SOURCE =
+            android.hardware.usb.V1_2.Constants.ContaminantProtectionStatus.FORCE_SOURCE;
+
+    /**
+     * Contaminant protection - Port is disabled upon detection of
+     * contaminant presence.
+     * @hide
+     */
+    public static final int CONTAMINANT_PROTECTION_FORCE_DISABLE =
+            android.hardware.usb.V1_2.Constants.ContaminantProtectionStatus.FORCE_DISABLE;
+
+    /**
+     * Contaminant protection - Port is disabled upon detection of
+     * contaminant presence.
+     * @hide
+     */
+    public static final int CONTAMINANT_PROTECTION_DISABLED =
+            android.hardware.usb.V1_2.Constants.ContaminantProtectionStatus.DISABLED;
+
+    @IntDef(prefix = { "CONTAMINANT_DETECION_" }, flag = true, value = {
+            CONTAMINANT_DETECTION_NOT_SUPPORTED,
+            CONTAMINANT_DETECTION_DISABLED,
+            CONTAMINANT_DETECTION_NOT_DETECTED,
+            CONTAMINANT_DETECTION_DETECTED,
+    })
+    @Retention(RetentionPolicy.SOURCE)
+    @interface ContaminantDetectionStatus{}
+
+    @IntDef(prefix = { "CONTAMINANT_PROTECTION_" }, flag = true, value = {
+            CONTAMINANT_PROTECTION_NONE,
+            CONTAMINANT_PROTECTION_SINK,
+            CONTAMINANT_PROTECTION_SOURCE,
+            CONTAMINANT_PROTECTION_FORCE_DISABLE,
+            CONTAMINANT_PROTECTION_DISABLED,
+    })
+    @Retention(RetentionPolicy.SOURCE)
+    @interface ContaminantProtectionStatus{}
+
     @IntDef(prefix = { "MODE_" }, flag = true, value = {
             MODE_NONE,
             MODE_DFP,
@@ -142,12 +231,15 @@
     @interface UsbPortMode{}
 
     /** @hide */
-    public UsbPortStatus(int currentMode, @UsbPowerRole int currentPowerRole,
-            @UsbDataRole int currentDataRole, int supportedRoleCombinations) {
+    public UsbPortStatus(int currentMode, int currentPowerRole, int currentDataRole,
+            int supportedRoleCombinations, int contaminantProtectionStatus,
+            int contaminantDetectionStatus) {
         mCurrentMode = currentMode;
         mCurrentPowerRole = currentPowerRole;
         mCurrentDataRole = currentDataRole;
         mSupportedRoleCombinations = supportedRoleCombinations;
+        mContaminantProtectionStatus = contaminantProtectionStatus;
+        mContaminantDetectionStatus = contaminantDetectionStatus;
     }
 
     /**
@@ -212,6 +304,24 @@
         return mSupportedRoleCombinations;
     }
 
+    /**
+     * Returns contaminant detection status.
+     *
+     * @hide
+     */
+    public @ContaminantDetectionStatus int getContaminantDetectionStatus() {
+        return mContaminantDetectionStatus;
+    }
+
+    /**
+     * Returns contamiant protection status.
+     *
+     * @hide
+     */
+    public @ContaminantProtectionStatus int getContaminantProtectionStatus() {
+        return mContaminantProtectionStatus;
+    }
+
     @Override
     public String toString() {
         return "UsbPortStatus{connected=" + isConnected()
@@ -220,6 +330,10 @@
                 + ", currentDataRole=" + UsbPort.dataRoleToString(mCurrentDataRole)
                 + ", supportedRoleCombinations="
                         + UsbPort.roleCombinationsToString(mSupportedRoleCombinations)
+                + ", contaminantDetectionStatus="
+                        + getContaminantDetectionStatus()
+                + ", contaminantProtectionStatus="
+                        + getContaminantProtectionStatus()
                 + "}";
     }
 
@@ -234,6 +348,8 @@
         dest.writeInt(mCurrentPowerRole);
         dest.writeInt(mCurrentDataRole);
         dest.writeInt(mSupportedRoleCombinations);
+        dest.writeInt(mContaminantProtectionStatus);
+        dest.writeInt(mContaminantDetectionStatus);
     }
 
     public static final Parcelable.Creator<UsbPortStatus> CREATOR =
@@ -244,8 +360,11 @@
             int currentPowerRole = in.readInt();
             int currentDataRole = in.readInt();
             int supportedRoleCombinations = in.readInt();
+            int contaminantProtectionStatus = in.readInt();
+            int contaminantDetectionStatus = in.readInt();
             return new UsbPortStatus(currentMode, currentPowerRole, currentDataRole,
-                    supportedRoleCombinations);
+                    supportedRoleCombinations, contaminantProtectionStatus,
+                    contaminantDetectionStatus);
         }
 
         @Override
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 c809cca..5bb24ba 100644
--- a/core/java/android/net/ConnectivityManager.java
+++ b/core/java/android/net/ConnectivityManager.java
@@ -15,6 +15,9 @@
  */
 package android.net;
 
+import static android.net.IpSecManager.INVALID_RESOURCE_ID;
+
+import android.annotation.CallbackExecutor;
 import android.annotation.IntDef;
 import android.annotation.NonNull;
 import android.annotation.Nullable;
@@ -28,6 +31,8 @@
 import android.app.PendingIntent;
 import android.content.Context;
 import android.content.Intent;
+import android.net.IpSecManager.UdpEncapsulationSocket;
+import android.net.SocketKeepalive.Callback;
 import android.os.Binder;
 import android.os.Build;
 import android.os.Build.VERSION_CODES;
@@ -58,6 +63,7 @@
 
 import libcore.net.event.NetworkEventDispatcher;
 
+import java.io.FileDescriptor;
 import java.lang.annotation.Retention;
 import java.lang.annotation.RetentionPolicy;
 import java.net.InetAddress;
@@ -66,6 +72,7 @@
 import java.util.HashMap;
 import java.util.List;
 import java.util.Map;
+import java.util.concurrent.Executor;
 
 /**
  * Class that answers queries about the state of network connectivity. It also
@@ -1699,6 +1706,8 @@
      * {@link PacketKeepaliveCallback#onStopped} if the operation was successful or
      * {@link PacketKeepaliveCallback#onError} if an error occurred.
      *
+     * @deprecated Use {@link SocketKeepalive} instead.
+     *
      * @hide
      */
     public class PacketKeepalive {
@@ -1802,6 +1811,8 @@
     /**
      * Starts an IPsec NAT-T keepalive packet with the specified parameters.
      *
+     * @deprecated Use {@link #createSocketKeepalive} instead.
+     *
      * @hide
      */
     @UnsupportedAppUsage
@@ -1821,6 +1832,62 @@
     }
 
     /**
+     * Request that keepalives be started on a IPsec NAT-T socket.
+     *
+     * @param network The {@link Network} the socket is on.
+     * @param socket The socket that needs to be kept alive.
+     * @param source The source address of the {@link UdpEncapsulationSocket}.
+     * @param destination The destination address of the {@link UdpEncapsulationSocket}.
+     * @param executor The executor on which callback will be invoked. The provided {@link Executor}
+     *                 must run callback sequentially, otherwise the order of callbacks cannot be
+     *                 guaranteed.
+     * @param callback A {@link SocketKeepalive.Callback}. Used for notifications about keepalive
+     *        changes. Must be extended by applications that use this API.
+     *
+     * @return A {@link SocketKeepalive} object, which can be used to control this keepalive object.
+     **/
+    public SocketKeepalive createSocketKeepalive(@NonNull Network network,
+            @NonNull UdpEncapsulationSocket socket,
+            @NonNull InetAddress source,
+            @NonNull InetAddress destination,
+            @NonNull @CallbackExecutor Executor executor,
+            @NonNull Callback callback) {
+        return new NattSocketKeepalive(mService, network, socket.getFileDescriptor(),
+            socket.getResourceId(), source, destination, executor, callback);
+    }
+
+    /**
+     * Request that keepalives be started on a IPsec NAT-T socket file descriptor. Directly called
+     * by system apps which don't use IpSecService to create {@link UdpEncapsulationSocket}.
+     *
+     * @param network The {@link Network} the socket is on.
+     * @param fd The {@link FileDescriptor} that needs to be kept alive. The provided
+     *        {@link FileDescriptor} must be bound to a port and the keepalives will be sent from
+     *        that port.
+     * @param source The source address of the {@link UdpEncapsulationSocket}.
+     * @param destination The destination address of the {@link UdpEncapsulationSocket}. The
+     *        keepalive packets will always be sent to port 4500 of the given {@code destination}.
+     * @param executor The executor on which callback will be invoked. The provided {@link Executor}
+     *                 must run callback sequentially, otherwise the order of callbacks cannot be
+     *                 guaranteed.
+     * @param callback A {@link SocketKeepalive.Callback}. Used for notifications about keepalive
+     *        changes. Must be extended by applications that use this API.
+     *
+     * @hide
+     */
+    @SystemApi
+    @RequiresPermission(android.Manifest.permission.PACKET_KEEPALIVE_OFFLOAD)
+    public SocketKeepalive createNattKeepalive(@NonNull Network network,
+            @NonNull FileDescriptor fd,
+            @NonNull InetAddress source,
+            @NonNull InetAddress destination,
+            @NonNull @CallbackExecutor Executor executor,
+            @NonNull Callback callback) {
+        return new NattSocketKeepalive(mService, network, fd, INVALID_RESOURCE_ID /* Unused */,
+                source, destination, executor, callback);
+    }
+
+    /**
      * Ensure that a network route exists to deliver traffic to the specified
      * host via the specified network interface. An attempt to add a route that
      * already exists is ignored, but treated as successful.
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 131925e..e97060a 100644
--- a/core/java/android/net/IConnectivityManager.aidl
+++ b/core/java/android/net/IConnectivityManager.aidl
@@ -181,6 +181,10 @@
     void startNattKeepalive(in Network network, int intervalSeconds, in Messenger messenger,
             in IBinder binder, String srcAddr, int srcPort, String dstAddr);
 
+    void startNattKeepaliveWithFd(in Network network, in FileDescriptor fd, int resourceId,
+            int intervalSeconds, in Messenger messenger, in IBinder binder, String srcAddr,
+            String dstAddr);
+
     void stopKeepalive(in Network network, int slot);
 
     String getCaptivePortalServerUrl();
diff --git a/core/java/android/net/IpPrefix.java b/core/java/android/net/IpPrefix.java
index 4631c56..b996cda 100644
--- a/core/java/android/net/IpPrefix.java
+++ b/core/java/android/net/IpPrefix.java
@@ -16,6 +16,8 @@
 
 package android.net;
 
+import android.annotation.SystemApi;
+import android.annotation.TestApi;
 import android.os.Parcel;
 import android.os.Parcelable;
 import android.util.Pair;
@@ -83,6 +85,8 @@
      * @param prefixLength the prefix length. Must be &gt;= 0 and &lt;= (32 or 128) (IPv4 or IPv6).
      * @hide
      */
+    @SystemApi
+    @TestApi
     public IpPrefix(InetAddress address, int prefixLength) {
         // We don't reuse the (byte[], int) constructor because it calls clone() on the byte array,
         // which is unnecessary because getAddress() already returns a clone.
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 a536d08..fbd602c 100644
--- a/core/java/android/net/LinkAddress.java
+++ b/core/java/android/net/LinkAddress.java
@@ -162,6 +162,8 @@
      *              {@link OsConstants#RT_SCOPE_LINK} or {@link OsConstants#RT_SCOPE_SITE}).
      * @hide
      */
+    @SystemApi
+    @TestApi
     public LinkAddress(InetAddress address, int prefixLength, int flags, int scope) {
         init(address, prefixLength, flags, scope);
     }
diff --git a/core/java/android/net/LinkProperties.java b/core/java/android/net/LinkProperties.java
index 21b6a8e..6628701 100644
--- a/core/java/android/net/LinkProperties.java
+++ b/core/java/android/net/LinkProperties.java
@@ -174,7 +174,8 @@
     /**
      * @hide
      */
-    @UnsupportedAppUsage
+    @SystemApi
+    @TestApi
     public LinkProperties(LinkProperties source) {
         if (source != null) {
             mIfaceName = source.mIfaceName;
@@ -576,6 +577,8 @@
      * @param addresses The {@link Collection} of PCSCF servers to set in this object.
      * @hide
      */
+    @SystemApi
+    @TestApi
     public void setPcscfServers(Collection<InetAddress> pcscfServers) {
         mPcscfs.clear();
         for (InetAddress pcscfServer: pcscfServers) {
@@ -590,6 +593,8 @@
      *         this link.
      * @hide
      */
+    @SystemApi
+    @TestApi
     public List<InetAddress> getPcscfServers() {
         return Collections.unmodifiableList(mPcscfs);
     }
@@ -781,6 +786,8 @@
      * @return the NAT64 prefix.
      * @hide
      */
+    @SystemApi
+    @TestApi
     public @Nullable IpPrefix getNat64Prefix() {
         return mNat64Prefix;
     }
@@ -794,6 +801,8 @@
      * @param prefix the NAT64 prefix.
      * @hide
      */
+    @SystemApi
+    @TestApi
     public void setNat64Prefix(IpPrefix prefix) {
         if (prefix != null && prefix.getPrefixLength() != 96) {
             throw new IllegalArgumentException("Only 96-bit prefixes are supported: " + prefix);
diff --git a/core/java/android/net/NattSocketKeepalive.java b/core/java/android/net/NattSocketKeepalive.java
new file mode 100644
index 0000000..88631ae
--- /dev/null
+++ b/core/java/android/net/NattSocketKeepalive.java
@@ -0,0 +1,75 @@
+/*
+ * 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.net;
+
+import android.annotation.NonNull;
+import android.os.Binder;
+import android.os.RemoteException;
+import android.util.Log;
+
+import java.io.FileDescriptor;
+import java.net.InetAddress;
+import java.util.concurrent.Executor;
+
+/** @hide */
+public final class NattSocketKeepalive extends SocketKeepalive {
+    /** The NAT-T destination port for IPsec */
+    public static final int NATT_PORT = 4500;
+
+    @NonNull private final InetAddress mSource;
+    @NonNull private final InetAddress mDestination;
+    @NonNull private final FileDescriptor mFd;
+    private final int mResourceId;
+
+    NattSocketKeepalive(@NonNull IConnectivityManager service,
+            @NonNull Network network,
+            @NonNull FileDescriptor fd,
+            int resourceId,
+            @NonNull InetAddress source,
+            @NonNull InetAddress destination,
+            @NonNull Executor executor,
+            @NonNull Callback callback) {
+        super(service, network, executor, callback);
+        mSource = source;
+        mDestination = destination;
+        mFd = fd;
+        mResourceId = resourceId;
+    }
+
+    @Override
+    void startImpl(int intervalSec) {
+        try {
+            mService.startNattKeepaliveWithFd(mNetwork, mFd, mResourceId, intervalSec, mMessenger,
+                    new Binder(), mSource.getHostAddress(), mDestination.getHostAddress());
+        } catch (RemoteException e) {
+            Log.e(TAG, "Error starting packet keepalive: ", e);
+            stopLooper();
+        }
+    }
+
+    @Override
+    void stopImpl() {
+        try {
+            if (mSlot != null) {
+                mService.stopKeepalive(mNetwork, mSlot);
+            }
+        } catch (RemoteException e) {
+            Log.e(TAG, "Error stopping packet keepalive: ", e);
+            stopLooper();
+        }
+    }
+}
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/ProxyInfo.java b/core/java/android/net/ProxyInfo.java
index e926fda..ef2269a 100644
--- a/core/java/android/net/ProxyInfo.java
+++ b/core/java/android/net/ProxyInfo.java
@@ -39,12 +39,12 @@
  */
 public class ProxyInfo implements Parcelable {
 
-    private String mHost;
-    private int mPort;
-    private String mExclusionList;
-    private String[] mParsedExclusionList;
+    private final String mHost;
+    private final int mPort;
+    private final String mExclusionList;
+    private final String[] mParsedExclusionList;
+    private final Uri mPacFileUrl;
 
-    private Uri mPacFileUrl;
     /**
      *@hide
      */
@@ -96,7 +96,8 @@
     public ProxyInfo(String host, int port, String exclList) {
         mHost = host;
         mPort = port;
-        setExclusionList(exclList);
+        mExclusionList = exclList;
+        mParsedExclusionList = parseExclusionList(mExclusionList);
         mPacFileUrl = Uri.EMPTY;
     }
 
@@ -107,7 +108,8 @@
     public ProxyInfo(Uri pacFileUrl) {
         mHost = LOCAL_HOST;
         mPort = LOCAL_PORT;
-        setExclusionList(LOCAL_EXCL_LIST);
+        mExclusionList = LOCAL_EXCL_LIST;
+        mParsedExclusionList = parseExclusionList(mExclusionList);
         if (pacFileUrl == null) {
             throw new NullPointerException();
         }
@@ -121,7 +123,8 @@
     public ProxyInfo(String pacFileUrl) {
         mHost = LOCAL_HOST;
         mPort = LOCAL_PORT;
-        setExclusionList(LOCAL_EXCL_LIST);
+        mExclusionList = LOCAL_EXCL_LIST;
+        mParsedExclusionList = parseExclusionList(mExclusionList);
         mPacFileUrl = Uri.parse(pacFileUrl);
     }
 
@@ -132,13 +135,22 @@
     public ProxyInfo(Uri pacFileUrl, int localProxyPort) {
         mHost = LOCAL_HOST;
         mPort = localProxyPort;
-        setExclusionList(LOCAL_EXCL_LIST);
+        mExclusionList = LOCAL_EXCL_LIST;
+        mParsedExclusionList = parseExclusionList(mExclusionList);
         if (pacFileUrl == null) {
             throw new NullPointerException();
         }
         mPacFileUrl = pacFileUrl;
     }
 
+    private static String[] parseExclusionList(String exclusionList) {
+        if (exclusionList == null) {
+            return new String[0];
+        } else {
+            return exclusionList.toLowerCase(Locale.ROOT).split(",");
+        }
+    }
+
     private ProxyInfo(String host, int port, String exclList, String[] parsedExclList) {
         mHost = host;
         mPort = port;
@@ -159,6 +171,10 @@
             mExclusionList = source.getExclusionListAsString();
             mParsedExclusionList = source.mParsedExclusionList;
         } else {
+            mHost = null;
+            mPort = 0;
+            mExclusionList = null;
+            mParsedExclusionList = null;
             mPacFileUrl = Uri.EMPTY;
         }
     }
@@ -214,24 +230,14 @@
         return mExclusionList;
     }
 
-    // comma separated
-    private void setExclusionList(String exclusionList) {
-        mExclusionList = exclusionList;
-        if (mExclusionList == null) {
-            mParsedExclusionList = new String[0];
-        } else {
-            mParsedExclusionList = exclusionList.toLowerCase(Locale.ROOT).split(",");
-        }
-    }
-
     /**
      * @hide
      */
     public boolean isValid() {
         if (!Uri.EMPTY.equals(mPacFileUrl)) return true;
         return Proxy.PROXY_VALID == Proxy.validate(mHost == null ? "" : mHost,
-                                                mPort == 0 ? "" : Integer.toString(mPort),
-                                                mExclusionList == null ? "" : mExclusionList);
+                mPort == 0 ? "" : Integer.toString(mPort),
+                mExclusionList == null ? "" : mExclusionList);
     }
 
     /**
@@ -262,7 +268,7 @@
             sb.append("] ");
             sb.append(Integer.toString(mPort));
             if (mExclusionList != null) {
-                    sb.append(" xl=").append(mExclusionList);
+                sb.append(" xl=").append(mExclusionList);
             }
         } else {
             sb.append("[ProxyProperties.mHost == null]");
@@ -308,8 +314,8 @@
      */
     public int hashCode() {
         return ((null == mHost) ? 0 : mHost.hashCode())
-        + ((null == mExclusionList) ? 0 : mExclusionList.hashCode())
-        + mPort;
+                + ((null == mExclusionList) ? 0 : mExclusionList.hashCode())
+                + mPort;
     }
 
     /**
@@ -352,8 +358,7 @@
                 }
                 String exclList = in.readString();
                 String[] parsedExclList = in.readStringArray();
-                ProxyInfo proxyProperties =
-                        new ProxyInfo(host, port, exclList, parsedExclList);
+                ProxyInfo proxyProperties = new ProxyInfo(host, port, exclList, parsedExclList);
                 return proxyProperties;
             }
 
diff --git a/core/java/android/net/RouteInfo.java b/core/java/android/net/RouteInfo.java
index 6bf2c67..5c0f758 100644
--- a/core/java/android/net/RouteInfo.java
+++ b/core/java/android/net/RouteInfo.java
@@ -110,6 +110,8 @@
      *
      * @hide
      */
+    @SystemApi
+    @TestApi
     public RouteInfo(IpPrefix destination, InetAddress gateway, String iface, int type) {
         switch (type) {
             case RTN_UNICAST:
diff --git a/core/java/android/net/SocketKeepalive.java b/core/java/android/net/SocketKeepalive.java
new file mode 100644
index 0000000..97d50f4
--- /dev/null
+++ b/core/java/android/net/SocketKeepalive.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 android.net;
+
+import android.annotation.IntDef;
+import android.annotation.IntRange;
+import android.annotation.NonNull;
+import android.os.Handler;
+import android.os.HandlerThread;
+import android.os.Looper;
+import android.os.Message;
+import android.os.Messenger;
+import android.os.Process;
+import android.util.Log;
+
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+import java.util.concurrent.Executor;
+
+/**
+ * Allows applications to request that the system periodically send specific packets on their
+ * behalf, using hardware offload to save battery power.
+ *
+ * To request that the system send keepalives, call one of the methods that return a
+ * {@link SocketKeepalive} object, such as {@link ConnectivityManager#createSocketKeepalive},
+ * passing in a non-null callback. If the {@link SocketKeepalive} is successfully
+ * started, the callback's {@code onStarted} method will be called. If an error occurs,
+ * {@code onError} will be called, specifying one of the {@code ERROR_*} constants in this
+ * class.
+ *
+ * To stop an existing keepalive, call {@link SocketKeepalive#stop}. The system will call
+ * {@link SocketKeepalive.Callback#onStopped} if the operation was successful or
+ * {@link SocketKeepalive.Callback#onError} if an error occurred.
+ */
+public abstract class SocketKeepalive implements AutoCloseable {
+    static final String TAG = "SocketKeepalive";
+
+    /** @hide */
+    public static final int SUCCESS = 0;
+
+    /** @hide */
+    public static final int NO_KEEPALIVE = -1;
+
+    /** @hide */
+    public static final int DATA_RECEIVED = -2;
+
+    /** @hide */
+    public static final int BINDER_DIED = -10;
+
+    /** The specified {@code Network} is not connected. */
+    public static final int ERROR_INVALID_NETWORK = -20;
+    /** The specified IP addresses are invalid. For example, the specified source IP address is
+     * not configured on the specified {@code Network}. */
+    public static final int ERROR_INVALID_IP_ADDRESS = -21;
+    /** The requested port is invalid. */
+    public static final int ERROR_INVALID_PORT = -22;
+    /** The packet length is invalid (e.g., too long). */
+    public static final int ERROR_INVALID_LENGTH = -23;
+    /** The packet transmission interval is invalid (e.g., too short). */
+    public static final int ERROR_INVALID_INTERVAL = -24;
+    /** The target socket is invalid. */
+    public static final int ERROR_INVALID_SOCKET = -25;
+    /** The target socket is not idle. */
+    public static final int ERROR_SOCKET_NOT_IDLE = -26;
+
+    /** The hardware does not support this request. */
+    public static final int ERROR_HARDWARE_UNSUPPORTED = -30;
+    /** The hardware returned an error. */
+    public static final int ERROR_HARDWARE_ERROR = -31;
+
+    /** @hide */
+    @Retention(RetentionPolicy.SOURCE)
+    @IntDef(prefix = { "ERROR_" }, value = {
+            ERROR_INVALID_NETWORK,
+            ERROR_INVALID_IP_ADDRESS,
+            ERROR_INVALID_PORT,
+            ERROR_INVALID_LENGTH,
+            ERROR_INVALID_INTERVAL,
+            ERROR_INVALID_SOCKET,
+            ERROR_SOCKET_NOT_IDLE
+    })
+    public @interface ErrorCode {}
+
+    /**
+     * The minimum interval in seconds between keepalive packet transmissions.
+     *
+     * @hide
+     **/
+    public static final int MIN_INTERVAL_SEC = 10;
+
+    /**
+     * The maximum interval in seconds between keepalive packet transmissions.
+     *
+     * @hide
+     **/
+    public static final int MAX_INTERVAL_SEC = 3600;
+
+    @NonNull final IConnectivityManager mService;
+    @NonNull final Network mNetwork;
+    @NonNull private final Executor mExecutor;
+    @NonNull private final SocketKeepalive.Callback mCallback;
+    @NonNull private final Looper mLooper;
+    @NonNull final Messenger mMessenger;
+    @NonNull Integer mSlot;
+
+    SocketKeepalive(@NonNull IConnectivityManager service, @NonNull Network network,
+            @NonNull Executor executor, @NonNull Callback callback) {
+        mService = service;
+        mNetwork = network;
+        mExecutor = executor;
+        mCallback = callback;
+        // TODO: 1. Use other thread modeling instead of create one thread for every instance to
+        //          reduce the memory cost.
+        //       2. support restart.
+        //       3. Fix race condition which caused by rapidly start and stop.
+        HandlerThread thread = new HandlerThread(TAG, Process.THREAD_PRIORITY_BACKGROUND
+                + Process.THREAD_PRIORITY_LESS_FAVORABLE);
+        thread.start();
+        mLooper = thread.getLooper();
+        mMessenger = new Messenger(new Handler(mLooper) {
+            @Override
+            public void handleMessage(Message message) {
+                switch (message.what) {
+                    case NetworkAgent.EVENT_PACKET_KEEPALIVE:
+                        final int status = message.arg2;
+                        try {
+                            if (status == SUCCESS) {
+                                if (mSlot == null) {
+                                    mSlot = message.arg1;
+                                    mExecutor.execute(() -> mCallback.onStarted());
+                                } else {
+                                    mSlot = null;
+                                    stopLooper();
+                                    mExecutor.execute(() -> mCallback.onStopped());
+                                }
+                            } else if (status == DATA_RECEIVED) {
+                                stopLooper();
+                                mExecutor.execute(() -> mCallback.onDataReceived());
+                            } else {
+                                stopLooper();
+                                mExecutor.execute(() -> mCallback.onError(status));
+                            }
+                        } catch (Exception e) {
+                            Log.e(TAG, "Exception in keepalive callback(" + status + ")", e);
+                        }
+                        break;
+                    default:
+                        Log.e(TAG, "Unhandled message " + Integer.toHexString(message.what));
+                        break;
+                }
+            }
+        });
+    }
+
+    /**
+     * Request that keepalive be started with the given {@code intervalSec}. See
+     * {@link SocketKeepalive}.
+     *
+     * @param intervalSec The target interval in seconds between keepalive packet transmissions.
+     *                    The interval should be between 10 seconds and 3600 seconds, otherwise
+     *                    {@link #ERROR_INVALID_INTERVAL} will be returned.
+     */
+    public final void start(@IntRange(from = MIN_INTERVAL_SEC, to = MAX_INTERVAL_SEC)
+            int intervalSec) {
+        startImpl(intervalSec);
+    }
+
+    abstract void startImpl(int intervalSec);
+
+    /** @hide */
+    protected void stopLooper() {
+        // TODO: remove this after changing thread modeling.
+        mLooper.quit();
+    }
+
+    /**
+     * Requests that keepalive be stopped. The application must wait for {@link Callback#onStopped}
+     * before using the object. See {@link SocketKeepalive}.
+     */
+    public final void stop() {
+        stopImpl();
+    }
+
+    abstract void stopImpl();
+
+    /**
+     * Deactivate this {@link SocketKeepalive} and free allocated resources. The instance won't be
+     * usable again if {@code close()} is called.
+     */
+    @Override
+    public final void close() {
+        stop();
+        stopLooper();
+    }
+
+    /**
+     * The callback which app can use to learn the status changes of {@link SocketKeepalive}. See
+     * {@link SocketKeepalive}.
+     */
+    public static class Callback {
+        /** The requested keepalive was successfully started. */
+        public void onStarted() {}
+        /** The keepalive was successfully stopped. */
+        public void onStopped() {}
+        /** An error occurred. */
+        public void onError(@ErrorCode int error) {}
+        /** The keepalive on a TCP socket was stopped because the socket received data. */
+        public void onDataReceived() {}
+    }
+}
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/VpnService.java b/core/java/android/net/VpnService.java
index 37bf3a7..dc099a4 100644
--- a/core/java/android/net/VpnService.java
+++ b/core/java/android/net/VpnService.java
@@ -509,6 +509,15 @@
         }
 
         /**
+         * Sets an HTTP proxy for the VPN network. This proxy is only a recommendation
+         * and it is possible that some apps will ignore it.
+         */
+        public Builder setHttpProxy(ProxyInfo proxyInfo) {
+            mConfig.proxyInfo = proxyInfo;
+            return this;
+        }
+
+        /**
          * Add a network address to the VPN interface. Both IPv4 and IPv6
          * addresses are supported. At least one address must be set before
          * calling {@link #establish}.
diff --git a/core/java/android/net/apf/ApfCapabilities.java b/core/java/android/net/apf/ApfCapabilities.java
index f28cdc9..73cf94b 100644
--- a/core/java/android/net/apf/ApfCapabilities.java
+++ b/core/java/android/net/apf/ApfCapabilities.java
@@ -16,11 +16,16 @@
 
 package android.net.apf;
 
+import android.annotation.SystemApi;
+import android.annotation.TestApi;
+
 /**
  * APF program support capabilities.
  *
  * @hide
  */
+@SystemApi
+@TestApi
 public class ApfCapabilities {
     /**
      * Version of APF instruction set supported for packet filtering. 0 indicates no support for
diff --git a/core/java/android/net/captiveportal/CaptivePortalProbeResult.java b/core/java/android/net/captiveportal/CaptivePortalProbeResult.java
index 1634694..7432687 100644
--- a/core/java/android/net/captiveportal/CaptivePortalProbeResult.java
+++ b/core/java/android/net/captiveportal/CaptivePortalProbeResult.java
@@ -17,11 +17,15 @@
 package android.net.captiveportal;
 
 import android.annotation.Nullable;
+import android.annotation.SystemApi;
+import android.annotation.TestApi;
 
 /**
  * Result of calling isCaptivePortal().
  * @hide
  */
+@SystemApi
+@TestApi
 public final class CaptivePortalProbeResult {
     public static final int SUCCESS_CODE = 204;
     public static final int FAILED_CODE = 599;
diff --git a/core/java/android/net/captiveportal/CaptivePortalProbeSpec.java b/core/java/android/net/captiveportal/CaptivePortalProbeSpec.java
index 57a926a..7ad4ecf 100644
--- a/core/java/android/net/captiveportal/CaptivePortalProbeSpec.java
+++ b/core/java/android/net/captiveportal/CaptivePortalProbeSpec.java
@@ -21,21 +21,26 @@
 
 import android.annotation.NonNull;
 import android.annotation.Nullable;
+import android.annotation.SystemApi;
+import android.annotation.TestApi;
 import android.text.TextUtils;
 import android.util.Log;
 
+import com.android.internal.annotations.VisibleForTesting;
+
 import java.net.MalformedURLException;
 import java.net.URL;
 import java.text.ParseException;
 import java.util.ArrayList;
+import java.util.Collection;
 import java.util.List;
 import java.util.regex.Pattern;
 import java.util.regex.PatternSyntaxException;
 
 /** @hide */
+@SystemApi
+@TestApi
 public abstract class CaptivePortalProbeSpec {
-    public static final String HTTP_LOCATION_HEADER_NAME = "Location";
-
     private static final String TAG = CaptivePortalProbeSpec.class.getSimpleName();
     private static final String REGEX_SEPARATOR = "@@/@@";
     private static final String SPEC_SEPARATOR = "@@,@@";
@@ -55,7 +60,9 @@
      * @throws MalformedURLException The URL has invalid format for {@link URL#URL(String)}.
      * @throws ParseException The string is empty, does not match the above format, or a regular
      * expression is invalid for {@link Pattern#compile(String)}.
+     * @hide
      */
+    @VisibleForTesting
     @NonNull
     public static CaptivePortalProbeSpec parseSpec(String spec) throws ParseException,
             MalformedURLException {
@@ -113,7 +120,8 @@
      * <p>Each spec is separated by @@,@@ and follows the format for {@link #parseSpec(String)}.
      * <p>This method does not throw but ignores any entry that could not be parsed.
      */
-    public static CaptivePortalProbeSpec[] parseCaptivePortalProbeSpecs(String settingsVal) {
+    public static Collection<CaptivePortalProbeSpec> parseCaptivePortalProbeSpecs(
+            String settingsVal) {
         List<CaptivePortalProbeSpec> specs = new ArrayList<>();
         if (settingsVal != null) {
             for (String spec : TextUtils.split(settingsVal, SPEC_SEPARATOR)) {
@@ -128,7 +136,7 @@
         if (specs.isEmpty()) {
             Log.e(TAG, String.format("could not create any validation spec from %s", settingsVal));
         }
-        return specs.toArray(new CaptivePortalProbeSpec[specs.size()]);
+        return specs;
     }
 
     /**
diff --git a/core/java/android/net/metrics/IpConnectivityLog.java b/core/java/android/net/metrics/IpConnectivityLog.java
index 16aea31b..5b5a235 100644
--- a/core/java/android/net/metrics/IpConnectivityLog.java
+++ b/core/java/android/net/metrics/IpConnectivityLog.java
@@ -18,7 +18,6 @@
 
 import android.annotation.SystemApi;
 import android.annotation.TestApi;
-import android.annotation.UnsupportedAppUsage;
 import android.net.ConnectivityMetricsEvent;
 import android.net.IIpConnectivityMetrics;
 import android.net.Network;
@@ -51,7 +50,8 @@
     public interface Event extends Parcelable {}
 
     /** @hide */
-    @UnsupportedAppUsage
+    @SystemApi
+    @TestApi
     public IpConnectivityLog() {
     }
 
diff --git a/core/java/android/net/metrics/RaEvent.java b/core/java/android/net/metrics/RaEvent.java
index d308246..04a2e6e 100644
--- a/core/java/android/net/metrics/RaEvent.java
+++ b/core/java/android/net/metrics/RaEvent.java
@@ -16,7 +16,8 @@
 
 package android.net.metrics;
 
-import android.annotation.UnsupportedAppUsage;
+import android.annotation.SystemApi;
+import android.annotation.TestApi;
 import android.os.Parcel;
 import android.os.Parcelable;
 
@@ -24,19 +25,28 @@
  * An event logged when the APF packet socket receives an RA packet.
  * {@hide}
  */
+@SystemApi
+@TestApi
 public final class RaEvent implements IpConnectivityLog.Event {
 
-    public static final long NO_LIFETIME = -1L;
+    private static final long NO_LIFETIME = -1L;
 
     // Lifetime in seconds of options found in a single RA packet.
     // When an option is not set, the value of the associated field is -1;
+    /** @hide */
     public final long routerLifetime;
+    /** @hide */
     public final long prefixValidLifetime;
+    /** @hide */
     public final long prefixPreferredLifetime;
+    /** @hide */
     public final long routeInfoLifetime;
+    /** @hide */
     public final long rdnssLifetime;
+    /** @hide */
     public final long dnsslLifetime;
 
+    /** @hide */
     public RaEvent(long routerLifetime, long prefixValidLifetime, long prefixPreferredLifetime,
             long routeInfoLifetime, long rdnssLifetime, long dnsslLifetime) {
         this.routerLifetime = routerLifetime;
@@ -47,6 +57,7 @@
         this.dnsslLifetime = dnsslLifetime;
     }
 
+    /** @hide */
     private RaEvent(Parcel in) {
         routerLifetime          = in.readLong();
         prefixValidLifetime     = in.readLong();
@@ -56,6 +67,7 @@
         dnsslLifetime           = in.readLong();
     }
 
+    /** @hide */
     @Override
     public void writeToParcel(Parcel out, int flags) {
         out.writeLong(routerLifetime);
@@ -66,6 +78,7 @@
         out.writeLong(dnsslLifetime);
     }
 
+    /** @hide */
     @Override
     public int describeContents() {
         return 0;
@@ -83,6 +96,7 @@
                 .toString();
     }
 
+    /** @hide */
     public static final Parcelable.Creator<RaEvent> CREATOR = new Parcelable.Creator<RaEvent>() {
         public RaEvent createFromParcel(Parcel in) {
             return new RaEvent(in);
@@ -102,47 +116,39 @@
         long rdnssLifetime           = NO_LIFETIME;
         long dnsslLifetime           = NO_LIFETIME;
 
-        @UnsupportedAppUsage
         public Builder() {
         }
 
-        @UnsupportedAppUsage
         public RaEvent build() {
             return new RaEvent(routerLifetime, prefixValidLifetime, prefixPreferredLifetime,
                     routeInfoLifetime, rdnssLifetime, dnsslLifetime);
         }
 
-        @UnsupportedAppUsage
         public Builder updateRouterLifetime(long lifetime) {
             routerLifetime = updateLifetime(routerLifetime, lifetime);
             return this;
         }
 
-        @UnsupportedAppUsage
         public Builder updatePrefixValidLifetime(long lifetime) {
             prefixValidLifetime = updateLifetime(prefixValidLifetime, lifetime);
             return this;
         }
 
-        @UnsupportedAppUsage
         public Builder updatePrefixPreferredLifetime(long lifetime) {
             prefixPreferredLifetime = updateLifetime(prefixPreferredLifetime, lifetime);
             return this;
         }
 
-        @UnsupportedAppUsage
         public Builder updateRouteInfoLifetime(long lifetime) {
             routeInfoLifetime = updateLifetime(routeInfoLifetime, lifetime);
             return this;
         }
 
-        @UnsupportedAppUsage
         public Builder updateRdnssLifetime(long lifetime) {
             rdnssLifetime = updateLifetime(rdnssLifetime, lifetime);
             return this;
         }
 
-        @UnsupportedAppUsage
         public Builder updateDnsslLifetime(long lifetime) {
             dnsslLifetime = updateLifetime(dnsslLifetime, lifetime);
             return this;
diff --git a/core/java/android/net/shared/Inet4AddressUtils.java b/core/java/android/net/shared/Inet4AddressUtils.java
new file mode 100644
index 0000000..bec0c84
--- /dev/null
+++ b/core/java/android/net/shared/Inet4AddressUtils.java
@@ -0,0 +1,166 @@
+/*
+ * Copyright (C) 2019 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.net.shared;
+
+import java.net.Inet4Address;
+import java.net.InetAddress;
+import java.net.UnknownHostException;
+
+/**
+ * Collection of utilities to work with IPv4 addresses.
+ * @hide
+ */
+public class Inet4AddressUtils {
+
+    /**
+     * Convert a IPv4 address from an integer to an InetAddress (0x04030201 -> 1.2.3.4)
+     *
+     * <p>This method uses the higher-order int bytes as the lower-order IPv4 address bytes,
+     * which is an unusual convention. Consider {@link #intToInet4AddressHTH(int)} instead.
+     * @param hostAddress an int coding for an IPv4 address, where higher-order int byte is
+     *                    lower-order IPv4 address byte
+     */
+    public static Inet4Address intToInet4AddressHTL(int hostAddress) {
+        return intToInet4AddressHTH(Integer.reverseBytes(hostAddress));
+    }
+
+    /**
+     * Convert a IPv4 address from an integer to an InetAddress (0x01020304 -> 1.2.3.4)
+     * @param hostAddress an int coding for an IPv4 address
+     */
+    public static Inet4Address intToInet4AddressHTH(int hostAddress) {
+        byte[] addressBytes = { (byte) (0xff & (hostAddress >> 24)),
+                (byte) (0xff & (hostAddress >> 16)),
+                (byte) (0xff & (hostAddress >> 8)),
+                (byte) (0xff & hostAddress) };
+
+        try {
+            return (Inet4Address) InetAddress.getByAddress(addressBytes);
+        } catch (UnknownHostException e) {
+            throw new AssertionError();
+        }
+    }
+
+    /**
+     * Convert an IPv4 address from an InetAddress to an integer (1.2.3.4 -> 0x01020304)
+     *
+     * <p>This conversion can help order IP addresses: considering the ordering
+     * 192.0.2.1 < 192.0.2.2 < ..., resulting ints will follow that ordering if read as unsigned
+     * integers with {@link Integer#toUnsignedLong}.
+     * @param inetAddr is an InetAddress corresponding to the IPv4 address
+     * @return the IP address as integer
+     */
+    public static int inet4AddressToIntHTH(Inet4Address inetAddr)
+            throws IllegalArgumentException {
+        byte [] addr = inetAddr.getAddress();
+        return ((addr[0] & 0xff) << 24) | ((addr[1] & 0xff) << 16)
+                | ((addr[2] & 0xff) << 8) | (addr[3] & 0xff);
+    }
+
+    /**
+     * Convert a IPv4 address from an InetAddress to an integer (1.2.3.4 -> 0x04030201)
+     *
+     * <p>This method stores the higher-order IPv4 address bytes in the lower-order int bytes,
+     * which is an unusual convention. Consider {@link #inet4AddressToIntHTH(Inet4Address)} instead.
+     * @param inetAddr is an InetAddress corresponding to the IPv4 address
+     * @return the IP address as integer
+     */
+    public static int inet4AddressToIntHTL(Inet4Address inetAddr) {
+        return Integer.reverseBytes(inet4AddressToIntHTH(inetAddr));
+    }
+
+    /**
+     * Convert a network prefix length to an IPv4 netmask integer (prefixLength 17 -> 0xffff8000)
+     * @return the IPv4 netmask as an integer
+     */
+    public static int prefixLengthToV4NetmaskIntHTH(int prefixLength)
+            throws IllegalArgumentException {
+        if (prefixLength < 0 || prefixLength > 32) {
+            throw new IllegalArgumentException("Invalid prefix length (0 <= prefix <= 32)");
+        }
+        // (int)a << b is equivalent to a << (b & 0x1f): can't shift by 32 (-1 << 32 == -1)
+        return prefixLength == 0 ? 0 : 0xffffffff << (32 - prefixLength);
+    }
+
+    /**
+     * Convert a network prefix length to an IPv4 netmask integer (prefixLength 17 -> 0x0080ffff).
+     *
+     * <p>This method stores the higher-order IPv4 address bytes in the lower-order int bytes,
+     * which is an unusual convention. Consider {@link #prefixLengthToV4NetmaskIntHTH(int)} instead.
+     * @return the IPv4 netmask as an integer
+     */
+    public static int prefixLengthToV4NetmaskIntHTL(int prefixLength)
+            throws IllegalArgumentException {
+        return Integer.reverseBytes(prefixLengthToV4NetmaskIntHTH(prefixLength));
+    }
+
+    /**
+     * Convert an IPv4 netmask to a prefix length, checking that the netmask is contiguous.
+     * @param netmask as a {@code Inet4Address}.
+     * @return the network prefix length
+     * @throws IllegalArgumentException the specified netmask was not contiguous.
+     * @hide
+     */
+    public static int netmaskToPrefixLength(Inet4Address netmask) {
+        // inetAddressToInt returns an int in *network* byte order.
+        int i = inet4AddressToIntHTH(netmask);
+        int prefixLength = Integer.bitCount(i);
+        int trailingZeros = Integer.numberOfTrailingZeros(i);
+        if (trailingZeros != 32 - prefixLength) {
+            throw new IllegalArgumentException("Non-contiguous netmask: " + Integer.toHexString(i));
+        }
+        return prefixLength;
+    }
+
+    /**
+     * Returns the implicit netmask of an IPv4 address, as was the custom before 1993.
+     */
+    public static int getImplicitNetmask(Inet4Address address) {
+        int firstByte = address.getAddress()[0] & 0xff;  // Convert to an unsigned value.
+        if (firstByte < 128) {
+            return 8;
+        } else if (firstByte < 192) {
+            return 16;
+        } else if (firstByte < 224) {
+            return 24;
+        } else {
+            return 32;  // Will likely not end well for other reasons.
+        }
+    }
+
+    /**
+     * Get the broadcast address for a given prefix.
+     *
+     * <p>For example 192.168.0.1/24 -> 192.168.0.255
+     */
+    public static Inet4Address getBroadcastAddress(Inet4Address addr, int prefixLength)
+            throws IllegalArgumentException {
+        final int intBroadcastAddr = inet4AddressToIntHTH(addr)
+                | ~prefixLengthToV4NetmaskIntHTH(prefixLength);
+        return intToInet4AddressHTH(intBroadcastAddr);
+    }
+
+    /**
+     * Get a prefix mask as Inet4Address for a given prefix length.
+     *
+     * <p>For example 20 -> 255.255.240.0
+     */
+    public static Inet4Address getPrefixMaskAsInet4Address(int prefixLength)
+            throws IllegalArgumentException {
+        return intToInet4AddressHTH(prefixLengthToV4NetmaskIntHTH(prefixLength));
+    }
+}
diff --git a/core/java/android/os/BatteryManager.java b/core/java/android/os/BatteryManager.java
index 954071a..3fc5e41 100644
--- a/core/java/android/os/BatteryManager.java
+++ b/core/java/android/os/BatteryManager.java
@@ -16,6 +16,8 @@
 
 package android.os;
 
+import android.Manifest.permission;
+import android.annotation.RequiresPermission;
 import android.annotation.SystemApi;
 import android.annotation.SystemService;
 import android.content.Context;
@@ -369,4 +371,26 @@
             throw e.rethrowFromSystemServer();
         }
     }
+
+    /**
+     * Sets the delay for reporting battery state as charging after device is plugged in.
+     * This allows machine-learning or heuristics to delay the reporting and the corresponding
+     * broadcast, based on battery level, charging rate, and/or other parameters.
+     *
+     * @param delayMillis the delay in milliseconds, negative value to reset.
+     *
+     * @return True if the delay was set successfully.
+     *
+     * @see ACTION_CHARGING
+     * @hide
+     */
+    @RequiresPermission(permission.POWER_SAVER)
+    @SystemApi
+    public boolean setChargingStateUpdateDelayMillis(int delayMillis) {
+        try {
+            return mBatteryStats.setChargingStateUpdateDelayMillis(delayMillis);
+        } catch (RemoteException e) {
+            throw e.rethrowFromSystemServer();
+        }
+    }
 }
diff --git a/core/java/android/os/BatteryStats.java b/core/java/android/os/BatteryStats.java
index eae1aa5..1919fcc 100644
--- a/core/java/android/os/BatteryStats.java
+++ b/core/java/android/os/BatteryStats.java
@@ -834,10 +834,10 @@
          * also be bumped.
          */
         static final String[] USER_ACTIVITY_TYPES = {
-            "other", "button", "touch", "accessibility"
+            "other", "button", "touch", "accessibility", "attention"
         };
 
-        public static final int NUM_USER_ACTIVITY_TYPES = 4;
+        public static final int NUM_USER_ACTIVITY_TYPES = USER_ACTIVITY_TYPES.length;
 
         public abstract void noteUserActivityLocked(int type);
         public abstract boolean hasUserActivity();
diff --git a/core/java/android/os/BugreportManager.java b/core/java/android/os/BugreportManager.java
index 518528d..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,55 +49,66 @@
     }
 
     /**
-     * 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;
+        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}.
+         * <p>If {@code BUGREPORT_ERROR_USER_DENIED_CONSENT} is passed, then the user did not
+         * consent to sharing the bugreport with the calling app.
+         *
+         * <p>If {@code BUGREPORT_ERROR_USER_CONSENT_TIMED_OUT} is passed, then the consent timed
+         * 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
@@ -106,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();
@@ -139,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
@@ -152,19 +171,37 @@
 
         @Override
         public void onProgress(int progress) throws RemoteException {
-            mListener.onProgress(progress);
+            final long identity = Binder.clearCallingIdentity();
+            try {
+                mExecutor.execute(() -> {
+                    mCallback.onProgress(progress);
+                });
+            } finally {
+                Binder.restoreCallingIdentity(identity);
+            }
         }
 
         @Override
         public void onError(int errorCode) throws RemoteException {
-            mListener.onError(errorCode);
+            final long identity = Binder.clearCallingIdentity();
+            try {
+                mExecutor.execute(() -> {
+                    mCallback.onError(errorCode);
+                });
+            } finally {
+                Binder.restoreCallingIdentity(identity);
+            }
         }
 
         @Override
         public void onFinished() throws RemoteException {
+            final long identity = Binder.clearCallingIdentity();
             try {
-                mListener.onFinished();
+                mExecutor.execute(() -> {
+                    mCallback.onFinished();
+                });
             } finally {
+                Binder.restoreCallingIdentity(identity);
                 // The bugreport has finished. Let's shutdown the service to minimize its footprint.
                 cancelBugreport();
             }
diff --git a/core/java/android/os/BugreportParams.java b/core/java/android/os/BugreportParams.java
index 4e696ae..3871375 100644
--- a/core/java/android/os/BugreportParams.java
+++ b/core/java/android/os/BugreportParams.java
@@ -17,6 +17,7 @@
 package android.os;
 
 import android.annotation.IntDef;
+import android.annotation.SystemApi;
 
 import java.lang.annotation.Retention;
 import java.lang.annotation.RetentionPolicy;
@@ -26,8 +27,7 @@
  *
  * @hide
  */
-// TODO: Expose API when the implementation is more complete.
-// @SystemApi
+@SystemApi
 public final class BugreportParams {
     private final int mMode;
 
diff --git a/core/java/android/os/ExternalVibration.aidl b/core/java/android/os/ExternalVibration.aidl
new file mode 100644
index 0000000..2629a40
--- /dev/null
+++ b/core/java/android/os/ExternalVibration.aidl
@@ -0,0 +1,19 @@
+/*
+ * Copyright (C) 2018 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.os;
+
+parcelable ExternalVibration cpp_header "vibrator/ExternalVibration.h";
diff --git a/core/java/android/os/ExternalVibration.java b/core/java/android/os/ExternalVibration.java
new file mode 100644
index 0000000..69ab1d9
--- /dev/null
+++ b/core/java/android/os/ExternalVibration.java
@@ -0,0 +1,171 @@
+/*
+ * Copyright (C) 2018 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.os;
+
+import android.annotation.NonNull;
+import android.media.AudioAttributes;
+import android.util.Slog;
+
+import com.android.internal.util.Preconditions;
+
+/**
+ * An ExternalVibration represents an on-going vibration being controlled by something other than
+ * the core vibrator service.
+ *
+ * @hide
+ */
+public class ExternalVibration implements Parcelable {
+    private static final String TAG = "ExternalVibration";
+    private int mUid;
+    @NonNull
+    private String mPkg;
+    @NonNull
+    private AudioAttributes mAttrs;
+    @NonNull
+    private IExternalVibrationController mController;
+    // A token used to maintain equality comparisons when passing objects across process
+    // boundaries.
+    @NonNull
+    private IBinder mToken;
+
+    public ExternalVibration(int uid, @NonNull String pkg, @NonNull AudioAttributes attrs,
+            @NonNull IExternalVibrationController controller) {
+        mUid = uid;
+        mPkg = Preconditions.checkNotNull(pkg);
+        mAttrs = Preconditions.checkNotNull(attrs);
+        mController = Preconditions.checkNotNull(controller);
+        mToken = new Binder();
+    }
+
+    private ExternalVibration(Parcel in) {
+        mUid = in.readInt();
+        mPkg = in.readString();
+        mAttrs = readAudioAttributes(in);
+        mController = IExternalVibrationController.Stub.asInterface(in.readStrongBinder());
+        mToken = in.readStrongBinder();
+    }
+
+    private AudioAttributes readAudioAttributes(Parcel in) {
+        int usage = in.readInt();
+        int contentType = in.readInt();
+        int capturePreset = in.readInt();
+        int flags = in.readInt();
+        AudioAttributes.Builder builder = new AudioAttributes.Builder();
+        return builder.setUsage(usage)
+                .setContentType(contentType)
+                .setCapturePreset(capturePreset)
+                .setFlags(flags)
+                .build();
+    }
+
+    public int getUid() {
+        return mUid;
+    }
+
+    public String getPackage() {
+        return mPkg;
+    }
+
+    public AudioAttributes getAudioAttributes() {
+        return mAttrs;
+    }
+
+    /**
+     * Mutes the external vibration if it's playing and unmuted.
+     *
+     * @return whether the muting operation was successful
+     */
+    public boolean mute() {
+        try {
+            mController.mute();
+        } catch (RemoteException e) {
+            Slog.wtf(TAG, "Failed to mute vibration stream: " + this, e);
+            return false;
+        }
+        return true;
+    }
+
+    /**
+     * Unmutes the external vibration if it's playing and muted.
+     *
+     * @return whether the unmuting operation was successful
+     */
+    public boolean unmute() {
+        try {
+            mController.unmute();
+        } catch (RemoteException e) {
+            Slog.wtf(TAG, "Failed to unmute vibration stream: " + this, e);
+            return false;
+        }
+        return true;
+    }
+
+    @Override
+    public boolean equals(Object o) {
+        if (o == null || !(o instanceof ExternalVibration)) {
+            return false;
+        }
+        ExternalVibration other = (ExternalVibration) o;
+        return mToken.equals(other.mToken);
+    }
+
+    @Override
+    public String toString() {
+        return "ExternalVibration{"
+            + "uid=" + mUid + ", "
+            + "pkg=" + mPkg + ", "
+            + "attrs=" + mAttrs + ", "
+            + "controller=" + mController
+            + "token=" + mController
+            + "}";
+    }
+
+    @Override
+    public void writeToParcel(Parcel out, int flags) {
+        out.writeInt(mUid);
+        out.writeString(mPkg);
+        writeAudioAttributes(mAttrs, out, flags);
+        out.writeParcelable(mAttrs, flags);
+        out.writeStrongBinder(mController.asBinder());
+        out.writeStrongBinder(mToken);
+    }
+
+    private static void writeAudioAttributes(AudioAttributes attrs, Parcel out, int flags) {
+        out.writeInt(attrs.getUsage());
+        out.writeInt(attrs.getContentType());
+        out.writeInt(attrs.getCapturePreset());
+        out.writeInt(attrs.getAllFlags());
+    }
+
+    @Override
+    public int describeContents() {
+        return 0;
+    }
+
+    public static final Parcelable.Creator<ExternalVibration> CREATOR =
+            new Parcelable.Creator<ExternalVibration>() {
+                @Override
+                public ExternalVibration createFromParcel(Parcel in) {
+                    return new ExternalVibration(in);
+                }
+
+                @Override
+                public ExternalVibration[] newArray(int size) {
+                    return new ExternalVibration[size];
+                }
+            };
+}
diff --git a/core/java/android/os/FileObserver.java b/core/java/android/os/FileObserver.java
index dd85e15..da03895 100644
--- a/core/java/android/os/FileObserver.java
+++ b/core/java/android/os/FileObserver.java
@@ -16,11 +16,15 @@
 
 package android.os;
 
+import android.annotation.NonNull;
 import android.annotation.Nullable;
 import android.util.Log;
 
+import java.io.File;
 import java.lang.ref.WeakReference;
+import java.util.Arrays;
 import java.util.HashMap;
+import java.util.List;
 
 /**
  * Monitors files (using <a href="http://en.wikipedia.org/wiki/Inotify">inotify</a>)
@@ -28,7 +32,7 @@
  * the device (including this one).  FileObserver is an abstract class;
  * subclasses must implement the event handler {@link #onEvent(int, String)}.
  *
- * <p>Each FileObserver instance monitors a single file or directory.
+ * <p>Each FileObserver instance can monitor multiple files or directories.
  * If a directory is monitored, events will be triggered for all files and
  * subdirectories inside the monitored directory.</p>
  *
@@ -86,21 +90,32 @@
             observe(m_fd);
         }
 
-        public int startWatching(String path, int mask, FileObserver observer) {
-            int wfd = startWatching(m_fd, path, mask);
+        public int[] startWatching(List<File> files, int mask, FileObserver observer) {
+            final int count = files.size();
+            final String[] paths = new String[count];
+            for (int i = 0; i < count; ++i) {
+                paths[i] = files.get(i).getAbsolutePath();
+            }
+            final int[] wfds = new int[count];
+            Arrays.fill(wfds, -1);
 
-            Integer i = new Integer(wfd);
-            if (wfd >= 0) {
-                synchronized (m_observers) {
-                    m_observers.put(i, new WeakReference(observer));
+            startWatching(m_fd, paths, mask, wfds);
+
+            final WeakReference<FileObserver> fileObserverWeakReference =
+                    new WeakReference<>(observer);
+            synchronized (m_observers) {
+                for (int wfd : wfds) {
+                    if (wfd >= 0) {
+                        m_observers.put(wfd, fileObserverWeakReference);
+                    }
                 }
             }
 
-            return i;
+            return wfds;
         }
 
-        public void stopWatching(int descriptor) {
-            stopWatching(m_fd, descriptor);
+        public void stopWatching(int[] descriptors) {
+            stopWatching(m_fd, descriptors);
         }
 
         public void onEvent(int wfd, int mask, String path) {
@@ -129,8 +144,8 @@
 
         private native int init();
         private native void observe(int fd);
-        private native int startWatching(int fd, String path, int mask);
-        private native void stopWatching(int fd, int wfd);
+        private native void startWatching(int fd, String[] paths, int mask, int[] wfds);
+        private native void stopWatching(int fd, int[] wfds);
     }
 
     private static ObserverThread s_observerThread;
@@ -141,15 +156,34 @@
     }
 
     // instance
-    private String m_path;
-    private Integer m_descriptor;
-    private int m_mask;
+    private final List<File> mFiles;
+    private int[] mDescriptors;
+    private final int mMask;
 
     /**
      * Equivalent to FileObserver(path, FileObserver.ALL_EVENTS).
+     *
+     * @deprecated use {@link #FileObserver(File)} instead.
      */
+    @Deprecated
     public FileObserver(String path) {
-        this(path, ALL_EVENTS);
+        this(new File(path));
+    }
+
+    /**
+     * Equivalent to FileObserver(file, FileObserver.ALL_EVENTS).
+     */
+    public FileObserver(@NonNull File file) {
+        this(Arrays.asList(file));
+    }
+
+    /**
+     * Equivalent to FileObserver(paths, FileObserver.ALL_EVENTS).
+     *
+     * @param files The files or directories to monitor
+     */
+    public FileObserver(@NonNull List<File> files) {
+        this(files, ALL_EVENTS);
     }
 
     /**
@@ -159,11 +193,36 @@
      *
      * @param path The file or directory to monitor
      * @param mask The event or events (added together) to watch for
+     *
+     * @deprecated use {@link #FileObserver(File, int)} instead.
      */
+    @Deprecated
     public FileObserver(String path, int mask) {
-        m_path = path;
-        m_mask = mask;
-        m_descriptor = -1;
+        this(new File(path), mask);
+    }
+
+    /**
+     * Create a new file observer for a certain file or directory.
+     * Monitoring does not start on creation!  You must call
+     * {@link #startWatching()} before you will receive events.
+     *
+     * @param file The file or directory to monitor
+     * @param mask The event or events (added together) to watch for
+     */
+    public FileObserver(@NonNull File file, int mask) {
+        this(Arrays.asList(file), mask);
+    }
+
+    /**
+     * Version of {@link #FileObserver(File, int)} that allows callers to monitor
+     * multiple files or directories.
+     *
+     * @param files The files or directories to monitor
+     * @param mask The event or events (added together) to watch for
+     */
+    public FileObserver(@NonNull List<File> files, int mask) {
+        mFiles = files;
+        mMask = mask;
     }
 
     protected void finalize() {
@@ -176,8 +235,8 @@
      * If monitoring is already started, this call has no effect.
      */
     public void startWatching() {
-        if (m_descriptor < 0) {
-            m_descriptor = s_observerThread.startWatching(m_path, m_mask, this);
+        if (mDescriptors == null) {
+            mDescriptors = s_observerThread.startWatching(mFiles, mMask, this);
         }
     }
 
@@ -187,9 +246,9 @@
      * monitoring is already stopped, this call has no effect.
      */
     public void stopWatching() {
-        if (m_descriptor >= 0) {
-            s_observerThread.stopWatching(m_descriptor);
-            m_descriptor = -1;
+        if (mDescriptors != null) {
+            s_observerThread.stopWatching(mDescriptors);
+            mDescriptors = null;
         }
     }
 
diff --git a/core/java/android/os/FileUtils.java b/core/java/android/os/FileUtils.java
index 51c3c4c..629289b 100644
--- a/core/java/android/os/FileUtils.java
+++ b/core/java/android/os/FileUtils.java
@@ -39,6 +39,7 @@
 
 import android.annotation.NonNull;
 import android.annotation.Nullable;
+import android.annotation.TestApi;
 import android.content.ContentResolver;
 import android.provider.DocumentsContract.Document;
 import android.system.ErrnoException;
@@ -852,6 +853,7 @@
      *
      * @hide
      */
+    @TestApi
     public static boolean contains(File dir, File file) {
         if (dir == null || file == null) return false;
         return contains(dir.getAbsolutePath(), file.getAbsolutePath());
diff --git a/core/java/android/os/GraphicsEnvironment.java b/core/java/android/os/GraphicsEnvironment.java
index 5bf9095..efcad3e 100644
--- a/core/java/android/os/GraphicsEnvironment.java
+++ b/core/java/android/os/GraphicsEnvironment.java
@@ -369,26 +369,6 @@
     }
 
     /**
-     * Attempt to setup ANGLE with a (temporary) default rules file: b/121153494
-     * True: Rules file was loaded.
-     * False: Rules file was *not* loaded.
-     */
-    private boolean setupAngleRulesDebug(String packageName, String paths, String devOptIn) {
-        // b/121153494
-        // Skip APK rules file checking.
-        if (!DEBUG) {
-            Log.v(TAG, "Skipping loading the rules file.");
-            // Fill in some default values for now, so the loader can get an answer when it asks.
-            // Most importantly, we need to indicate which app we are init'ing and what the
-            // developer options for it are so we can turn on ANGLE if needed.
-            setAngleInfo(paths, packageName, devOptIn, null, 0, 0);
-            return true;
-        }
-
-        return false;
-    }
-
-    /**
      * Attempt to setup ANGLE with a rules file loaded from the ANGLE APK.
      * True: APK rules file was loaded.
      * False: APK rules file was *not* loaded.
@@ -425,16 +405,57 @@
     }
 
     /**
+     * Pull ANGLE whitelist from GlobalSettings and compare against current package
+     */
+    private boolean checkAngleWhitelist(Bundle bundle, String packageName) {
+        List<String> angleWhitelist =
+                getGlobalSettingsString(bundle,
+                    Settings.Global.GLOBAL_SETTINGS_ANGLE_WHITELIST);
+
+        return angleWhitelist.contains(packageName);
+    }
+
+    /**
      * Pass ANGLE details down to trigger enable logic
      */
     public void setupAngle(Context context, Bundle bundle, String packageName) {
-        String devOptIn = getDriverForPkg(bundle, packageName);
+        if (packageName.isEmpty()) {
+            Log.v(TAG, "No package name available yet, skipping ANGLE setup");
+            return;
+        }
 
+        String devOptIn = getDriverForPkg(bundle, packageName);
         if (DEBUG) {
             Log.v(TAG, "ANGLE Developer option for '" + packageName + "' "
                     + "set to: '" + devOptIn + "'");
         }
 
+        // We only need to check rules if the app is whitelisted or the developer has
+        // explicitly chosen something other than default driver.
+        //
+        // The whitelist will be generated by the ANGLE APK at both boot time and
+        // ANGLE update time. It will only include apps mentioned in the rules file.
+        //
+        // If the user has set the developer option to something other than default,
+        // we need to call setupAngleRulesApk() with the package name and the developer
+        // option value (native/angle/other). Then later when we are actually trying to
+        // load a driver, GraphicsEnv::shouldUseAngle() has seen the package name before
+        // and can confidently answer yes/no based on the previously set developer
+        // option value.
+        boolean whitelisted = checkAngleWhitelist(bundle, packageName);
+        boolean defaulted = devOptIn.equals(sDriverMap.get(OpenGlDriverChoice.DEFAULT));
+        boolean rulesCheck = (whitelisted || !defaulted);
+        if (!rulesCheck) {
+            return;
+        }
+
+        if (whitelisted) {
+            Log.v(TAG, "ANGLE whitelist includes " + packageName);
+        }
+        if (!defaulted) {
+            Log.v(TAG, "ANGLE developer option for " + packageName + ": " + devOptIn);
+        }
+
         String anglePkgName = getAnglePackageName(context);
         if (anglePkgName.isEmpty()) {
             Log.e(TAG, "Failed to find ANGLE package.");
@@ -466,12 +487,6 @@
             return;
         }
 
-        // b/121153494
-        if (setupAngleRulesDebug(packageName, paths, devOptIn)) {
-            // We setup ANGLE with defaults, so we're done here.
-            return;
-        }
-
         if (setupAngleRulesApk(anglePkgName, angleInfo, context, packageName, paths, devOptIn)) {
             // We setup ANGLE with rules from the APK, so we're done here.
             return;
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 63912ec..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;
@@ -54,6 +55,7 @@
 import java.io.FileOutputStream;
 import java.io.IOException;
 import java.io.InterruptedIOException;
+import java.io.UncheckedIOException;
 import java.net.DatagramSocket;
 import java.net.Socket;
 import java.nio.ByteOrder;
@@ -393,26 +395,41 @@
      * @param socket The Socket whose FileDescriptor is used to create
      *               a new ParcelFileDescriptor.
      *
-     * @return A new ParcelFileDescriptor with the FileDescriptor of the
-     *         specified Socket.
+     * @return A new ParcelFileDescriptor with a duped copy of the
+     * FileDescriptor of the specified Socket.
+     *
+     * @throws UncheckedIOException if {@link #dup(FileDescriptor)} throws IOException.
      */
     public static ParcelFileDescriptor fromSocket(Socket socket) {
         FileDescriptor fd = socket.getFileDescriptor$();
-        return fd != null ? new ParcelFileDescriptor(fd) : null;
+        try {
+            return fd != null ? ParcelFileDescriptor.dup(fd) : null;
+        } catch (IOException e) {
+            throw new UncheckedIOException(e);
+        }
     }
 
     /**
-     * Create a new ParcelFileDescriptor from the specified DatagramSocket.
+     * Create a new ParcelFileDescriptor from the specified DatagramSocket. The
+     * new ParcelFileDescriptor holds a dup of the original FileDescriptor in
+     * the DatagramSocket, so you must still close the DatagramSocket as well
+     * as the new ParcelFileDescriptor.
      *
      * @param datagramSocket The DatagramSocket whose FileDescriptor is used
      *               to create a new ParcelFileDescriptor.
      *
-     * @return A new ParcelFileDescriptor with the FileDescriptor of the
-     *         specified DatagramSocket.
+     * @return A new ParcelFileDescriptor with a duped copy of the
+     * FileDescriptor of the specified Socket.
+     *
+     * @throws UncheckedIOException if {@link #dup(FileDescriptor)} throws IOException.
      */
     public static ParcelFileDescriptor fromDatagramSocket(DatagramSocket datagramSocket) {
         FileDescriptor fd = datagramSocket.getFileDescriptor$();
-        return fd != null ? new ParcelFileDescriptor(fd) : null;
+        try {
+            return fd != null ? ParcelFileDescriptor.dup(fd) : null;
+        } catch (IOException e) {
+            throw new UncheckedIOException(e);
+        }
     }
 
     /**
@@ -542,7 +559,7 @@
         }
         file.deactivate();
         FileDescriptor fd = file.getFileDescriptor();
-        return fd != null ? new ParcelFileDescriptor(fd) : null;
+        return fd != null ? ParcelFileDescriptor.dup(fd) : null;
     }
 
     /**
@@ -564,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/Process.java b/core/java/android/os/Process.java
index 07c4933..d2ab053 100644
--- a/core/java/android/os/Process.java
+++ b/core/java/android/os/Process.java
@@ -530,7 +530,7 @@
         return ZYGOTE_PROCESS.start(processClass, niceName, uid, gid, gids,
                     runtimeFlags, mountExternal, targetSdkVersion, seInfo,
                     abi, instructionSet, appDataDir, invokeWith, packageName,
-                    packagesForUid, visibleVols, zygoteArgs);
+                    packagesForUid, visibleVols, /*useBlastulaPool=*/ true, zygoteArgs);
     }
 
     /** @hide */
@@ -551,7 +551,7 @@
         return WebViewZygote.getProcess().start(processClass, niceName, uid, gid, gids,
                     runtimeFlags, mountExternal, targetSdkVersion, seInfo,
                     abi, instructionSet, appDataDir, invokeWith, packageName,
-                    packagesForUid, visibleVols, zygoteArgs);
+                    packagesForUid, visibleVols, /*useBlastulaPool=*/ false, zygoteArgs);
     }
 
     /**
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/VibrationEffect.java b/core/java/android/os/VibrationEffect.java
index 01d85c6..99fb608 100644
--- a/core/java/android/os/VibrationEffect.java
+++ b/core/java/android/os/VibrationEffect.java
@@ -16,6 +16,7 @@
 
 package android.os;
 
+import android.annotation.IntDef;
 import android.annotation.Nullable;
 import android.annotation.TestApi;
 import android.content.ContentResolver;
@@ -25,6 +26,8 @@
 import android.net.Uri;
 import android.util.MathUtils;
 
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
 import java.util.Arrays;
 
 /**
@@ -52,26 +55,20 @@
      * A click effect.
      *
      * @see #get(int)
-     * @hide
      */
-    @TestApi
     public static final int EFFECT_CLICK = Effect.CLICK;
 
     /**
      * A double click effect.
      *
      * @see #get(int)
-     * @hide
      */
-    @TestApi
     public static final int EFFECT_DOUBLE_CLICK = Effect.DOUBLE_CLICK;
 
     /**
      * A tick effect.
      * @see #get(int)
-     * @hide
      */
-    @TestApi
     public static final int EFFECT_TICK = Effect.TICK;
 
     /**
@@ -93,9 +90,7 @@
     /**
      * A heavy click effect.
      * @see #get(int)
-     * @hide
      */
-    @TestApi
     public static final int EFFECT_HEAVY_CLICK = Effect.HEAVY_CLICK;
 
     /** {@hide} */
@@ -136,6 +131,16 @@
         Effect.RINGTONE_15
     };
 
+    /** @hide */
+    @IntDef(prefix = { "EFFECT_" }, value = {
+            EFFECT_TICK,
+            EFFECT_CLICK,
+            EFFECT_HEAVY_CLICK,
+            EFFECT_DOUBLE_CLICK,
+    })
+    @Retention(RetentionPolicy.SOURCE)
+    public @interface EffectType {}
+
     /** @hide to prevent subclassing from outside of the framework */
     public VibrationEffect() { }
 
@@ -219,6 +224,27 @@
     }
 
     /**
+     * Create a predefined vibration effect.
+     *
+     * Predefined effects are a set of common vibration effects that should be identical, regardless
+     * of the app they come from, in order to provide a cohesive experience for users across
+     * the entire device. They also may be custom tailored to the device hardware in order to
+     * provide a better experience than you could otherwise build using the generic building
+     * blocks.
+     *
+     * This will fallback to a generic pattern if one exists and there does not exist a
+     * hardware-specific implementation of the effect.
+     *
+     * @param effectId The ID of the effect to perform:
+     *                 {@link #EFFECT_CLICK}, {@link #EFFECT_DOUBLE_CLICK}, {@link #EFFECT_TICK}
+     *
+     * @return The desired effect.
+     */
+    public static VibrationEffect createPrebaked(@EffectType int effectId) {
+        return get(effectId, true);
+    }
+
+    /**
      * Get a predefined vibration effect.
      *
      * Predefined effects are a set of common vibration effects that should be identical, regardless
diff --git a/core/java/android/os/ZygoteProcess.java b/core/java/android/os/ZygoteProcess.java
index 4cb9c5b..9e47179 100644
--- a/core/java/android/os/ZygoteProcess.java
+++ b/core/java/android/os/ZygoteProcess.java
@@ -76,6 +76,16 @@
     /**
      * @hide for internal use only
      */
+    public static final String BLASTULA_POOL_SOCKET_NAME = "blastula_pool";
+
+    /**
+     * @hide for internal use only
+     */
+    public static final String BLASTULA_POOL_SECONDARY_SOCKET_NAME = "blastula_pool_secondary";
+
+    /**
+     * @hide for internal use only
+     */
     private static final String LOG_TAG = "ZygoteProcess";
 
     /**
@@ -87,6 +97,15 @@
      * The name of the secondary (alternate ABI) zygote socket.
      */
     private final LocalSocketAddress mZygoteSecondarySocketAddress;
+    /**
+     * The name of the socket used to communicate with the primary blastula pool.
+     */
+    private final LocalSocketAddress mBlastulaPoolSocketAddress;
+
+    /**
+     * The name of the socket used to communicate with the secondary (alternate ABI) blastula pool.
+     */
+    private final LocalSocketAddress mBlastulaPoolSecondarySocketAddress;
 
     public ZygoteProcess() {
         mZygoteSocketAddress =
@@ -94,12 +113,22 @@
         mZygoteSecondarySocketAddress =
                 new LocalSocketAddress(ZYGOTE_SECONDARY_SOCKET_NAME,
                                        LocalSocketAddress.Namespace.RESERVED);
+
+        mBlastulaPoolSocketAddress =
+                new LocalSocketAddress(BLASTULA_POOL_SOCKET_NAME,
+                                       LocalSocketAddress.Namespace.RESERVED);
+        mBlastulaPoolSecondarySocketAddress =
+                new LocalSocketAddress(BLASTULA_POOL_SECONDARY_SOCKET_NAME,
+                                       LocalSocketAddress.Namespace.RESERVED);
     }
 
     public ZygoteProcess(LocalSocketAddress primarySocketAddress,
                          LocalSocketAddress secondarySocketAddress) {
         mZygoteSocketAddress = primarySocketAddress;
         mZygoteSecondarySocketAddress = secondarySocketAddress;
+
+        mBlastulaPoolSocketAddress = null;
+        mBlastulaPoolSecondarySocketAddress = null;
     }
 
     public LocalSocketAddress getPrimarySocketAddress() {
@@ -111,6 +140,7 @@
      */
     public static class ZygoteState {
         final LocalSocketAddress mZygoteSocketAddress;
+        final LocalSocketAddress mBlastulaSocketAddress;
 
         private final LocalSocket mZygoteSessionSocket;
 
@@ -122,11 +152,13 @@
         private boolean mClosed;
 
         private ZygoteState(LocalSocketAddress zygoteSocketAddress,
+                            LocalSocketAddress blastulaSocketAddress,
                             LocalSocket zygoteSessionSocket,
                             DataInputStream zygoteInputStream,
                             BufferedWriter zygoteOutputWriter,
                             List<String> abiList) {
             this.mZygoteSocketAddress = zygoteSocketAddress;
+            this.mBlastulaSocketAddress = blastulaSocketAddress;
             this.mZygoteSessionSocket = zygoteSessionSocket;
             this.mZygoteInputStream = zygoteInputStream;
             this.mZygoteOutputWriter = zygoteOutputWriter;
@@ -134,14 +166,17 @@
         }
 
         /**
-         * Create a new ZygoteState object by connecting to the given Zygote socket.
+         * Create a new ZygoteState object by connecting to the given Zygote socket and saving the
+         * given blastula socket address.
          *
          * @param zygoteSocketAddress  Zygote socket to connect to
+         * @param blastulaSocketAddress  Blastula socket address to save for later
          * @return  A new ZygoteState object containing a session socket for the given Zygote socket
          * address
          * @throws IOException
          */
-        public static ZygoteState connect(LocalSocketAddress zygoteSocketAddress)
+        public static ZygoteState connect(LocalSocketAddress zygoteSocketAddress,
+                                          LocalSocketAddress blastulaSocketAddress)
                 throws IOException {
 
             DataInputStream zygoteInputStream = null;
@@ -154,7 +189,7 @@
                 zygoteOutputWriter =
                         new BufferedWriter(
                                 new OutputStreamWriter(zygoteSessionSocket.getOutputStream()),
-                                256);
+                                Zygote.SOCKET_BUFFER_SIZE);
             } catch (IOException ex) {
                 try {
                     zygoteSessionSocket.close();
@@ -163,11 +198,18 @@
                 throw ex;
             }
 
-            return new ZygoteState(zygoteSocketAddress,
+            return new ZygoteState(zygoteSocketAddress, blastulaSocketAddress,
                                    zygoteSessionSocket, zygoteInputStream, zygoteOutputWriter,
                                    getAbiList(zygoteOutputWriter, zygoteInputStream));
         }
 
+        LocalSocket getBlastulaSessionSocket() throws IOException {
+            final LocalSocket blastulaSessionSocket = new LocalSocket();
+            blastulaSessionSocket.connect(this.mBlastulaSocketAddress);
+
+            return blastulaSessionSocket;
+        }
+
         boolean matches(String abi) {
             return mABIList.contains(abi);
         }
@@ -269,12 +311,13 @@
                                                   @Nullable String packageName,
                                                   @Nullable String[] packagesForUid,
                                                   @Nullable String[] visibleVols,
+                                                  boolean useBlastulaPool,
                                                   @Nullable String[] zygoteArgs) {
         try {
             return startViaZygote(processClass, niceName, uid, gid, gids,
                     runtimeFlags, mountExternal, targetSdkVersion, seInfo,
                     abi, instructionSet, appDataDir, invokeWith, /*startChildZygote=*/false,
-                    packageName, packagesForUid, visibleVols, zygoteArgs);
+                    packageName, packagesForUid, visibleVols, useBlastulaPool, zygoteArgs);
         } catch (ZygoteStartFailedEx ex) {
             Log.e(LOG_TAG,
                     "Starting VM process through Zygote failed");
@@ -322,59 +365,128 @@
      */
     @GuardedBy("mLock")
     private static Process.ProcessStartResult zygoteSendArgsAndGetResult(
-            ZygoteState zygoteState, ArrayList<String> args)
+            ZygoteState zygoteState, boolean useBlastulaPool, ArrayList<String> args)
             throws ZygoteStartFailedEx {
-        try {
-            // Throw early if any of the arguments are malformed. This means we can
-            // avoid writing a partial response to the zygote.
-            int sz = args.size();
-            for (int i = 0; i < sz; i++) {
-                if (args.get(i).indexOf('\n') >= 0) {
-                    throw new ZygoteStartFailedEx("embedded newlines not allowed");
+        // Throw early if any of the arguments are malformed. This means we can
+        // avoid writing a partial response to the zygote.
+        for (String arg : args) {
+            if (arg.indexOf('\n') >= 0) {
+                throw new ZygoteStartFailedEx("embedded newlines not allowed");
+            }
+        }
+
+        /**
+         * See com.android.internal.os.SystemZygoteInit.readArgumentList()
+         * Presently the wire format to the zygote process is:
+         * a) a count of arguments (argc, in essence)
+         * b) a number of newline-separated argument strings equal to count
+         *
+         * After the zygote process reads these it will write the pid of
+         * the child or -1 on failure, followed by boolean to
+         * indicate whether a wrapper process was used.
+         */
+        String msgStr = Integer.toString(args.size()) + "\n"
+                        + String.join("\n", args) + "\n";
+
+        // Should there be a timeout on this?
+        Process.ProcessStartResult result = new Process.ProcessStartResult();
+
+        // TODO (chriswailes): Move branch body into separate function.
+        if (useBlastulaPool && Zygote.BLASTULA_POOL_ENABLED && isValidBlastulaCommand(args)) {
+            LocalSocket blastulaSessionSocket = null;
+
+            try {
+                blastulaSessionSocket = zygoteState.getBlastulaSessionSocket();
+
+                final BufferedWriter blastulaWriter =
+                        new BufferedWriter(
+                                new OutputStreamWriter(blastulaSessionSocket.getOutputStream()),
+                                Zygote.SOCKET_BUFFER_SIZE);
+                final DataInputStream blastulaReader =
+                        new DataInputStream(blastulaSessionSocket.getInputStream());
+
+                blastulaWriter.write(msgStr);
+                blastulaWriter.flush();
+
+                result.pid = blastulaReader.readInt();
+                // Blastulas can't be used to spawn processes that need wrappers.
+                result.usingWrapper = false;
+
+                if (result.pid < 0) {
+                    throw new ZygoteStartFailedEx("Blastula specialization failed");
+                }
+
+                return result;
+            } catch (IOException ex) {
+                // If there was an IOException using the blastula pool we will log the error and
+                // attempt to start the process through the Zygote.
+                Log.e(LOG_TAG, "IO Exception while communicating with blastula pool - "
+                               + ex.toString());
+            } finally {
+                try {
+                    blastulaSessionSocket.close();
+                } catch (IOException ex) {
+                    Log.e(LOG_TAG, "Failed to close blastula session socket: " + ex.getMessage());
                 }
             }
+        }
 
-            /**
-             * See com.android.internal.os.SystemZygoteInit.readArgumentList()
-             * Presently the wire format to the zygote process is:
-             * a) a count of arguments (argc, in essence)
-             * b) a number of newline-separated argument strings equal to count
-             *
-             * After the zygote process reads these it will write the pid of
-             * the child or -1 on failure, followed by boolean to
-             * indicate whether a wrapper process was used.
-             */
-            final BufferedWriter writer = zygoteState.mZygoteOutputWriter;
-            final DataInputStream inputStream = zygoteState.mZygoteInputStream;
+        try {
+            final BufferedWriter zygoteWriter = zygoteState.mZygoteOutputWriter;
+            final DataInputStream zygoteInputStream = zygoteState.mZygoteInputStream;
 
-            writer.write(Integer.toString(args.size()));
-            writer.newLine();
-
-            for (int i = 0; i < sz; i++) {
-                String arg = args.get(i);
-                writer.write(arg);
-                writer.newLine();
-            }
-
-            writer.flush();
-
-            // Should there be a timeout on this?
-            Process.ProcessStartResult result = new Process.ProcessStartResult();
+            zygoteWriter.write(msgStr);
+            zygoteWriter.flush();
 
             // Always read the entire result from the input stream to avoid leaving
             // bytes in the stream for future process starts to accidentally stumble
             // upon.
-            result.pid = inputStream.readInt();
-            result.usingWrapper = inputStream.readBoolean();
-
-            if (result.pid < 0) {
-                throw new ZygoteStartFailedEx("fork() failed");
-            }
-            return result;
+            result.pid = zygoteInputStream.readInt();
+            result.usingWrapper = zygoteInputStream.readBoolean();
         } catch (IOException ex) {
             zygoteState.close();
+            Log.e(LOG_TAG, "IO Exception while communicating with Zygote - "
+                    + ex.toString());
             throw new ZygoteStartFailedEx(ex);
         }
+
+        if (result.pid < 0) {
+            throw new ZygoteStartFailedEx("fork() failed");
+        }
+
+        return result;
+    }
+
+    /**
+     * Flags that may not be passed to a blastula.
+     */
+    private static final String[] INVALID_BLASTULA_FLAGS = {
+        "--query-abi-list",
+        "--get-pid",
+        "--preload-default",
+        "--preload-package",
+        "--preload-app",
+        "--start-child-zygote",
+        "--set-api-blacklist-exemptions",
+        "--hidden-api-log-sampling-rate",
+        "--invoke-with"
+    };
+
+    /**
+     * Tests a command list to see if it is valid to send to a blastula.
+     * @param args  Zygote/Blastula command arguments
+     * @return  True if the command can be passed to a blastula; false otherwise
+     */
+    private static boolean isValidBlastulaCommand(ArrayList<String> args) {
+        for (String flag : args) {
+            for (String badFlag : INVALID_BLASTULA_FLAGS) {
+                if (flag.startsWith(badFlag)) {
+                    return false;
+                }
+            }
+        }
+
+        return true;
     }
 
     /**
@@ -416,6 +528,7 @@
                                                       @Nullable String packageName,
                                                       @Nullable String[] packagesForUid,
                                                       @Nullable String[] visibleVols,
+                                                      boolean useBlastulaPool,
                                                       @Nullable String[] extraArgs)
                                                       throws ZygoteStartFailedEx {
         ArrayList<String> argsForZygote = new ArrayList<String>();
@@ -522,7 +635,9 @@
         }
 
         synchronized(mLock) {
-            return zygoteSendArgsAndGetResult(openZygoteSocketIfNeeded(abi), argsForZygote);
+            return zygoteSendArgsAndGetResult(openZygoteSocketIfNeeded(abi),
+                                              useBlastulaPool,
+                                              argsForZygote);
         }
     }
 
@@ -686,7 +801,7 @@
         if (primaryZygoteState == null || primaryZygoteState.isClosed()) {
             try {
                 primaryZygoteState =
-                    ZygoteState.connect(mZygoteSocketAddress);
+                    ZygoteState.connect(mZygoteSocketAddress, mBlastulaPoolSocketAddress);
             } catch (IOException ioe) {
                 throw new ZygoteStartFailedEx("Error connecting to primary zygote", ioe);
             }
@@ -703,7 +818,8 @@
         if (secondaryZygoteState == null || secondaryZygoteState.isClosed()) {
             try {
                 secondaryZygoteState =
-                    ZygoteState.connect(mZygoteSecondarySocketAddress);
+                    ZygoteState.connect(mZygoteSecondarySocketAddress,
+                                        mBlastulaPoolSecondarySocketAddress);
             } catch (IOException ioe) {
                 throw new ZygoteStartFailedEx("Error connecting to secondary zygote", ioe);
             }
@@ -820,7 +936,7 @@
         for (int n = 20; n >= 0; n--) {
             try {
                 final ZygoteState zs =
-                        ZygoteState.connect(zygoteSocketAddress);
+                        ZygoteState.connect(zygoteSocketAddress, null);
                 zs.close();
                 return;
             } catch (IOException ioe) {
@@ -884,7 +1000,8 @@
                     gids, runtimeFlags, 0 /* mountExternal */, 0 /* targetSdkVersion */, seInfo,
                     abi, instructionSet, null /* appDataDir */, null /* invokeWith */,
                     true /* startChildZygote */, null /* packageName */,
-                    null /* packagesForUid */, null /* visibleVolumes */, extraArgs);
+                    null /* packagesForUid */, null /* visibleVolumes */,
+                    false /* useBlastulaPool */, extraArgs);
         } catch (ZygoteStartFailedEx ex) {
             throw new RuntimeException("Starting child-zygote through Zygote failed", ex);
         }
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..9a58b971 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 c167ea1..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;
@@ -44,6 +45,8 @@
 
 import com.android.internal.util.Preconditions;
 
+import java.util.Set;
+
 /**
  * <p>
  * The contract between the calendar provider and applications. Contains
@@ -217,7 +220,7 @@
      * The intent will have its action set to
      * {@link CalendarContract#ACTION_VIEW_WORK_CALENDAR_EVENT} and contain extras
      * corresponding to the API's arguments. A calendar app intending to support
-     * cross profile events viewing should handle this intent, parse the arguments
+     * cross-profile events viewing should handle this intent, parse the arguments
      * and show the appropriate UI.
      *
      * @param context the context.
@@ -767,9 +770,10 @@
          * projection of the query to this uri that are not contained in the above list.
          *
          * <p>This uri will return an empty cursor if the calling user is not a parent profile
-         * of a managed profile, or cross profile calendar is disabled in Settings, or this uri is
+         * of a managed profile, or cross-profile calendar is disabled in Settings, or this uri is
          * queried from a package that is not whitelisted by profile owner of the managed profile
-         * via {@link DevicePolicyManager#addCrossProfileCalendarPackage(ComponentName, String)}.
+         * via
+         * {@link DevicePolicyManager#setCrossProfileCalendarPackages(ComponentName, Set)}.
          *
          * @see DevicePolicyManager#getCrossProfileCalendarPackages(ComponentName)
          * @see Settings.Secure#CROSS_PROFILE_CALENDAR_ENABLED
@@ -802,6 +806,7 @@
          *
          * @hide
          */
+        @TestApi
         public static final String[] SYNC_WRITABLE_COLUMNS = new String[] {
             ACCOUNT_NAME,
             ACCOUNT_TYPE,
@@ -1758,9 +1763,10 @@
          * projection of the query to this uri that are not contained in the above list.
          *
          * <p>This uri will return an empty cursor if the calling user is not a parent profile
-         * of a managed profile, or cross profile calendar is disabled in Settings, or this uri is
+         * of a managed profile, or cross-profile calendar is disabled in Settings, or this uri is
          * queried from a package that is not whitelisted by profile owner of the managed profile
-         * via {@link DevicePolicyManager#addCrossProfileCalendarPackage(ComponentName, String)}.
+         * via
+         * {@link DevicePolicyManager#setCrossProfileCalendarPackages(ComponentName, Set)}.
          *
          * @see DevicePolicyManager#getCrossProfileCalendarPackages(ComponentName)
          * @see Settings.Secure#CROSS_PROFILE_CALENDAR_ENABLED
@@ -1828,6 +1834,7 @@
          *
          * @hide
          */
+        @TestApi
         public static final String[] SYNC_WRITABLE_COLUMNS = new String[] {
             _SYNC_ID,
             DIRTY,
@@ -1968,10 +1975,10 @@
          * projection of the query to this uri that are not contained in the above list.
          *
          * <p>This uri will return an empty cursor if the calling user is not a parent profile
-         * of a managed profile, or cross profile calendar for the managed profile is disabled in
+         * of a managed profile, or cross-profile calendar for the managed profile is disabled in
          * Settings, or this uri is queried from a package that is not whitelisted by
          * profile owner of the managed profile via
-         * {@link DevicePolicyManager#addCrossProfileCalendarPackage(ComponentName, String)}.
+         * {@link DevicePolicyManager#setCrossProfileCalendarPackages(ComponentName, Set)}.
          *
          * @see DevicePolicyManager#getCrossProfileCalendarPackages(ComponentName)
          * @see Settings.Secure#CROSS_PROFILE_CALENDAR_ENABLED
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 87efbf3..148dd91 100644
--- a/core/java/android/provider/DeviceConfig.java
+++ b/core/java/android/provider/DeviceConfig.java
@@ -54,6 +54,7 @@
 
     /**
      * Namespace for all Game Driver features.
+     *
      * @hide
      */
     @SystemApi
@@ -104,6 +105,24 @@
     public static final String NAMESPACE_NOTIFICATION_ASSISTANT = "notification_assistant";
 
     /**
+     * Namespace for attention-based features provided by on-device machine intelligence.
+     *
+     * @hide
+     */
+    @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";
+    }
+
+    /**
      * Telephony related properties definitions.
      *
      * @hide
@@ -121,6 +140,64 @@
         String PROPERTY_RAMPING_RINGER_DURATION = "ramping_duration";
     }
 
+    /**
+     * Namespace for Full Stack Integrity to run privileged apps only in JIT mode. The flag applies
+     * at process start, so reboot is a way to bring the device to a clean state.
+     *
+     * @hide
+     */
+    @SystemApi
+    public interface FsiBoot {
+        String NAMESPACE = "fsi_boot";
+        String OOB_ENABLED = "oob_enabled";
+        String OOB_WHITELIST = "oob_whitelist";
+    }
+
+    /**
+     * Namespace for activity manager related features. These features will be applied
+     * immediately upon change.
+     *
+     * @hide
+     */
+    @SystemApi
+    public interface ActivityManager {
+        String NAMESPACE = "activity_manager";
+
+        /**
+         * App compaction flags. See {@link com.android.server.am.AppCompactor}.
+         */
+        String KEY_USE_COMPACTION = "use_compaction";
+        String KEY_COMPACT_ACTION_1 = "compact_action_1";
+        String KEY_COMPACT_ACTION_2 = "compact_action_2";
+        String KEY_COMPACT_THROTTLE_1 = "compact_throttle_1";
+        String KEY_COMPACT_THROTTLE_2 = "compact_throttle_2";
+        String KEY_COMPACT_THROTTLE_3 = "compact_throttle_3";
+        String KEY_COMPACT_THROTTLE_4 = "compact_throttle_4";
+
+        /**
+         * Maximum number of cached processes. See
+         * {@link com.android.server.am.ActivityManagerConstants}.
+         */
+        String KEY_MAX_CACHED_PROCESSES = "max_cached_processes";
+    }
+
+    /**
+     * 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 =
@@ -136,9 +213,8 @@
      * Look up the value of a property for a particular namespace.
      *
      * @param namespace The namespace containing the property to look up.
-     * @param name The name of the property to look up.
+     * @param name      The name of the property to look up.
      * @return the corresponding value, or null if not present.
-     *
      * @hide
      */
     @SystemApi
@@ -160,14 +236,13 @@
      * All properties stored for a particular scope can be reverted to their default values
      * by passing the namespace to {@link #resetToDefaults(int, String)}.
      *
-     * @param namespace The namespace containing the property to create or update.
-     * @param name The name of the property to create or update.
-     * @param value The value to store for the property.
+     * @param namespace   The namespace containing the property to create or update.
+     * @param name        The name of the property to create or update.
+     * @param value       The value to store for the property.
      * @param makeDefault Whether to make the new value the default one.
      * @return True if the value was set, false if the storage implementation throws errors.
-     * @see #resetToDefaults(int, String).
-     *
      * @hide
+     * @see #resetToDefaults(int, String).
      */
     @SystemApi
     @RequiresPermission(WRITE_DEVICE_CONFIG)
@@ -186,9 +261,8 @@
      *
      * @param resetMode The reset mode to use.
      * @param namespace Optionally, the specific namespace which resets will be limited to.
-     * @see #setProperty(String, String, String, boolean)
-     *
      * @hide
+     * @see #setProperty(String, String, String, boolean)
      */
     @SystemApi
     @RequiresPermission(WRITE_DEVICE_CONFIG)
@@ -205,12 +279,11 @@
      * will replace the old namespace and executor. Remove the listener entirely by calling
      * {@link #removeOnPropertyChangedListener(OnPropertyChangedListener)}.
      *
-     * @param namespace The namespace containing properties to monitor.
-     * @param executor The executor which will be used to run callbacks.
+     * @param namespace                 The namespace containing properties to monitor.
+     * @param executor                  The executor which will be used to run callbacks.
      * @param onPropertyChangedListener The listener to add.
-     * @see #removeOnPropertyChangedListener(OnPropertyChangedListener)
-     *
      * @hide
+     * @see #removeOnPropertyChangedListener(OnPropertyChangedListener)
      */
     @SystemApi
     @RequiresPermission(READ_DEVICE_CONFIG)
@@ -242,9 +315,8 @@
      * property changes.
      *
      * @param onPropertyChangedListener The listener to remove.
-     * @see #addOnPropertyChangedListener(String, Executor, OnPropertyChangedListener)
-     *
      * @hide
+     * @see #addOnPropertyChangedListener(String, Executor, OnPropertyChangedListener)
      */
     @SystemApi
     public static void removeOnPropertyChangedListener(
@@ -345,8 +417,8 @@
          * Called when a property has changed.
          *
          * @param namespace The namespace containing the property which has changed.
-         * @param name The name of the property which has changed.
-         * @param value The new value of the property which has changed.
+         * @param name      The name of the property which has changed.
+         * @param value     The new value of the property which has changed.
          */
         void onPropertyChanged(String namespace, String name, String value);
     }
diff --git a/core/java/android/provider/MediaStore.java b/core/java/android/provider/MediaStore.java
index 487198b..887175a 100644
--- a/core/java/android/provider/MediaStore.java
+++ b/core/java/android/provider/MediaStore.java
@@ -89,11 +89,26 @@
     /** A content:// style uri to the authority for the media provider */
     public static final Uri AUTHORITY_URI = Uri.parse("content://" + AUTHORITY);
 
-    /** {@hide} */
+    /**
+     * Volume name used for content on "internal" storage of device. This
+     * volume contains media distributed with the device, such as built-in
+     * ringtones and wallpapers.
+     */
     public static final String VOLUME_INTERNAL = "internal";
-    /** {@hide} */
+
+    /**
+     * Volume name used for content on "external" storage of device. This only
+     * includes media on the primary shared storage device; the contents of any
+     * secondary storage devices can be obtained using
+     * {@link #getAllVolumeNames(Context)}.
+     */
     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
@@ -1566,7 +1581,13 @@
         /**
          * This class provides utility methods to obtain thumbnails for various
          * {@link Images} items.
+         *
+         * @deprecated Callers should migrate to using
+         *             {@link ContentResolver#loadThumbnail}, since it offers
+         *             richer control over requested thumbnail sizes and
+         *             cancellation behavior.
          */
+        @Deprecated
         public static class Thumbnails implements BaseColumns {
             public static final Cursor query(ContentResolver cr, Uri uri, String[] projection) {
                 return cr.query(uri, projection, null, null, DEFAULT_SORT_ORDER);
@@ -2743,7 +2764,13 @@
         /**
          * This class provides utility methods to obtain thumbnails for various
          * {@link Video} items.
+         *
+         * @deprecated Callers should migrate to using
+         *             {@link ContentResolver#loadThumbnail}, since it offers
+         *             richer control over requested thumbnail sizes and
+         *             cancellation behavior.
          */
+        @Deprecated
         public static class Thumbnails implements BaseColumns {
             /**
              * Cancel any outstanding {@link #getThumbnail} requests, causing
@@ -2970,6 +2997,7 @@
      *
      * @hide
      */
+    @TestApi
     public static @NonNull File getVolumePath(@NonNull String volumeName)
             throws FileNotFoundException {
         if (TextUtils.isEmpty(volumeName)) {
@@ -3000,6 +3028,7 @@
      *
      * @hide
      */
+    @TestApi
     public static @NonNull Collection<File> getVolumeScanPaths(@NonNull String volumeName)
             throws FileNotFoundException {
         if (TextUtils.isEmpty(volumeName)) {
diff --git a/core/java/android/provider/Settings.java b/core/java/android/provider/Settings.java
index fa3f54a..f04c752 100644
--- a/core/java/android/provider/Settings.java
+++ b/core/java/android/provider/Settings.java
@@ -59,6 +59,7 @@
 import android.content.res.Resources;
 import android.database.Cursor;
 import android.database.SQLException;
+import android.hardware.display.ColorDisplayManager;
 import android.location.LocationManager;
 import android.media.AudioFormat;
 import android.net.ConnectivityManager;
@@ -89,7 +90,6 @@
 import android.view.inputmethod.InputMethodSystemProperty;
 
 import com.android.internal.annotations.GuardedBy;
-import com.android.internal.app.ColorDisplayController;
 import com.android.internal.widget.ILockSettings;
 
 import java.io.IOException;
@@ -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
@@ -3239,8 +3270,8 @@
 
         private static final Validator DISPLAY_COLOR_MODE_VALIDATOR =
                 new SettingsValidators.InclusiveIntegerRangeValidator(
-                        ColorDisplayController.COLOR_MODE_NATURAL,
-                        ColorDisplayController.COLOR_MODE_AUTOMATIC);
+                        ColorDisplayManager.COLOR_MODE_NATURAL,
+                        ColorDisplayManager.COLOR_MODE_AUTOMATIC);
 
         /**
          * The amount of time in milliseconds before the device goes to sleep or begins
@@ -7802,6 +7833,9 @@
          * or an activity that handles ACTION_ASSIST, or empty which means using the default
          * handling.
          *
+         * <p>This should be set indirectly by setting the {@link
+         * android.app.role.RoleManager#ROLE_ASSISTANT assistant role}.
+         *
          * @hide
          */
         @UnsupportedAppUsage
@@ -8236,6 +8270,16 @@
         private static final Validator NOTIFICATION_BADGING_VALIDATOR = BOOLEAN_VALIDATOR;
 
         /**
+         * Whether notifications are dismissed by a right-to-left swipe (instead of a left-to-right
+         * swipe).
+         *
+         * @hide
+         */
+        public static final String NOTIFICATION_DISMISS_RTL = "notification_dismiss_rtl";
+
+        private static final Validator NOTIFICATION_DISMISS_RTL_VALIDATOR = BOOLEAN_VALIDATOR;
+
+        /**
          * Comma separated list of QS tiles that have been auto-added already.
          * @hide
          */
@@ -8421,6 +8465,27 @@
         public static final String LOCATION_ACCESS_CHECK_DELAY_MILLIS =
                 "location_access_check_delay_millis";
 
+
+        /**
+         * Comma separated list of enabled overlay packages for all android.theme.customization.*
+         * categories. If there is no corresponding package included for a category, then all
+         * overlay packages in that category must be disabled.
+         * @hide
+         */
+        @SystemApi
+        public static final String THEME_CUSTOMIZATION_OVERLAY_PACKAGES =
+                "theme_customization_overlay_packages";
+
+        private static final Validator THEME_CUSTOMIZATION_OVERLAY_PACKAGES_VALIDATOR =
+                new SettingsValidators.PackageNameListValidator(",");
+
+        /**
+         * Controls whether aware is enabled.
+         * @hide
+         */
+        public static final String AWARE_ENABLED = "aware_enabled";
+
+        private static final Validator AWARE_ENABLED_VALIDATOR = BOOLEAN_VALIDATOR;
         /**
          * This are the settings to be backed up.
          *
@@ -8516,6 +8581,7 @@
             ASSIST_GESTURE_WAKE_ENABLED,
             VR_DISPLAY_MODE,
             NOTIFICATION_BADGING,
+            NOTIFICATION_DISMISS_RTL,
             QS_AUTO_ADDED_TILES,
             SCREENSAVER_ENABLED,
             SCREENSAVER_COMPONENTS,
@@ -8544,6 +8610,8 @@
             LOCK_SCREEN_WHEN_TRUST_LOST,
             SKIP_GESTURE,
             SILENCE_GESTURE,
+            THEME_CUSTOMIZATION_OVERLAY_PACKAGES,
+            AWARE_ENABLED,
         };
 
         /**
@@ -8676,6 +8744,7 @@
             VALIDATORS.put(ASSIST_GESTURE_WAKE_ENABLED, ASSIST_GESTURE_WAKE_ENABLED_VALIDATOR);
             VALIDATORS.put(VR_DISPLAY_MODE, VR_DISPLAY_MODE_VALIDATOR);
             VALIDATORS.put(NOTIFICATION_BADGING, NOTIFICATION_BADGING_VALIDATOR);
+            VALIDATORS.put(NOTIFICATION_DISMISS_RTL, NOTIFICATION_DISMISS_RTL_VALIDATOR);
             VALIDATORS.put(QS_AUTO_ADDED_TILES, QS_AUTO_ADDED_TILES_VALIDATOR);
             VALIDATORS.put(SCREENSAVER_ENABLED, SCREENSAVER_ENABLED_VALIDATOR);
             VALIDATORS.put(SCREENSAVER_COMPONENTS, SCREENSAVER_COMPONENTS_VALIDATOR);
@@ -8714,6 +8783,9 @@
             VALIDATORS.put(LOCK_SCREEN_WHEN_TRUST_LOST, LOCK_SCREEN_WHEN_TRUST_LOST_VALIDATOR);
             VALIDATORS.put(SKIP_GESTURE, SKIP_GESTURE_VALIDATOR);
             VALIDATORS.put(SILENCE_GESTURE, SILENCE_GESTURE_VALIDATOR);
+            VALIDATORS.put(THEME_CUSTOMIZATION_OVERLAY_PACKAGES,
+                    THEME_CUSTOMIZATION_OVERLAY_PACKAGES_VALIDATOR);
+            VALIDATORS.put(AWARE_ENABLED, AWARE_ENABLED_VALIDATOR);
         }
 
         /**
@@ -8745,6 +8817,7 @@
             CLONE_TO_MANAGED_PROFILE.add(LOCATION_CHANGER);
             CLONE_TO_MANAGED_PROFILE.add(LOCATION_MODE);
             CLONE_TO_MANAGED_PROFILE.add(LOCATION_PROVIDERS_ALLOWED);
+            CLONE_TO_MANAGED_PROFILE.add(SHOW_IME_WITH_HARD_KEYBOARD);
             if (!InputMethodSystemProperty.PER_PROFILE_IME_ENABLED) {
                 CLONE_TO_MANAGED_PROFILE.add(DEFAULT_INPUT_METHOD);
                 CLONE_TO_MANAGED_PROFILE.add(ENABLED_INPUT_METHODS);
@@ -9452,23 +9525,6 @@
                 "hdmi_control_auto_device_off_enabled";
 
         /**
-         * If <b>true</b>, enables out-of-the-box execution for priv apps.
-         * Default: false
-         * Values: 0 = false, 1 = true
-         *
-         * @hide
-         */
-        public static final String PRIV_APP_OOB_ENABLED = "priv_app_oob_enabled";
-
-        /**
-         * Comma separated list of privileged package names, which will be running out-of-box APK.
-         * Default: "ALL"
-         *
-         * @hide
-         */
-        public static final String PRIV_APP_OOB_LIST = "priv_app_oob_list";
-
-        /**
          * The interval in milliseconds at which location requests will be throttled when they are
          * coming from the background.
          *
@@ -9493,6 +9549,14 @@
             "location_background_throttle_package_whitelist";
 
         /**
+         * Packages that are whitelisted for ignoring location settings (may retrieve location even
+         * when user location settings are off), for emergency purposes.
+         * @hide
+         */
+        public static final String LOCATION_IGNORE_SETTINGS_PACKAGE_WHITELIST =
+                "location_ignore_settings_package_whitelist";
+
+        /**
          * Whether to disable location status callbacks in preparation for deprecation.
          * @hide
          */
@@ -11690,6 +11754,7 @@
          * entity_list_default                      (String[])
          * entity_list_not_editable                 (String[])
          * entity_list_editable                     (String[])
+         * lang_id_threshold_override               (float)
          * </pre>
          *
          * <p>
@@ -12121,6 +12186,13 @@
                 "angle_gl_driver_selection_values";
 
         /**
+         * List of package names that should check ANGLE rules
+         * @hide
+         */
+        public static final String GLOBAL_SETTINGS_ANGLE_WHITELIST =
+                "angle_whitelist";
+
+        /**
          * Game Update Package global preference for all Apps.
          * 0 = Default
          * 1 = All Apps use Game Update Package
@@ -12150,6 +12222,14 @@
         public static final String GUP_BLACKLIST = "gup_blacklist";
 
         /**
+         * Apps on the whitelist that are allowed to use Game Driver.
+         * The string is a list of application package names, seperated by comma.
+         * i.e. <apk1>,<apk2>,...,<apkN>
+         * @hide
+         */
+        public static final String GAME_DRIVER_WHITELIST = "game_driver_whitelist";
+
+        /**
          * Ordered GPU debug layer list for Vulkan
          * i.e. <layer1>:<layer2>:...:<layerN>
          * @hide
@@ -12194,6 +12274,31 @@
         public static final String LOW_POWER_MODE_STICKY = "low_power_sticky";
 
         /**
+         * When a device is unplugged from a changer (or is rebooted), do not re-activate battery
+         * saver even if {@link #LOW_POWER_MODE_STICKY} is 1, if the battery level is equal to or
+         * above this threshold.
+         *
+         * @hide
+         */
+        public static final String LOW_POWER_MODE_STICKY_AUTO_DISABLE_LEVEL =
+                "low_power_sticky_auto_disable_level";
+
+        private static final Validator LOW_POWER_MODE_STICKY_AUTO_DISABLE_LEVEL_VALIDATOR =
+                new SettingsValidators.InclusiveIntegerRangeValidator(0, 100);
+
+        /**
+         * Whether sticky battery saver should be deactivated once the battery level has reached the
+         * threshold specified by {@link #LOW_POWER_MODE_STICKY_AUTO_DISABLE_LEVEL}.
+         *
+         * @hide
+         */
+        public static final String LOW_POWER_MODE_STICKY_AUTO_DISABLE_ENABLED =
+                "low_power_sticky_auto_disable_enabled";
+
+        private static final Validator LOW_POWER_MODE_STICKY_AUTO_DISABLE_ENABLED_VALIDATOR =
+                new SettingsValidators.DiscreteValueValidator(new String[] {"0", "1"});
+
+        /**
          * Battery level [1-100] at which low power mode automatically turns on.
          * Pre-Q If 0, it will not automatically turn on. Q and newer it will only automatically
          * turn on if the {@link #AUTOMATIC_POWER_SAVER_MODE} setting is also set to
@@ -12205,7 +12310,6 @@
          */
         public static final String LOW_POWER_MODE_TRIGGER_LEVEL = "low_power_trigger_level";
 
-
         private static final Validator LOW_POWER_MODE_TRIGGER_LEVEL_VALIDATOR =
                 new SettingsValidators.InclusiveIntegerRangeValidator(0, 100);
 
@@ -12959,28 +13063,6 @@
                 "sms_access_restriction_enabled";
 
         /**
-         * If set to 1, an app must have the READ_PRIVILEGED_PHONE_STATE permission (or be a device
-         * / profile owner with the READ_PHONE_STATE permission) to access device identifiers.
-         *
-         * STOPSHIP: Remove this once we ship with the new device identifier check enabled.
-         *
-         * @hide
-         */
-        public static final String PRIVILEGED_DEVICE_IDENTIFIER_CHECK_ENABLED =
-                "privileged_device_identifier_check_enabled";
-
-        /**
-         * If set to 1, an app that is targeting Q and does not meet the new requirements to access
-         * device identifiers will receive a SecurityException.
-         *
-         * STOPSHIP: Remove this once we ship with the new device identifier check enabled.
-         *
-         * @hide
-         */
-        public static final String PRIVILEGED_DEVICE_IDENTIFIER_TARGET_Q_BEHAVIOR_ENABLED =
-                "privileged_device_identifier_target_q_behavior_enabled";
-
-        /**
          * If set to 1, the device identifier check will be relaxed to the previous READ_PHONE_STATE
          * permission check for 3P apps.
          *
@@ -13003,6 +13085,17 @@
                 "privileged_device_identifier_non_priv_check_relaxed";
 
         /**
+         * If set to 1, the device identifier check will be relaxed to the previous READ_PHONE_STATE
+         * permission check for preloaded privileged apps.
+         *
+         * STOPSHIP: Remove this once we ship with the new device identifier check enabled.
+         *
+         * @hide
+         */
+        public static final String PRIVILEGED_DEVICE_IDENTIFIER_PRIV_CHECK_RELAXED =
+                "privileged_device_identifier_priv_check_relaxed";
+
+        /**
          * If set to 1, SettingsProvider's restoreAnyVersion="true" attribute will be ignored
          * and restoring to lower version of platform API will be skipped.
          *
@@ -13196,6 +13289,8 @@
             ENCODED_SURROUND_OUTPUT,
             ENCODED_SURROUND_OUTPUT_ENABLED_FORMATS,
             LOW_POWER_MODE_TRIGGER_LEVEL,
+            LOW_POWER_MODE_STICKY_AUTO_DISABLE_ENABLED,
+            LOW_POWER_MODE_STICKY_AUTO_DISABLE_LEVEL,
             BLUETOOTH_ON,
             PRIVATE_DNS_MODE,
             PRIVATE_DNS_SPECIFIER,
@@ -13234,6 +13329,10 @@
             VALIDATORS.put(ENCODED_SURROUND_OUTPUT, ENCODED_SURROUND_OUTPUT_VALIDATOR);
             VALIDATORS.put(ENCODED_SURROUND_OUTPUT_ENABLED_FORMATS,
                     ENCODED_SURROUND_OUTPUT_ENABLED_FORMATS_VALIDATOR);
+            VALIDATORS.put(LOW_POWER_MODE_STICKY_AUTO_DISABLE_LEVEL,
+                    LOW_POWER_MODE_STICKY_AUTO_DISABLE_LEVEL_VALIDATOR);
+            VALIDATORS.put(LOW_POWER_MODE_STICKY_AUTO_DISABLE_ENABLED,
+                    LOW_POWER_MODE_STICKY_AUTO_DISABLE_ENABLED_VALIDATOR);
             VALIDATORS.put(LOW_POWER_MODE_TRIGGER_LEVEL, LOW_POWER_MODE_TRIGGER_LEVEL_VALIDATOR);
             VALIDATORS.put(LOW_POWER_MODE_TRIGGER_LEVEL_MAX,
                     LOW_POWER_MODE_TRIGGER_LEVEL_VALIDATOR);
@@ -13748,6 +13847,19 @@
                 "user_preferred_sub2","user_preferred_sub3"};
 
         /**
+         * Which subscription is enabled for a physical slot.
+         * @hide
+         */
+        public static final String ENABLED_SUBSCRIPTION_FOR_SLOT = "enabled_subscription_for_slot";
+
+        /**
+         * Whether corresponding logical modem is enabled for a physical slot.
+         * The value 1 - enable, 0 - disable
+         * @hide
+         */
+        public static final String MODEM_STACK_ENABLED_FOR_SLOT = "modem_stack_enabled_for_slot";
+
+        /**
          * Whether to enable new contacts aggregator or not.
          * The value 1 - enable, 0 - disable
          * @hide
@@ -14201,6 +14313,17 @@
          */
         public static final String APPOP_HISTORY_PARAMETERS =
                 "appop_history_parameters";
+
+        /**
+         * Delay for sending ACTION_CHARGING after device is plugged in.
+         * This is used as an override for constants defined in BatteryStatsImpl for
+         * ease of experimentation.
+         *
+         * @see com.android.internal.os.BatteryStatsImpl.Constants.KEY_BATTERY_CHARGED_DELAY_MS
+         * @hide
+         */
+        public static final String BATTERY_CHARGING_STATE_UPDATE_DELAY =
+                "battery_charging_state_update_delay";
     }
 
     /**
@@ -14524,6 +14647,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/contentcapture/ContentCaptureService.java b/core/java/android/service/contentcapture/ContentCaptureService.java
index 302e1a6..020de7f 100644
--- a/core/java/android/service/contentcapture/ContentCaptureService.java
+++ b/core/java/android/service/contentcapture/ContentCaptureService.java
@@ -116,6 +116,13 @@
             mHandler.sendMessage(obtainMessage(ContentCaptureService::handleFinishSession,
                     ContentCaptureService.this, sessionId));
         }
+
+        @Override
+        public void onUserDataRemovalRequest(UserDataRemovalRequest request) {
+            mHandler.sendMessage(
+                    obtainMessage(ContentCaptureService::handleOnUserDataRemovalRequest,
+                            ContentCaptureService.this, request));
+        }
     };
 
     /**
@@ -431,6 +438,10 @@
         onDestroyContentCaptureSession(new ContentCaptureSessionId(sessionId));
     }
 
+    private void handleOnUserDataRemovalRequest(@NonNull UserDataRemovalRequest request) {
+        onUserDataRemovalRequest(request);
+    }
+
     /**
      * Checks if the given {@code uid} owns the session associated with the event.
      */
diff --git a/core/java/android/service/contentcapture/IContentCaptureService.aidl b/core/java/android/service/contentcapture/IContentCaptureService.aidl
index a8dd213..d92fb3b 100644
--- a/core/java/android/service/contentcapture/IContentCaptureService.aidl
+++ b/core/java/android/service/contentcapture/IContentCaptureService.aidl
@@ -19,6 +19,7 @@
 import android.os.IBinder;
 import android.service.contentcapture.SnapshotData;
 import android.view.contentcapture.ContentCaptureContext;
+import android.view.contentcapture.UserDataRemovalRequest;
 
 import com.android.internal.os.IResultReceiver;
 
@@ -36,4 +37,5 @@
                           in IResultReceiver clientReceiver);
     void onSessionFinished(String sessionId);
     void onActivitySnapshot(String sessionId, in SnapshotData snapshotData);
+    void onUserDataRemovalRequest(in UserDataRemovalRequest request);
 }
diff --git a/core/java/android/service/notification/Adjustment.java b/core/java/android/service/notification/Adjustment.java
index de532b7..93189b3 100644
--- a/core/java/android/service/notification/Adjustment.java
+++ b/core/java/android/service/notification/Adjustment.java
@@ -15,8 +15,6 @@
  */
 package android.service.notification;
 
-import android.annotation.SystemApi;
-import android.annotation.TestApi;
 import android.app.Notification;
 import android.os.Bundle;
 import android.os.Parcel;
@@ -24,10 +22,7 @@
 
 /**
  * Ranking updates from the Assistant.
- * @hide
  */
-@SystemApi
-@TestApi
 public final class Adjustment implements Parcelable {
     private final String mPackage;
     private final String mKey;
@@ -39,6 +34,7 @@
      * Data type: ArrayList of {@code String}, where each is a representation of a
      * {@link android.provider.ContactsContract.Contacts#CONTENT_LOOKUP_URI}.
      * See {@link android.app.Notification.Builder#addPerson(String)}.
+     * @hide
      */
     public static final String KEY_PEOPLE = "key_people";
     /**
@@ -46,6 +42,7 @@
      * users. If a user chooses to snooze a notification until one of these criterion, the
      * assistant will be notified via
      * {@link NotificationAssistantService#onNotificationSnoozedUntilContext}.
+     * @hide
      */
     public static final String KEY_SNOOZE_CRITERIA = "key_snooze_criteria";
     /**
@@ -66,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.
@@ -112,7 +111,7 @@
         mUser = user;
     }
 
-    protected Adjustment(Parcel in) {
+    private Adjustment(Parcel in) {
         if (in.readInt() == 1) {
             mPackage = in.readString();
         } else {
diff --git a/core/java/android/service/notification/NotificationAssistantService.java b/core/java/android/service/notification/NotificationAssistantService.java
index ad34ab3..e93b158 100644
--- a/core/java/android/service/notification/NotificationAssistantService.java
+++ b/core/java/android/service/notification/NotificationAssistantService.java
@@ -21,8 +21,6 @@
 import android.annotation.IntDef;
 import android.annotation.NonNull;
 import android.annotation.SdkConstant;
-import android.annotation.SystemApi;
-import android.annotation.TestApi;
 import android.app.Notification;
 import android.app.NotificationChannel;
 import android.app.admin.DevicePolicyManager;
@@ -61,11 +59,7 @@
  * <p>
  *     All callbacks are called on the main thread.
  * </p>
- *
- * @hide
  */
-@SystemApi
-@TestApi
 public abstract class NotificationAssistantService extends NotificationListenerService {
     private static final String TAG = "NotificationAssistants";
 
@@ -109,6 +103,7 @@
      *
      * @param sbn the notification to snooze
      * @param snoozeCriterionId the {@link SnoozeCriterion#getId()} representing a device context.
+     * @hide
      */
     abstract public void onNotificationSnoozedUntilContext(StatusBarNotification sbn,
             String snoozeCriterionId);
@@ -250,6 +245,7 @@
      * {@link #onNotificationPosted(StatusBarNotification, RankingMap)} callback for the
      * notification.
      * @param key The key of the notification to snooze
+     * @hide
      */
     public final void unsnoozeNotification(String key) {
         if (!isBound()) return;
diff --git a/core/java/android/service/notification/NotificationListenerService.java b/core/java/android/service/notification/NotificationListenerService.java
index 0e63cd37..c734b63 100644
--- a/core/java/android/service/notification/NotificationListenerService.java
+++ b/core/java/android/service/notification/NotificationListenerService.java
@@ -1617,14 +1617,16 @@
         }
 
         /**
-         * @hide
+         * Returns a list of smart {@link Notification.Action} that can be added by the
+         * {@link NotificationAssistantService}
          */
         public List<Notification.Action> getSmartActions() {
             return mSmartActions;
         }
 
         /**
-         * @hide
+         * Returns a list of smart replies that can be added by the
+         * {@link NotificationAssistantService}
          */
         public List<CharSequence> getSmartReplies() {
             return mSmartReplies;
diff --git a/core/java/android/service/notification/NotificationStats.java b/core/java/android/service/notification/NotificationStats.java
index e5f3dfb..814b477 100644
--- a/core/java/android/service/notification/NotificationStats.java
+++ b/core/java/android/service/notification/NotificationStats.java
@@ -16,8 +16,6 @@
 package android.service.notification;
 
 import android.annotation.IntDef;
-import android.annotation.SystemApi;
-import android.annotation.TestApi;
 import android.app.RemoteInput;
 import android.os.Parcel;
 import android.os.Parcelable;
@@ -25,11 +23,6 @@
 import java.lang.annotation.Retention;
 import java.lang.annotation.RetentionPolicy;
 
-/**
- * @hide
- */
-@TestApi
-@SystemApi
 public final class NotificationStats implements Parcelable {
 
     private boolean mSeen;
@@ -105,7 +98,7 @@
     public NotificationStats() {
     }
 
-    protected NotificationStats(Parcel in) {
+    private NotificationStats(Parcel in) {
         mSeen = in.readByte() != 0;
         mExpanded = in.readByte() != 0;
         mDirectReplied = in.readByte() != 0;
diff --git a/core/java/android/service/voice/VoiceInteractionService.java b/core/java/android/service/voice/VoiceInteractionService.java
index e105fdf..2789651 100644
--- a/core/java/android/service/voice/VoiceInteractionService.java
+++ b/core/java/android/service/voice/VoiceInteractionService.java
@@ -16,6 +16,7 @@
 
 package android.service.voice;
 
+import android.annotation.IntDef;
 import android.annotation.NonNull;
 import android.annotation.Nullable;
 import android.annotation.SdkConstant;
@@ -40,6 +41,8 @@
 
 import java.io.FileDescriptor;
 import java.io.PrintWriter;
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
 import java.util.ArrayList;
 import java.util.List;
 import java.util.Locale;
@@ -77,6 +80,33 @@
      */
     public static final String SERVICE_META_DATA = "android.voice_interaction";
 
+    /** @hide */
+    @Retention(RetentionPolicy.SOURCE)
+    @IntDef(prefix = {"VOICE_STATE_"}, value = {
+            VOICE_STATE_NONE,
+            VOICE_STATE_CONDITIONAL_LISTENING,
+            VOICE_STATE_LISTENING,
+            VOICE_STATE_FULFILLING})
+    public @interface VoiceState {
+    }
+
+    /**
+     * Voice assistant inactive.
+     */
+    public static final int VOICE_STATE_NONE = 0;
+    /**
+     * Voice assistant listening, but will only trigger if it hears a request it can fulfill.
+     */
+    public static final int VOICE_STATE_CONDITIONAL_LISTENING = 1;
+    /**
+     * Voice assistant is listening to user speech.
+     */
+    public static final int VOICE_STATE_LISTENING = 2;
+    /**
+     * Voice assistant is fulfilling an action requested by the user.
+     */
+    public static final int VOICE_STATE_FULFILLING = 3;
+
     IVoiceInteractionService mInterface = new IVoiceInteractionService.Stub() {
         @Override
         public void ready() {
@@ -341,6 +371,43 @@
         }
     }
 
+    /**
+     * Requests that the voice state UI indicate the given state.
+     *
+     * @param state value indicating whether the assistant is listening, fulfilling, etc.
+     */
+    public final void setVoiceState(@VoiceState int state) {
+        try {
+            mSystemService.setVoiceState(state);
+        } catch (RemoteException e) {
+            throw e.rethrowFromSystemServer();
+        }
+    }
+
+    /**
+     * Displays the given voice transcription contents.
+     */
+    public final void setTranscription(@NonNull String transcription) {
+        try {
+            mSystemService.setTranscription(transcription);
+        } catch (RemoteException e) {
+            throw e.rethrowFromSystemServer();
+        }
+    }
+
+    /**
+     * Hides transcription.
+     *
+     * @param immediate if {@code true}, remove before transcription animation completes.
+     */
+    public final void clearTranscription(boolean immediate) {
+        try {
+            mSystemService.clearTranscription(immediate);
+        } catch (RemoteException e) {
+            throw e.rethrowFromSystemServer();
+        }
+    }
+
     @Override
     protected void dump(FileDescriptor fd, PrintWriter pw, String[] args) {
         pw.println("VOICE INTERACTION");
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/util/LongArrayQueue.java b/core/java/android/util/LongArrayQueue.java
new file mode 100644
index 0000000..d5f0484
--- /dev/null
+++ b/core/java/android/util/LongArrayQueue.java
@@ -0,0 +1,165 @@
+/*
+ * 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.util;
+
+import com.android.internal.util.ArrayUtils;
+import com.android.internal.util.GrowingArrayUtils;
+
+import libcore.util.EmptyArray;
+
+import java.util.NoSuchElementException;
+
+/**
+ * A lightweight implementation for a queue with long values.
+ * Additionally supports getting an element with a specified position from the head of the queue.
+ * The queue grows in size if needed to accommodate new elements.
+ *
+ * @hide
+ */
+public class LongArrayQueue {
+
+    private long[] mValues;
+    private int mSize;
+    private int mHead;
+    private int mTail;
+
+    /**
+     * Initializes a queue with the given starting capacity.
+     *
+     * @param initialCapacity the capacity.
+     */
+    public LongArrayQueue(int initialCapacity) {
+        if (initialCapacity == 0) {
+            mValues = EmptyArray.LONG;
+        } else {
+            mValues = ArrayUtils.newUnpaddedLongArray(initialCapacity);
+        }
+        mSize = 0;
+        mHead = mTail = 0;
+    }
+
+    /**
+     * Initializes a queue with default starting capacity.
+     */
+    public LongArrayQueue() {
+        this(16);
+    }
+
+    private void grow() {
+        if (mSize < mValues.length) {
+            throw new IllegalStateException("Queue not full yet!");
+        }
+        final int newSize = GrowingArrayUtils.growSize(mSize);
+        final long[] newArray = ArrayUtils.newUnpaddedLongArray(newSize);
+        final int r = mValues.length - mHead; // Number of elements on and to the right of head.
+        System.arraycopy(mValues, mHead, newArray, 0, r);
+        System.arraycopy(mValues, 0, newArray, r, mHead);
+        mValues = newArray;
+        mHead = 0;
+        mTail = mSize;
+    }
+
+    /**
+     * Returns the number of elements in the queue.
+     */
+    public int size() {
+        return mSize;
+    }
+
+    /**
+     * Removes all elements from this queue.
+     */
+    public void clear() {
+        mSize = 0;
+        mHead = mTail = 0;
+    }
+
+    /**
+     * Adds a value to the tail of the queue.
+     *
+     * @param value the value to be added.
+     */
+    public void addLast(long value) {
+        if (mSize == mValues.length) {
+            grow();
+        }
+        mValues[mTail] = value;
+        mTail = (mTail + 1) % mValues.length;
+        mSize++;
+    }
+
+    /**
+     * Removes an element from the head of the queue.
+     *
+     * @return the element at the head of the queue.
+     * @throws NoSuchElementException if the queue is empty.
+     */
+    public long removeFirst() {
+        if (mSize == 0) {
+            throw new NoSuchElementException("Queue is empty!");
+        }
+        final long ret = mValues[mHead];
+        mHead = (mHead + 1) % mValues.length;
+        mSize--;
+        return ret;
+    }
+
+    /**
+     * Returns the element at the given position from the head of the queue, where 0 represents the
+     * head of the queue.
+     *
+     * @param position the position from the head of the queue.
+     * @return the element found at the given position.
+     * @throws IndexOutOfBoundsException if {@code position} < {@code 0} or
+     *                                   {@code position} >= {@link #size()}
+     */
+    public long get(int position) {
+        if (position < 0 || position >= mSize) {
+            throw new IndexOutOfBoundsException("Index " + position
+                    + " not valid for a queue of size " + mSize);
+        }
+        final int index = (mHead + position) % mValues.length;
+        return mValues[index];
+    }
+
+    /**
+     * Returns the element at the head of the queue, without removing it.
+     *
+     * @return the element at the head of the queue.
+     * @throws NoSuchElementException if the queue is empty
+     */
+    public long peekFirst() {
+        if (mSize == 0) {
+            throw new NoSuchElementException("Queue is empty!");
+        }
+        return mValues[mHead];
+    }
+
+    /**
+     * Returns the element at the tail of the queue.
+     *
+     * @return the element at the tail of the queue.
+     * @throws NoSuchElementException if the queue is empty.
+     */
+    public long peekLast() {
+        if (mSize == 0) {
+            throw new NoSuchElementException("Queue is empty!");
+        }
+        final int index = (mTail == 0) ? mValues.length - 1 : mTail - 1;
+        return mValues[index];
+    }
+}
diff --git a/core/java/android/view/Display.java b/core/java/android/view/Display.java
index f58efc9..e3a6bd7 100644
--- a/core/java/android/view/Display.java
+++ b/core/java/android/view/Display.java
@@ -26,6 +26,7 @@
 import android.content.res.CompatibilityInfo;
 import android.content.res.Configuration;
 import android.content.res.Resources;
+import android.graphics.ColorSpace;
 import android.graphics.PixelFormat;
 import android.graphics.Point;
 import android.graphics.Rect;
@@ -953,6 +954,24 @@
     }
 
     /**
+     * Returns the preferred wide color space of the Display.
+     * The returned wide gamut color space is based on hardware capability and
+     * is preferred by the composition pipeline.
+     * Returns null if the display doesn't support wide color gamut.
+     * {@link Display#isWideColorGamut()}.
+     */
+    @Nullable
+    public ColorSpace getPreferredWideGamutColorSpace() {
+        synchronized (this) {
+            updateDisplayInfoLocked();
+            if (mDisplayInfo.isWideColorGamut()) {
+                return mGlobal.getPreferredWideGamutColorSpace();
+            }
+            return null;
+        }
+    }
+
+    /**
      * Gets the supported color modes of this device.
      * @hide
      */
diff --git a/core/java/android/view/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..7c776f8 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) {
@@ -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..a6af1a2 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) {
diff --git a/core/java/android/view/RemoteAnimationAdapter.java b/core/java/android/view/RemoteAnimationAdapter.java
index 3c9ce78..6f5a85d 100644
--- a/core/java/android/view/RemoteAnimationAdapter.java
+++ b/core/java/android/view/RemoteAnimationAdapter.java
@@ -18,7 +18,6 @@
 
 import android.annotation.UnsupportedAppUsage;
 import android.app.ActivityOptions;
-import android.os.IBinder;
 import android.os.Parcel;
 import android.os.Parcelable;
 
@@ -52,6 +51,7 @@
     private final IRemoteAnimationRunner mRunner;
     private final long mDuration;
     private final long mStatusBarTransitionDelay;
+    private final boolean mChangeNeedsSnapshot;
 
     /** @see #getCallingPid */
     private int mCallingPid;
@@ -59,21 +59,31 @@
     /**
      * @param runner The interface that gets notified when we actually need to start the animation.
      * @param duration The duration of the animation.
+     * @param changeNeedsSnapshot For change transitions, whether this should create a snapshot by
+     *                            screenshotting the task.
      * @param statusBarTransitionDelay The desired delay for all visual animations in the
      *        status bar caused by this app animation in millis.
      */
     @UnsupportedAppUsage
     public RemoteAnimationAdapter(IRemoteAnimationRunner runner, long duration,
-            long statusBarTransitionDelay) {
+            long statusBarTransitionDelay, boolean changeNeedsSnapshot) {
         mRunner = runner;
         mDuration = duration;
+        mChangeNeedsSnapshot = changeNeedsSnapshot;
         mStatusBarTransitionDelay = statusBarTransitionDelay;
     }
 
+    @UnsupportedAppUsage
+    public RemoteAnimationAdapter(IRemoteAnimationRunner runner, long duration,
+            long statusBarTransitionDelay) {
+        this(runner, duration, statusBarTransitionDelay, false /* changeNeedsSnapshot */);
+    }
+
     public RemoteAnimationAdapter(Parcel in) {
         mRunner = IRemoteAnimationRunner.Stub.asInterface(in.readStrongBinder());
         mDuration = in.readLong();
         mStatusBarTransitionDelay = in.readLong();
+        mChangeNeedsSnapshot = in.readBoolean();
     }
 
     public IRemoteAnimationRunner getRunner() {
@@ -88,6 +98,10 @@
         return mStatusBarTransitionDelay;
     }
 
+    public boolean getChangeNeedsSnapshot() {
+        return mChangeNeedsSnapshot;
+    }
+
     /**
      * To be called by system_server to keep track which pid is running this animation.
      */
@@ -112,6 +126,7 @@
         dest.writeStrongInterface(mRunner);
         dest.writeLong(mDuration);
         dest.writeLong(mStatusBarTransitionDelay);
+        dest.writeBoolean(mChangeNeedsSnapshot);
     }
 
     public static final Creator<RemoteAnimationAdapter> CREATOR
diff --git a/core/java/android/view/RemoteAnimationTarget.java b/core/java/android/view/RemoteAnimationTarget.java
index 567b279..1d2cf4b 100644
--- a/core/java/android/view/RemoteAnimationTarget.java
+++ b/core/java/android/view/RemoteAnimationTarget.java
@@ -24,6 +24,8 @@
 import static android.view.RemoteAnimationTargetProto.POSITION;
 import static android.view.RemoteAnimationTargetProto.PREFIX_ORDER_INDEX;
 import static android.view.RemoteAnimationTargetProto.SOURCE_CONTAINER_BOUNDS;
+import static android.view.RemoteAnimationTargetProto.START_BOUNDS;
+import static android.view.RemoteAnimationTargetProto.START_LEASH;
 import static android.view.RemoteAnimationTargetProto.TASK_ID;
 import static android.view.RemoteAnimationTargetProto.WINDOW_CONFIGURATION;
 
@@ -57,9 +59,15 @@
      */
     public static final int MODE_CLOSING = 1;
 
+    /**
+     * The app is in the set of resizing apps (eg. mode change) of this transition.
+     */
+    public static final int MODE_CHANGING = 2;
+
     @IntDef(prefix = { "MODE_" }, value = {
             MODE_OPENING,
-            MODE_CLOSING
+            MODE_CLOSING,
+            MODE_CHANGING
     })
     @Retention(RetentionPolicy.SOURCE)
     public @interface Mode {}
@@ -83,6 +91,13 @@
     public final SurfaceControl leash;
 
     /**
+     * The {@link SurfaceControl} for the starting state of a target if this transition is
+     * MODE_CHANGING, {@code null)} otherwise. This is relative to the app window.
+     */
+    @UnsupportedAppUsage
+    public final SurfaceControl startLeash;
+
+    /**
      * Whether the app is translucent and may reveal apps behind.
      */
     @UnsupportedAppUsage
@@ -128,6 +143,15 @@
     public final Rect sourceContainerBounds;
 
     /**
+     * The starting bounds of the source container in screen space coordinates. This is {@code null}
+     * if the animation target isn't MODE_CHANGING. Since this is the starting bounds, it's size
+     * should be equivalent to the size of the starting thumbnail. Note that sourceContainerBounds
+     * is the end bounds of a change transition.
+     */
+    @UnsupportedAppUsage
+    public final Rect startBounds;
+
+    /**
      * The window configuration for the target.
      */
     @UnsupportedAppUsage
@@ -141,7 +165,8 @@
 
     public RemoteAnimationTarget(int taskId, int mode, SurfaceControl leash, boolean isTranslucent,
             Rect clipRect, Rect contentInsets, int prefixOrderIndex, Point position,
-            Rect sourceContainerBounds, WindowConfiguration windowConfig, boolean isNotInRecents) {
+            Rect sourceContainerBounds, WindowConfiguration windowConfig, boolean isNotInRecents,
+            SurfaceControl startLeash, Rect startBounds) {
         this.mode = mode;
         this.taskId = taskId;
         this.leash = leash;
@@ -153,6 +178,8 @@
         this.sourceContainerBounds = new Rect(sourceContainerBounds);
         this.windowConfiguration = windowConfig;
         this.isNotInRecents = isNotInRecents;
+        this.startLeash = startLeash;
+        this.startBounds = startBounds == null ? null : new Rect(startBounds);
     }
 
     public RemoteAnimationTarget(Parcel in) {
@@ -167,6 +194,8 @@
         sourceContainerBounds = in.readParcelable(null);
         windowConfiguration = in.readParcelable(null);
         isNotInRecents = in.readBoolean();
+        startLeash = in.readParcelable(null);
+        startBounds = in.readParcelable(null);
     }
 
     @Override
@@ -187,6 +216,8 @@
         dest.writeParcelable(sourceContainerBounds, 0 /* flags */);
         dest.writeParcelable(windowConfiguration, 0 /* flags */);
         dest.writeBoolean(isNotInRecents);
+        dest.writeParcelable(startLeash, 0 /* flags */);
+        dest.writeParcelable(startBounds, 0 /* flags */);
     }
 
     public void dump(PrintWriter pw, String prefix) {
@@ -215,6 +246,8 @@
         position.writeToProto(proto, POSITION);
         sourceContainerBounds.writeToProto(proto, SOURCE_CONTAINER_BOUNDS);
         windowConfiguration.writeToProto(proto, WINDOW_CONFIGURATION);
+        startLeash.writeToProto(proto, START_LEASH);
+        startBounds.writeToProto(proto, START_BOUNDS);
         proto.end(token);
     }
 
diff --git a/core/java/android/view/SurfaceControl.java b/core/java/android/view/SurfaceControl.java
index 863b717..4032a6b8 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 9d0c9f4..cd3decf 100644
--- a/core/java/android/view/View.java
+++ b/core/java/android/view/View.java
@@ -3985,6 +3985,15 @@
     public static final int SCROLL_AXIS_VERTICAL = 1 << 1;
 
     /**
+     * If a MotionEvent has CLASSIFICATION_AMBIGUOUS_GESTURE set, then certain the default
+     * long press action will be inhibited. However, to account for the possibility of incorrect
+     * classification, the default long press timeout will instead be increased for some situations
+     * by the following factor.
+     * Likewise, the touch slop for allowing long press will be increased when gesture is uncertain.
+     */
+    private static final int AMBIGUOUS_GESTURE_MULTIPLIER = 2;
+
+    /**
      * Controls the over-scroll mode for this view.
      * See {@link #overScrollBy(int, int, int, int, int, int, int, int, boolean)},
      * {@link #OVER_SCROLL_ALWAYS}, {@link #OVER_SCROLL_IF_CONTENT_SCROLLS},
@@ -9058,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);
         }
     }
 
@@ -9154,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();
@@ -13994,7 +14011,7 @@
                     if (clickable) {
                         setPressed(true, x, y);
                     }
-                    checkForLongClick(0, x, y);
+                    checkForLongClick(ViewConfiguration.getLongPressTimeout(), x, y);
                     return true;
                 }
             }
@@ -14735,7 +14752,7 @@
                     mHasPerformedLongPress = false;
 
                     if (!clickable) {
-                        checkForLongClick(0, x, y);
+                        checkForLongClick(ViewConfiguration.getLongPressTimeout(), x, y);
                         break;
                     }
 
@@ -14759,7 +14776,7 @@
                     } else {
                         // Not inside a scrolling container, so show the feedback right away
                         setPressed(true, x, y);
-                        checkForLongClick(0, x, y);
+                        checkForLongClick(ViewConfiguration.getLongPressTimeout(), x, y);
                     }
                     break;
 
@@ -14780,8 +14797,27 @@
                         drawableHotspotChanged(x, y);
                     }
 
+                    final int motionClassification = event.getClassification();
+                    final boolean ambiguousGesture =
+                            motionClassification == MotionEvent.CLASSIFICATION_AMBIGUOUS_GESTURE;
+                    int touchSlop = mTouchSlop;
+                    if (ambiguousGesture && hasPendingLongPressCallback()) {
+                        if (!pointInView(x, y, touchSlop)) {
+                            // The default action here is to cancel long press. But instead, we
+                            // just extend the timeout here, in case the classification
+                            // stays ambiguous.
+                            removeLongPressCallback();
+                            long delay = ViewConfiguration.getLongPressTimeout()
+                                    * AMBIGUOUS_GESTURE_MULTIPLIER;
+                            // Subtract the time already spent
+                            delay -= event.getEventTime() - event.getDownTime();
+                            checkForLongClick(delay, x, y);
+                        }
+                        touchSlop *= AMBIGUOUS_GESTURE_MULTIPLIER;
+                    }
+
                     // Be lenient about moving outside of buttons
-                    if (!pointInView(x, y, mTouchSlop)) {
+                    if (!pointInView(x, y, touchSlop)) {
                         // Outside button
                         // Remove any future long press/tap checks
                         removeTapCallback();
@@ -14791,6 +14827,15 @@
                         }
                         mPrivateFlags3 &= ~PFLAG3_FINGER_DOWN;
                     }
+
+                    final boolean deepPress =
+                            motionClassification == MotionEvent.CLASSIFICATION_DEEP_PRESS;
+                    if (deepPress && hasPendingLongPressCallback()) {
+                        // process the long click action immediately
+                        removeLongPressCallback();
+                        checkForLongClick(0 /* send immediately */, x, y);
+                    }
+
                     break;
             }
 
@@ -14825,6 +14870,21 @@
     }
 
     /**
+     * Return true if the long press callback is scheduled to run sometime in the future.
+     * Return false if there is no scheduled long press callback at the moment.
+     */
+    private boolean hasPendingLongPressCallback() {
+        if (mPendingCheckForLongPress == null) {
+            return false;
+        }
+        final AttachInfo attachInfo = mAttachInfo;
+        if (attachInfo == null) {
+            return false;
+        }
+        return attachInfo.mHandler.hasCallbacks(mPendingCheckForLongPress);
+    }
+
+   /**
      * Remove the pending click action
      */
     @UnsupportedAppUsage
@@ -25434,7 +25494,7 @@
         }
     }
 
-    private void checkForLongClick(int delayOffset, float x, float y) {
+    private void checkForLongClick(long delay, float x, float y) {
         if ((mViewFlags & LONG_CLICKABLE) == LONG_CLICKABLE || (mViewFlags & TOOLTIP) == TOOLTIP) {
             mHasPerformedLongPress = false;
 
@@ -25444,8 +25504,7 @@
             mPendingCheckForLongPress.setAnchor(x, y);
             mPendingCheckForLongPress.rememberWindowAttachCount();
             mPendingCheckForLongPress.rememberPressedState();
-            postDelayed(mPendingCheckForLongPress,
-                    ViewConfiguration.getLongPressTimeout() - delayOffset);
+            postDelayed(mPendingCheckForLongPress, delay);
         }
     }
 
@@ -27035,7 +27094,9 @@
         public void run() {
             mPrivateFlags &= ~PFLAG_PREPRESSED;
             setPressed(true, x, y);
-            checkForLongClick(ViewConfiguration.getTapTimeout(), x, y);
+            final long delay =
+                    ViewConfiguration.getLongPressTimeout() - ViewConfiguration.getTapTimeout();
+            checkForLongClick(delay, x, y);
         }
     }
 
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/WindowInsetsController.java b/core/java/android/view/WindowInsetsController.java
index a35be27..b708323 100644
--- a/core/java/android/view/WindowInsetsController.java
+++ b/core/java/android/view/WindowInsetsController.java
@@ -16,6 +16,8 @@
 
 package android.view;
 
+import static android.view.WindowInsets.Type.ime;
+
 import android.annotation.NonNull;
 import android.view.WindowInsets.Type.InsetType;
 
@@ -32,11 +34,11 @@
      * <p>
      * Note that if the window currently doesn't have control over a certain type, it will apply the
      * change as soon as the window gains control. The app can listen to the event by observing
-     * {@link View#onApplyWindowInsets} and checking visibility with "TODO at method" in
-     * {@link WindowInsets}.
+     * {@link View#onApplyWindowInsets} and checking visibility with {@link WindowInsets#isVisible}.
      *
      * @param types A bitmask of {@link WindowInsets.Type.InsetType} specifying what windows the app
      *              would like to make appear on screen.
+     * @hide
      */
     void show(@InsetType int types);
 
@@ -45,11 +47,11 @@
      * <p>
      * Note that if the window currently doesn't have control over a certain type, it will apply the
      * change as soon as the window gains control. The app can listen to the event by observing
-     * {@link View#onApplyWindowInsets} and checking visibility with "TODO at method" in
-     * {@link WindowInsets}.
+     * {@link View#onApplyWindowInsets} and checking visibility with {@link WindowInsets#isVisible}.
      *
      * @param types A bitmask of {@link WindowInsets.Type.InsetType} specifying what windows the app
      *              would like to make disappear.
+     * @hide
      */
     void hide(@InsetType int types);
 
@@ -60,7 +62,50 @@
      * @param types The {@link InsetType}s the application has requested to control.
      * @param listener The {@link WindowInsetsAnimationControlListener} that gets called when the
      *                 windows are ready to be controlled, among other callbacks.
+     * @hide
      */
     void controlWindowInsetsAnimation(@InsetType int types,
             @NonNull WindowInsetsAnimationControlListener listener);
+
+    /**
+     * Lets the application control the animation for showing the IME in a frame-by-frame manner by
+     * modifying the position of the IME when it's causing insets.
+     *
+     * @param listener The {@link WindowInsetsAnimationControlListener} that gets called when the
+     *                 IME are ready to be controlled, among other callbacks.
+     */
+    default void controlInputMethodAnimation(
+            @NonNull WindowInsetsAnimationControlListener listener) {
+        controlWindowInsetsAnimation(ime(), listener);
+    }
+
+    /**
+     * Makes the IME appear on screen.
+     * <p>
+     * Note that if the window currently doesn't have control over the IME, because it doesn't have
+     * focus, it will apply the change as soon as the window gains control. The app can listen to
+     * the event by observing {@link View#onApplyWindowInsets} and checking visibility with
+     * {@link WindowInsets#isVisible}.
+     *
+     * @see #controlInputMethodAnimation(WindowInsetsAnimationControlListener)
+     * @see #hideInputMethod()
+     */
+    default void showInputMethod() {
+        show(ime());
+    }
+
+    /**
+     * Makes the IME disappear on screen.
+     * <p>
+     * Note that if the window currently doesn't have control over IME, because it doesn't have
+     * focus, it will apply the change as soon as the window gains control. The app can listen to
+     * the event by observing {@link View#onApplyWindowInsets} and checking visibility with
+     * {@link WindowInsets#isVisible}.
+     *
+     * @see #controlInputMethodAnimation(WindowInsetsAnimationControlListener)
+     * @see #showInputMethod()
+     */
+    default void hideInputMethod() {
+        hide(ime());
+    }
 }
diff --git a/core/java/android/view/WindowManager.java b/core/java/android/view/WindowManager.java
index 45c3651..6326c59 100644
--- a/core/java/android/view/WindowManager.java
+++ b/core/java/android/view/WindowManager.java
@@ -255,6 +255,12 @@
     int TRANSIT_CRASHING_ACTIVITY_CLOSE = 26;
 
     /**
+     * A task is changing windowing modes
+     * @hide
+     */
+    int TRANSIT_TASK_CHANGE_WINDOWING_MODE = 27;
+
+    /**
      * @hide
      */
     @IntDef(prefix = { "TRANSIT_" }, value = {
@@ -280,7 +286,8 @@
             TRANSIT_KEYGUARD_UNOCCLUDE,
             TRANSIT_TRANSLUCENT_ACTIVITY_OPEN,
             TRANSIT_TRANSLUCENT_ACTIVITY_CLOSE,
-            TRANSIT_CRASHING_ACTIVITY_CLOSE
+            TRANSIT_CRASHING_ACTIVITY_CLOSE,
+            TRANSIT_TASK_CHANGE_WINDOWING_MODE
     })
     @Retention(RetentionPolicy.SOURCE)
     @interface TransitionType {}
diff --git a/core/java/android/view/accessibility/AccessibilityManager.java b/core/java/android/view/accessibility/AccessibilityManager.java
index c5c1bca..6aafa34 100644
--- a/core/java/android/view/accessibility/AccessibilityManager.java
+++ b/core/java/android/view/accessibility/AccessibilityManager.java
@@ -1166,9 +1166,10 @@
     /**
      * Notifies that the accessibility button in the system's navigation area has been clicked
      *
+     * @param displayId The logical display id.
      * @hide
      */
-    public void notifyAccessibilityButtonClicked() {
+    public void notifyAccessibilityButtonClicked(int displayId) {
         final IAccessibilityManager service;
         synchronized (mLock) {
             service = getServiceLocked();
@@ -1177,7 +1178,7 @@
             }
         }
         try {
-            service.notifyAccessibilityButtonClicked();
+            service.notifyAccessibilityButtonClicked(displayId);
         } catch (RemoteException re) {
             Log.e(LOG_TAG, "Error while dispatching accessibility button click", re);
         }
diff --git a/core/java/android/view/accessibility/IAccessibilityManager.aidl b/core/java/android/view/accessibility/IAccessibilityManager.aidl
index 38dac94..486b35d 100644
--- a/core/java/android/view/accessibility/IAccessibilityManager.aidl
+++ b/core/java/android/view/accessibility/IAccessibilityManager.aidl
@@ -63,7 +63,7 @@
 
     IBinder getWindowToken(int windowId, int userId);
 
-    void notifyAccessibilityButtonClicked();
+    void notifyAccessibilityButtonClicked(int displayId);
 
     void notifyAccessibilityButtonVisibilityChanged(boolean available);
 
diff --git a/core/java/android/view/contentcapture/ContentCaptureContext.java b/core/java/android/view/contentcapture/ContentCaptureContext.java
index 2d2987a..1928613 100644
--- a/core/java/android/view/contentcapture/ContentCaptureContext.java
+++ b/core/java/android/view/contentcapture/ContentCaptureContext.java
@@ -22,6 +22,7 @@
 import android.app.TaskInfo;
 import android.content.ComponentName;
 import android.content.Context;
+import android.content.Intent;
 import android.net.Uri;
 import android.os.Bundle;
 import android.os.Parcel;
@@ -84,9 +85,9 @@
     // Fields below are set by app on Builder
     private final @Nullable Bundle mExtras;
     private final @Nullable Uri mUri;
+    private final @Nullable String mAction;
 
     // Fields below are set by server when the session starts
-    // TODO(b/111276913): create new object for taskId + componentName / reuse on other places
     private final @Nullable ComponentName mComponentName;
     private final int mTaskId;
     private final int mDisplayId;
@@ -102,10 +103,12 @@
             mHasClientContext = true;
             mExtras = clientContext.mExtras;
             mUri = clientContext.mUri;
+            mAction = clientContext.mAction;
         } else {
             mHasClientContext = false;
             mExtras = null;
             mUri = null;
+            mAction = null;
         }
         mComponentName = Preconditions.checkNotNull(componentName);
         mTaskId = taskId;
@@ -117,13 +120,14 @@
         mHasClientContext = true;
         mExtras = builder.mExtras;
         mUri = builder.mUri;
+        mAction = builder.mAction;
 
         mComponentName  = null;
         mTaskId = mFlags = mDisplayId = 0;
     }
 
     /**
-     * Gets the (optional) extras set by the app.
+     * Gets the (optional) extras set by the app (through {@link Builder#setExtras(Bundle)}).
      *
      * <p>It can be used to provide vendor-specific data that can be modified and examined.
      *
@@ -136,7 +140,7 @@
     }
 
     /**
-     * Gets the (optional) URI set by the app.
+     * Gets the (optional) URI set by the app (through {@link Builder#setUri(Uri)}).
      *
      * @hide
      */
@@ -147,6 +151,17 @@
     }
 
     /**
+     * Gets the (optional) action set by the app (through {@link Builder#setAction(String)}).
+     *
+     * @hide
+     */
+    @SystemApi
+    @Nullable
+    public String getAction() {
+        return mAction;
+    }
+
+    /**
      * Gets the id of the {@link TaskInfo task} associated with this context.
      *
      * @hide
@@ -213,6 +228,8 @@
     public static final class Builder {
         private Bundle mExtras;
         private Uri mUri;
+        private boolean mDestroyed;
+        private String mAction;
 
         /**
          * Sets extra options associated with this context.
@@ -221,11 +238,13 @@
          *
          * @param extras extra options.
          * @return this builder.
+         *
+         * @throws IllegalStateException if {@link #build()} was already called.
          */
         @NonNull
         public Builder setExtras(@NonNull Bundle extras) {
-            // TODO(b/111276913): check build just once / throw exception / test / document
             mExtras = Preconditions.checkNotNull(extras);
+            throwIfDestroyed();
             return this;
         }
 
@@ -236,23 +255,51 @@
          *
          * @param uri URI associated with this context.
          * @return this builder.
+         *
+         * @throws IllegalStateException if {@link #build()} was already called.
          */
         @NonNull
         public Builder setUri(@NonNull Uri uri) {
-            // TODO(b/111276913): check build just once / throw exception / test / document
             mUri = Preconditions.checkNotNull(uri);
+            throwIfDestroyed();
+            return this;
+        }
+
+        /**
+         * Sets an {@link Intent#getAction() intent action} associated with this context.
+         *
+         * @param action intent action
+         *
+         * @return this builder
+         *
+         * @throws IllegalStateException if {@link #build()} was already called.
+         */
+        @NonNull
+        public Builder setAction(@NonNull String action) {
+            mAction = Preconditions.checkNotNull(action);
+            throwIfDestroyed();
             return this;
         }
 
         /**
          * Builds the {@link ContentCaptureContext}.
+         *
+         * @throws IllegalStateException if {@link #build()} was already called or no call to either
+         * {@link #setExtras(Bundle)}, {@link #setAction(String)}, or {@link #setUri(Uri)} was made.
+         *
+         * @return the built {@code ContentCaptureContext}
          */
         public ContentCaptureContext build() {
-            // TODO(b/111276913): check build just once / throw exception / test / document
-            // TODO(b/111276913): make sure it at least one property (uri / extras) / test /
-            // throw exception / documment
+            throwIfDestroyed();
+            Preconditions.checkState(mExtras != null || mUri != null || mAction != null,
+                    "Must call setUri() or setExtras() or setUri() before calling build()");
+            mDestroyed = true;
             return new ContentCaptureContext(this);
         }
+
+        private void throwIfDestroyed() {
+            Preconditions.checkState(!mDestroyed, "Already called #build()");
+        }
     }
 
     /**
@@ -277,6 +324,10 @@
             // NOTE: cannot dump because it could contain PII
             pw.print(", hasUri");
         }
+        if (mAction != null) {
+            // NOTE: cannot dump because it could contain PII
+            pw.print(", hasAction");
+        }
     }
 
     @Override
@@ -297,6 +348,10 @@
             // NOTE: cannot print because it could contain PII
             builder.append(", hasUri");
         }
+        if (mAction != null) {
+            // NOTE: cannot print because it could contain PII
+            builder.append(", hasAction");
+        }
         return builder.append(']').toString();
     }
 
@@ -310,6 +365,7 @@
         parcel.writeInt(mHasClientContext ? 1 : 0);
         if (mHasClientContext) {
             parcel.writeParcelable(mUri, flags);
+            parcel.writeString(mAction);
             parcel.writeBundle(mExtras);
         }
         parcel.writeParcelable(mComponentName, flags);
@@ -329,12 +385,14 @@
 
             final ContentCaptureContext clientContext;
             if (hasClientContext) {
+                // Must reconstruct the client context using the Builder API
                 final Builder builder = new Builder();
                 final Uri uri = parcel.readParcelable(null);
+                final String action = parcel.readString();
                 final Bundle extras = parcel.readBundle();
                 if (uri != null) builder.setUri(uri);
+                if (action != null) builder.setAction(action);
                 if (extras != null) builder.setExtras(extras);
-                // Must reconstruct the client context using the Builder API
                 clientContext = new ContentCaptureContext(builder);
             } else {
                 clientContext = null;
diff --git a/core/java/android/view/contentcapture/ContentCaptureEvent.java b/core/java/android/view/contentcapture/ContentCaptureEvent.java
index 43963c3..a6d4472 100644
--- a/core/java/android/view/contentcapture/ContentCaptureEvent.java
+++ b/core/java/android/view/contentcapture/ContentCaptureEvent.java
@@ -15,6 +15,8 @@
  */
 package android.view.contentcapture;
 
+import static android.view.contentcapture.ContentCaptureHelper.getSanitizedString;
+
 import android.annotation.IntDef;
 import android.annotation.NonNull;
 import android.annotation.Nullable;
@@ -267,8 +269,7 @@
             pw.print(", parentSessionId="); pw.print(mParentSessionId);
         }
         if (mText != null) {
-            // Cannot print content because could have PII
-            pw.print(", text="); pw.print(mText.length()); pw.print("_chars");
+            pw.print(", text="); pw.println(getSanitizedString(mText));
         }
     }
 
@@ -293,6 +294,9 @@
             }
             string.append(", id=").append(mNode.getAutofillId());
         }
+        if (mText != null) {
+            string.append(", text=").append(getSanitizedString(mText));
+        }
         return string.append(']').toString();
     }
 
diff --git a/core/java/android/view/contentcapture/ContentCaptureHelper.java b/core/java/android/view/contentcapture/ContentCaptureHelper.java
new file mode 100644
index 0000000..508880f
--- /dev/null
+++ b/core/java/android/view/contentcapture/ContentCaptureHelper.java
@@ -0,0 +1,40 @@
+/*
+ * 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.contentcapture;
+
+import android.annotation.Nullable;
+
+/**
+ * Helpe class for this package.
+ */
+final class ContentCaptureHelper {
+
+    // TODO(b/121044306): define a way to dynamically set them(for example, using settings?)
+    static final boolean VERBOSE = false;
+    static final boolean DEBUG = true; // STOPSHIP if not set to false
+
+    /**
+     * Used to log text that could contain PII.
+     */
+    @Nullable
+    public static String getSanitizedString(@Nullable CharSequence text) {
+        return text == null ? null : text.length() + "_chars";
+    }
+
+    private ContentCaptureHelper() {
+        throw new UnsupportedOperationException("contains only static methods");
+    }
+}
diff --git a/core/java/android/view/contentcapture/ContentCaptureManager.java b/core/java/android/view/contentcapture/ContentCaptureManager.java
index 413f1a5..b9017b3 100644
--- a/core/java/android/view/contentcapture/ContentCaptureManager.java
+++ b/core/java/android/view/contentcapture/ContentCaptureManager.java
@@ -15,6 +15,8 @@
  */
 package android.view.contentcapture;
 
+import static android.view.contentcapture.ContentCaptureHelper.VERBOSE;
+
 import static com.android.internal.util.function.pooled.PooledLambda.obtainMessage;
 
 import android.annotation.NonNull;
@@ -57,10 +59,6 @@
      */
     private static final int SYNC_CALLS_TIMEOUT_MS = 5000;
 
-    // TODO(b/121044306): define a way to dynamically set them(for example, using settings?)
-    static final boolean VERBOSE = false;
-    static final boolean DEBUG = true; // STOPSHIP if not set to false
-
     private final Object mLock = new Object();
 
     @GuardedBy("mLock")
@@ -191,13 +189,19 @@
     }
 
     /**
-     * Called by the ap to request the Content Capture service to remove user-data associated with
+     * Called by the app to request the Content Capture service to remove user-data associated with
      * some context.
      *
      * @param request object specifying what user data should be removed.
      */
     public void removeUserData(@NonNull UserDataRemovalRequest request) {
-        //TODO(b/111276913): implement
+        Preconditions.checkNotNull(request);
+
+        try {
+            mService.removeUserData(mContext.getUserId(), request);
+        } catch (RemoteException e) {
+            e.rethrowFromSystemServer();
+        }
     }
 
     /** @hide */
diff --git a/core/java/android/view/contentcapture/ContentCaptureSession.java b/core/java/android/view/contentcapture/ContentCaptureSession.java
index a1e581e..c425e7b 100644
--- a/core/java/android/view/contentcapture/ContentCaptureSession.java
+++ b/core/java/android/view/contentcapture/ContentCaptureSession.java
@@ -15,8 +15,8 @@
  */
 package android.view.contentcapture;
 
-import static android.view.contentcapture.ContentCaptureManager.DEBUG;
-import static android.view.contentcapture.ContentCaptureManager.VERBOSE;
+import static android.view.contentcapture.ContentCaptureHelper.DEBUG;
+import static android.view.contentcapture.ContentCaptureHelper.VERBOSE;
 
 import android.annotation.CallSuper;
 import android.annotation.IntDef;
@@ -34,8 +34,6 @@
 import com.android.internal.util.ArrayUtils;
 import com.android.internal.util.Preconditions;
 
-import dalvik.system.CloseGuard;
-
 import java.io.PrintWriter;
 import java.lang.annotation.Retention;
 import java.lang.annotation.RetentionPolicy;
@@ -148,9 +146,6 @@
     @Retention(RetentionPolicy.SOURCE)
     @interface FlushReason{}
 
-
-    private final CloseGuard mCloseGuard = CloseGuard.get();
-
     private final Object mLock = new Object();
 
     /**
@@ -185,7 +180,6 @@
     @VisibleForTesting
     public ContentCaptureSession(@NonNull String id) {
         mId = Preconditions.checkNotNull(id);
-        mCloseGuard.open("destroy");
     }
 
     /** @hide */
@@ -246,13 +240,11 @@
     public final void destroy() {
         synchronized (mLock) {
             if (mDestroyed) {
-                Log.e(TAG, "destroy(" + mId + "): already destroyed");
+                if (DEBUG) Log.d(TAG, "destroy(" + mId + "): already destroyed");
                 return;
             }
             mDestroyed = true;
 
-            mCloseGuard.close();
-
             // TODO(b/111276913): check state (for example, how to handle if it's waiting for remote
             // id) and send it to the cache of batched commands
             if (VERBOSE) {
@@ -288,18 +280,6 @@
         destroy();
     }
 
-    @Override
-    protected void finalize() throws Throwable {
-        try {
-            if (mCloseGuard != null) {
-                mCloseGuard.warnIfOpen();
-            }
-            destroy();
-        } finally {
-            super.finalize();
-        }
-    }
-
     /**
      * Notifies the Content Capture Service that a node has been added to the view structure.
      *
diff --git a/core/java/android/view/contentcapture/IContentCaptureManager.aidl b/core/java/android/view/contentcapture/IContentCaptureManager.aidl
index be9c00f..51aea16 100644
--- a/core/java/android/view/contentcapture/IContentCaptureManager.aidl
+++ b/core/java/android/view/contentcapture/IContentCaptureManager.aidl
@@ -19,6 +19,7 @@
 import android.content.ComponentName;
 import android.view.contentcapture.ContentCaptureContext;
 import android.view.contentcapture.ContentCaptureEvent;
+import android.view.contentcapture.UserDataRemovalRequest;
 import android.os.IBinder;
 
 import com.android.internal.os.IResultReceiver;
@@ -56,4 +57,9 @@
      *     provided {@code Bundle} with key "{@code EXTRA}".
      */
     void getReceiverServiceComponentName(int userId, in IResultReceiver result);
+
+    /**
+     * Requests the removal of user data for the provided {@code userId}.
+     */
+    void removeUserData(int userId, in UserDataRemovalRequest request);
 }
diff --git a/core/java/android/view/contentcapture/MainContentCaptureSession.java b/core/java/android/view/contentcapture/MainContentCaptureSession.java
index 103d7e6..9e99c88 100644
--- a/core/java/android/view/contentcapture/MainContentCaptureSession.java
+++ b/core/java/android/view/contentcapture/MainContentCaptureSession.java
@@ -20,8 +20,9 @@
 import static android.view.contentcapture.ContentCaptureEvent.TYPE_VIEW_APPEARED;
 import static android.view.contentcapture.ContentCaptureEvent.TYPE_VIEW_DISAPPEARED;
 import static android.view.contentcapture.ContentCaptureEvent.TYPE_VIEW_TEXT_CHANGED;
-import static android.view.contentcapture.ContentCaptureManager.DEBUG;
-import static android.view.contentcapture.ContentCaptureManager.VERBOSE;
+import static android.view.contentcapture.ContentCaptureHelper.DEBUG;
+import static android.view.contentcapture.ContentCaptureHelper.VERBOSE;
+import static android.view.contentcapture.ContentCaptureHelper.getSanitizedString;
 
 import static com.android.internal.util.function.pooled.PooledLambda.obtainMessage;
 
@@ -269,6 +270,7 @@
 
     private void handleSendEvent(@NonNull ContentCaptureEvent event, boolean forceFlush) {
         final int eventType = event.getType();
+        if (VERBOSE) Log.v(TAG, "handleSendEvent(" + getDebugState() + "): " + event);
         if (!handleHasStarted() && eventType != ContentCaptureEvent.TYPE_SESSION_STARTED) {
             // TODO(b/120494182): comment when this could happen (dialogs?)
             Log.v(TAG, "handleSendEvent(" + getDebugState() + ", "
@@ -276,12 +278,16 @@
                     + "): session not started yet");
             return;
         }
-        if (VERBOSE) Log.v(TAG, "handleSendEvent(" + getDebugState() + "): " + event);
+        if (mDisabled.get()) {
+            // This happens when the event was queued in the handler before the sesison was ready,
+            // then handleSessionStarted() returned and set it as disabled - we need to drop it,
+            // otherwise it will keep triggering handleScheduleFlush()
+            if (VERBOSE) Log.v(TAG, "handleSendEvent(): ignoring when disabled");
+            return;
+        }
         if (mEvents == null) {
             if (VERBOSE) {
-                Log.v(TAG, "handleSendEvent(" + getDebugState() + ", "
-                        + ContentCaptureEvent.getTypeAsString(eventType)
-                        + "): creating buffer for " + MAX_BUFFER_SIZE + " events");
+                Log.v(TAG, "handleSendEvent(): creating buffer for " + MAX_BUFFER_SIZE + " events");
             }
             mEvents = new ArrayList<>(MAX_BUFFER_SIZE);
         }
@@ -296,8 +302,8 @@
             if (lastEvent.getType() == TYPE_VIEW_TEXT_CHANGED
                     && lastEvent.getId().equals(event.getId())) {
                 if (VERBOSE) {
-                    Log.v(TAG, "Buffering VIEW_TEXT_CHANGED event, updated text = "
-                            + event.getText());
+                    Log.v(TAG, "Buffering VIEW_TEXT_CHANGED event, updated text="
+                            + getSanitizedString(event.getText()));
                 }
                 lastEvent.setText(event.getText());
                 addEvent = false;
@@ -365,8 +371,20 @@
     }
 
     private void handleScheduleFlush(@FlushReason int reason, boolean checkExisting) {
+        if (VERBOSE) {
+            Log.v(TAG, "handleScheduleFlush(" + getDebugState(reason)
+                    + ", checkExisting=" + checkExisting);
+        }
         if (!handleHasStarted()) {
-            Log.v(TAG, "handleScheduleFlush(" + getDebugState() + "): session not started yet");
+            if (VERBOSE) Log.v(TAG, "handleScheduleFlush(): session not started yet");
+            return;
+        }
+
+        if (mDisabled.get()) {
+            // Should not be called on this state, as handleSendEvent checks.
+            // But we rather add one if check and log than re-schedule and keep the session alive...
+            Log.e(TAG, "handleScheduleFlush(" + getDebugState(reason) + "): should not be called "
+                    + "when disabled. events=" + (mEvents == null ? null : mEvents.size()));
             return;
         }
         if (checkExisting && mHandler.hasMessages(MSG_FLUSH)) {
@@ -375,8 +393,7 @@
         }
         mNextFlush = System.currentTimeMillis() + FLUSHING_FREQUENCY_MS;
         if (VERBOSE) {
-            Log.v(TAG, "handleScheduleFlush(" + getDebugState()
-                    + ", reason=" + getflushReasonAsString(reason) + "): scheduled to flush in "
+            Log.v(TAG, "handleScheduleFlush(): scheduled to flush in "
                     + FLUSHING_FREQUENCY_MS + "ms: " + TimeUtils.logTimeOfDay(mNextFlush));
         }
         mHandler.sendMessageDelayed(
@@ -395,11 +412,16 @@
     private void handleForceFlush(@FlushReason int reason) {
         if (mEvents == null) return;
 
+        if (mDisabled.get()) {
+            Log.e(TAG, "handleForceFlush(" + getDebugState(reason) + "): should not be when "
+                    + "disabled");
+            return;
+        }
+
         if (mDirectServiceInterface == null) {
             if (VERBOSE) {
-                Log.v(TAG, "handleForceFlush(" + getDebugState()
-                        + ", reason=" + getflushReasonAsString(reason)
-                        + "): hold your horses, client not ready: " + mEvents);
+                Log.v(TAG, "handleForceFlush(" + getDebugState(reason) + "): hold your horses, "
+                        + "client not ready: " + mEvents);
             }
             if (!mHandler.hasMessages(MSG_FLUSH)) {
                 handleScheduleFlush(reason, /* checkExisting= */ false);
@@ -410,8 +432,7 @@
         final int numberEvents = mEvents.size();
         final String reasonString = getflushReasonAsString(reason);
         if (DEBUG) {
-            Log.d(TAG, "Flushing " + numberEvents + " event(s) for " + getDebugState()
-                    + ". Reason: " + reasonString);
+            Log.d(TAG, "Flushing " + numberEvents + " event(s) for " + getDebugState(reason));
         }
         // Logs reason, size, max size, idle timeout
         final String logRecord = "r=" + reasonString + " s=" + numberEvents
@@ -592,7 +613,14 @@
                 : "act:" + mComponentName.flattenToShortString();
     }
 
+    @NonNull
     private String getDebugState() {
-        return getActivityName() + " (state=" + getStateAsString(mState) + ")";
+        return getActivityName() + " [state=" + getStateAsString(mState) + ", disabled="
+                + mDisabled.get() + "]";
+    }
+
+    @NonNull
+    private String getDebugState(@FlushReason int reason) {
+        return getDebugState() + ", reason=" + getflushReasonAsString(reason);
     }
 }
diff --git a/services/net/java/android/net/dhcp/DhcpClient.java b/core/java/android/view/contentcapture/UserDataRemovalRequest.aidl
similarity index 61%
copy from services/net/java/android/net/dhcp/DhcpClient.java
copy to core/java/android/view/contentcapture/UserDataRemovalRequest.aidl
index cddb91f..fbe47e0 100644
--- a/services/net/java/android/net/dhcp/DhcpClient.java
+++ b/core/java/android/view/contentcapture/UserDataRemovalRequest.aidl
@@ -14,16 +14,6 @@
  * limitations under the License.
  */
 
-package android.net.dhcp;
+package android.view.contentcapture;
 
-/**
- * TODO: remove this class after migrating clients.
- */
-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;
-}
+parcelable UserDataRemovalRequest;
diff --git a/core/java/android/view/contentcapture/UserDataRemovalRequest.java b/core/java/android/view/contentcapture/UserDataRemovalRequest.java
index 0261b70..8ee63ef 100644
--- a/core/java/android/view/contentcapture/UserDataRemovalRequest.java
+++ b/core/java/android/view/contentcapture/UserDataRemovalRequest.java
@@ -17,10 +17,15 @@
 
 import android.annotation.NonNull;
 import android.annotation.SystemApi;
+import android.app.ActivityThread;
 import android.net.Uri;
 import android.os.Parcel;
 import android.os.Parcelable;
+import android.util.IntArray;
 
+import com.android.internal.util.Preconditions;
+
+import java.util.ArrayList;
 import java.util.List;
 
 /**
@@ -29,8 +34,35 @@
  */
 public final class UserDataRemovalRequest implements Parcelable {
 
-    private UserDataRemovalRequest(Builder builder) {
-        // TODO(b/111276913): implement
+    private final String mPackageName;
+
+    private final boolean mForEverything;
+    private ArrayList<UriRequest> mUriRequests;
+
+    private UserDataRemovalRequest(@NonNull Builder builder) {
+        mPackageName = ActivityThread.currentActivityThread().getApplication().getPackageName();
+        mForEverything = builder.mForEverything;
+        if (builder.mUris != null) {
+            final int size = builder.mUris.size();
+            mUriRequests = new ArrayList<>(size);
+            for (int i = 0; i < size; i++) {
+                mUriRequests.add(new UriRequest(builder.mUris.get(i),
+                        builder.mRecursive.get(i) == 1));
+            }
+        }
+    }
+
+    private UserDataRemovalRequest(@NonNull Parcel parcel) {
+        mPackageName = parcel.readString();
+        mForEverything = parcel.readBoolean();
+        if (!mForEverything) {
+            final int size = parcel.readInt();
+            mUriRequests = new ArrayList<>(size);
+            for (int i = 0; i < size; i++) {
+                mUriRequests.add(new UriRequest((Uri) parcel.readValue(null),
+                        parcel.readBoolean()));
+            }
+        }
     }
 
     /**
@@ -40,9 +72,7 @@
     @SystemApi
     @NonNull
     public String getPackageName() {
-        // TODO(b/111276913): implement
-        // TODO(b/111276913): make sure it's set on system_service so it cannot be faked by app
-        return null;
+        return mPackageName;
     }
 
     /**
@@ -52,8 +82,7 @@
      */
     @SystemApi
     public boolean isForEverything() {
-        // TODO(b/111276913): implement
-        return false;
+        return mForEverything;
     }
 
     /**
@@ -64,8 +93,7 @@
     @SystemApi
     @NonNull
     public List<UriRequest> getUriRequests() {
-        // TODO(b/111276913): implement
-        return null;
+        return mUriRequests;
     }
 
     /**
@@ -73,6 +101,12 @@
      */
     public static final class Builder {
 
+        private boolean mForEverything;
+        private ArrayList<Uri> mUris;
+        private IntArray mRecursive;
+
+        private boolean mDestroyed;
+
         /**
          * Requests servive to remove all user data associated with the app's package.
          *
@@ -80,7 +114,12 @@
          */
         @NonNull
         public Builder forEverything() {
-            // TODO(b/111276913): implement
+            throwIfDestroyed();
+            if (mUris != null) {
+                throw new IllegalStateException("Already added Uris");
+            }
+
+            mForEverything = true;
             return this;
         }
 
@@ -94,7 +133,19 @@
          * @return this builder
          */
         public Builder addUri(@NonNull Uri uri, boolean recursive) {
-            // TODO(b/111276913): implement
+            throwIfDestroyed();
+            if (mForEverything) {
+                throw new IllegalStateException("Already is for everything");
+            }
+            Preconditions.checkNotNull(uri);
+
+            if (mUris == null) {
+                mUris = new ArrayList<>();
+                mRecursive = new IntArray();
+            }
+
+            mUris.add(uri);
+            mRecursive.add(recursive ? 1 : 0);
             return this;
         }
 
@@ -103,8 +154,16 @@
          */
         @NonNull
         public UserDataRemovalRequest build() {
-            // TODO(b/111276913): implement / unit test / check built / document exceptions
-            return null;
+            throwIfDestroyed();
+
+            Preconditions.checkState(mForEverything || mUris != null);
+
+            mDestroyed = true;
+            return new UserDataRemovalRequest(this);
+        }
+
+        private void throwIfDestroyed() {
+            Preconditions.checkState(!mDestroyed, "Already destroyed!");
         }
     }
 
@@ -115,7 +174,17 @@
 
     @Override
     public void writeToParcel(Parcel parcel, int flags) {
-        // TODO(b/111276913): implement
+        parcel.writeString(mPackageName);
+        parcel.writeBoolean(mForEverything);
+        if (!mForEverything) {
+            final int size = mUriRequests.size();
+            parcel.writeInt(size);
+            for (int i = 0; i < size; i++) {
+                final UriRequest request = mUriRequests.get(i);
+                parcel.writeValue(request.getUri());
+                parcel.writeBoolean(request.isRecursive());
+            }
+        }
     }
 
     public static final Parcelable.Creator<UserDataRemovalRequest> CREATOR =
@@ -123,8 +192,7 @@
 
         @Override
         public UserDataRemovalRequest createFromParcel(Parcel parcel) {
-            // TODO(b/111276913): implement
-            return null;
+            return new UserDataRemovalRequest(parcel);
         }
 
         @Override
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 15088d2..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
@@ -2490,7 +2549,14 @@
      * @param subtype A new input method subtype to switch.
      * @return true if the current subtype was successfully switched. When the specified subtype is
      * null, this method returns false.
+     * @deprecated If the calling process is an IME, use
+     *             {@link InputMethodService#switchInputMethod(String, InputMethodSubtype)}, which
+     *             does not require any permission as long as the caller is the current IME.
+     *             If the calling process is some privileged app that already has
+     *             {@link android.Manifest.permission#WRITE_SECURE_SETTINGS} permission, just
+     *             directly update {@link Settings.Secure#SELECTED_INPUT_METHOD_SUBTYPE}.
      */
+    @Deprecated
     @RequiresPermission(WRITE_SECURE_SETTINGS)
     public boolean setCurrentInputMethodSubtype(InputMethodSubtype subtype) {
         if (Process.myUid() == Process.SYSTEM_UID) {
@@ -2675,7 +2741,13 @@
      *
      * @param imiId Id of InputMethodInfo which additional input method subtypes will be added to.
      * @param subtypes subtypes will be added as additional subtypes of the current input method.
+     * @deprecated For IMEs that have already implemented features like customizable/downloadable
+     *             keyboard layouts/languages, please start migration to other approaches. One idea
+     *             would be exposing only one unified {@link InputMethodSubtype} then implement
+     *             IME's own language switching mechanism within that unified subtype. The support
+     *             of "Additional Subtype" may be completely dropped in a future version of Android.
      */
+    @Deprecated
     public void setAdditionalInputMethodSubtypes(String imiId, InputMethodSubtype[] subtypes) {
         try {
             mService.setAdditionalInputMethodSubtypes(imiId, subtypes);
@@ -2692,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/inspector/InspectableProperty.java b/core/java/android/view/inspector/InspectableProperty.java
index 355ff1d..f859521 100644
--- a/core/java/android/view/inspector/InspectableProperty.java
+++ b/core/java/android/view/inspector/InspectableProperty.java
@@ -106,6 +106,7 @@
     /**
      * One entry in an enumeration packed into a primitive {int}.
      *
+     * @see IntEnumMapping
      * @hide
      */
     @Target({TYPE})
diff --git a/core/java/android/view/inspector/IntEnumMapping.java b/core/java/android/view/inspector/IntEnumMapping.java
new file mode 100644
index 0000000..147bb46
--- /dev/null
+++ b/core/java/android/view/inspector/IntEnumMapping.java
@@ -0,0 +1,102 @@
+/*
+ * 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.view.inspector;
+
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.util.SparseArray;
+
+import java.util.Objects;
+
+/**
+ * Maps the values of an {@code int} property to strings for properties that encode an enumeration.
+ *
+ * An {@link InspectionCompanion} may provide an instance of this class to a {@link PropertyMapper}
+ * for flag values packed into primitive {@code int} properties.
+ *
+ * This class is an immutable wrapper for {@link SparseArray}, and must be constructed by a
+ * {@link Builder}.
+ *
+ * @see PropertyMapper#mapIntEnum(String, int, IntEnumMapping)
+ */
+public final class IntEnumMapping {
+    private final SparseArray<String> mValues;
+
+    /**
+     * Get the name for the given property value
+     *
+     * @param value The value of the property
+     * @return The name of the value in the enumeration, or null if no value is defined
+     */
+    @Nullable
+    public String get(int value) {
+        return mValues.get(value);
+    }
+
+    /**
+     * Create a new instance from a builder.
+     *
+     * This constructor is private, use {@link Builder#build()} instead.
+     *
+     * @param builder A builder to create from
+     */
+    private IntEnumMapping(Builder builder) {
+        mValues = builder.mValues.clone();
+    }
+
+    /**
+     * A builder for {@link IntEnumMapping}.
+     */
+    public static final class Builder {
+        @NonNull
+        private SparseArray<String> mValues;
+        private boolean mMustCloneValues = false;
+
+        public Builder() {
+            mValues = new SparseArray<>();
+        }
+
+        /**
+         * Add a new enumerated value.
+         *
+         * @param name The string name of the enumeration value
+         * @param value The {@code int} value of the enumeration value
+         * @return This builder
+         */
+        @NonNull
+        public Builder addValue(@NonNull String name, int value) {
+            // Save an allocation, only re-clone if the builder is used again after building
+            if (mMustCloneValues) {
+                mValues = mValues.clone();
+            }
+
+            mValues.put(value, Objects.requireNonNull(name));
+            return this;
+        }
+
+        /**
+         * Build a new {@link IntEnumMapping} from this builder.
+         *
+         * @return A new mapping
+         */
+        @NonNull
+        public IntEnumMapping build() {
+            mMustCloneValues = true;
+            return new IntEnumMapping(this);
+        }
+    }
+}
diff --git a/core/java/android/view/inspector/IntFlagMapping.java b/core/java/android/view/inspector/IntFlagMapping.java
index 8f7dfd5..2409081 100644
--- a/core/java/android/view/inspector/IntFlagMapping.java
+++ b/core/java/android/view/inspector/IntFlagMapping.java
@@ -21,10 +21,11 @@
 import java.util.ArrayList;
 import java.util.Collections;
 import java.util.HashSet;
+import java.util.Objects;
 import java.util.Set;
 
 /**
- * Maps the values of an {@code int} property to arrays of string for properties that encode flags.
+ * Maps the values of an {@code int} property to sets of string for properties that encode flags.
  *
  * An {@link InspectionCompanion} may provide an instance of this class to a {@link PropertyMapper}
  * for flag values packed into primitive {@code int} properties.
@@ -45,7 +46,7 @@
      * Get an array of the names of enabled flags for a given property value.
      *
      * @param value The value of the property
-     * @return The names of the enabled flags
+     * @return The names of the enabled flags, empty if no flags enabled
      */
     @NonNull
     public Set<String> get(int value) {
@@ -136,7 +137,7 @@
         private final int mMask;
 
         private Flag(@NonNull String name, int target, int mask) {
-            mName = name;
+            mName = Objects.requireNonNull(name);
             mTarget = target;
             mMask = mask;
         }
diff --git a/core/java/android/view/inspector/PropertyMapper.java b/core/java/android/view/inspector/PropertyMapper.java
index e20582b..00b18d1 100644
--- a/core/java/android/view/inspector/PropertyMapper.java
+++ b/core/java/android/view/inspector/PropertyMapper.java
@@ -18,7 +18,6 @@
 
 import android.annotation.AttrRes;
 import android.annotation.NonNull;
-import android.util.SparseArray;
 
 /**
  * An interface for mapping the string names of inspectable properties to integer identifiers.
@@ -155,14 +154,14 @@
     int mapIntEnum(
             @NonNull String name,
             @AttrRes int attributeId,
-            @NonNull SparseArray<String> mapping);
+            @NonNull IntEnumMapping mapping);
 
     /**
      * Map a string name to an integer ID for a flag set packed into an int property.
      *
      * @param name The name of the property
      * @param attributeId If the property is from an XML attribute, the resource ID of the property
-     * @param mapping A mapping from int to an array of strings
+     * @param mapping A mapping from int to a set of strings
      * @return An integer ID for the property
      * @throws PropertyConflictException If the property name is already mapped as another type.
      */
diff --git a/core/java/android/view/textclassifier/ActionsSuggestionsHelper.java b/core/java/android/view/textclassifier/ActionsSuggestionsHelper.java
index 77cb4cd..4d917a1 100644
--- a/core/java/android/view/textclassifier/ActionsSuggestionsHelper.java
+++ b/core/java/android/view/textclassifier/ActionsSuggestionsHelper.java
@@ -103,10 +103,9 @@
         final String modelName = String.format(
                 Locale.US, "%s_v%d", localesJoiner.toString(), modelVersion);
         final int hash = Objects.hash(
-                messages.stream()
-                        .map(ConversationActions.Message::getText)
-                        .collect(Collectors.toList()),
-                context.getPackageName());
+                messages.stream().mapToInt(ActionsSuggestionsHelper::hashMessage),
+                context.getPackageName(),
+                System.currentTimeMillis());
         return SelectionSessionLogger.SignatureParser.createSignature(
                 SelectionSessionLogger.CLASSIFIER_ID, modelName, hash);
     }
@@ -116,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);
@@ -128,4 +127,8 @@
             return result;
         }
     }
+
+    private static int hashMessage(ConversationActions.Message message) {
+        return Objects.hash(message.getAuthor(), message.getText(), message.getReferenceTime());
+    }
 }
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/TextClassificationManager.java b/core/java/android/view/textclassifier/TextClassificationManager.java
index ed86206..10c7ade 100644
--- a/core/java/android/view/textclassifier/TextClassificationManager.java
+++ b/core/java/android/view/textclassifier/TextClassificationManager.java
@@ -73,9 +73,16 @@
     /**
      * Returns the text classifier that was set via {@link #setTextClassifier(TextClassifier)}.
      * If this is null, this method returns a default text classifier (i.e. either the system text
-     * classifier if one exists, or a local text classifier running in this app.)
+     * classifier if one exists, or a local text classifier running in this process.)
+     * <p>
+     * Note that if system textclassifier is in use, requests will be sent to a textclassifier
+     * package provided from OEM. If you want to make sure the requests are handled in your own
+     * process, you should consider {@link #getLocalTextClassifier()} instead. However, the local
+     * textclassifier may return inferior results to those returned by the system
+     * textclassifier.
      *
      * @see #setTextClassifier(TextClassifier)
+     * @see #getLocalTextClassifier()
      */
     @NonNull
     public TextClassifier getTextClassifier() {
@@ -215,7 +222,13 @@
         return TextClassifier.NO_OP;
     }
 
-    private TextClassifier getLocalTextClassifier() {
+    /**
+     * Returns a local textclassifier, which is running in this process.
+     *
+     * @see #getTextClassifier()
+     */
+    @NonNull
+    public TextClassifier getLocalTextClassifier() {
         synchronized (mLock) {
             if (mLocalTextClassifier == null) {
                 if (getSettings().isLocalTextClassifierEnabled()) {
diff --git a/core/java/android/view/textclassifier/TextClassifierEvent.java b/core/java/android/view/textclassifier/TextClassifierEvent.java
index b84f6f0..cd13cc0 100644
--- a/core/java/android/view/textclassifier/TextClassifierEvent.java
+++ b/core/java/android/view/textclassifier/TextClassifierEvent.java
@@ -72,7 +72,7 @@
              TYPE_ACTIONS_SHOWN, TYPE_LINK_CLICKED, TYPE_OVERTYPE, TYPE_COPY_ACTION,
              TYPE_PASTE_ACTION, TYPE_CUT_ACTION, TYPE_SHARE_ACTION, TYPE_SMART_ACTION,
              TYPE_SELECTION_DRAG, TYPE_SELECTION_DESTROYED, TYPE_OTHER_ACTION, TYPE_SELECT_ALL,
-             TYPE_SELECTION_RESET, TYPE_MANUAL_REPLY})
+             TYPE_SELECTION_RESET, TYPE_MANUAL_REPLY, TYPE_ACTIONS_GENERATED})
     public @interface Type {
         // For custom event types, use range 1,000,000+.
     }
@@ -121,7 +121,7 @@
 
     @Category private final int mEventCategory;
     @Type private final int mEventType;
-    @Nullable private final String mEntityType;
+    @Nullable private final String[] mEntityTypes;
     @Nullable private final TextClassificationContext mEventContext;
     @Nullable private final String mResultId;
     private final int mEventIndex;
@@ -139,11 +139,12 @@
 
     // Language detection.
     @Nullable private final String mLanguage;
+    private final float mScore;
 
     private TextClassifierEvent(
             int eventCategory,
             int eventType,
-            String entityType,
+            String[] entityTypes,
             TextClassificationContext eventContext,
             String resultId,
             int eventIndex,
@@ -154,10 +155,11 @@
             int relativeSuggestedWordStartIndex,
             int relativeSuggestedWordEndIndex,
             int[] actionIndex,
-            String language) {
+            String language,
+            float score) {
         mEventCategory = eventCategory;
         mEventType = eventType;
-        mEntityType = entityType;
+        mEntityTypes = entityTypes;
         mEventContext = eventContext;
         mResultId = resultId;
         mEventIndex = eventIndex;
@@ -169,6 +171,7 @@
         mRelativeSuggestedWordEndIndex = relativeSuggestedWordEndIndex;
         mActionIndices = actionIndex;
         mLanguage = language;
+        mScore = score;
     }
 
     @Override
@@ -180,7 +183,7 @@
     public void writeToParcel(Parcel dest, int flags) {
         dest.writeInt(mEventCategory);
         dest.writeInt(mEventType);
-        dest.writeString(mEntityType);
+        dest.writeStringArray(mEntityTypes);
         dest.writeParcelable(mEventContext, flags);
         dest.writeString(mResultId);
         dest.writeInt(mEventIndex);
@@ -192,13 +195,14 @@
         dest.writeInt(mRelativeSuggestedWordEndIndex);
         dest.writeIntArray(mActionIndices);
         dest.writeString(mLanguage);
+        dest.writeFloat(mScore);
     }
 
     private static TextClassifierEvent readFromParcel(Parcel in) {
         return new TextClassifierEvent(
                 /* eventCategory= */ in.readInt(),
                 /* eventType= */ in.readInt(),
-                /* entityType= */ in.readString(),
+                /* entityTypes=*/ in.readStringArray(),
                 /* eventContext= */ in.readParcelable(null),
                 /* resultId= */ in.readString(),
                 /* eventIndex= */ in.readInt(),
@@ -209,7 +213,8 @@
                 /* relativeSuggestedWordStartIndex= */ in.readInt(),
                 /* relativeSuggestedWordEndIndex= */ in.readInt(),
                 /* actionIndices= */ in.createIntArray(),
-                /* language= */ in.readString());
+                /* language= */ in.readString(),
+                /* score= */ in.readFloat());
     }
 
     /**
@@ -229,11 +234,11 @@
     }
 
     /**
-     * Returns the entity type. e.g. {@link TextClassifier#TYPE_ADDRESS}.
+     * Returns an array of entity types. e.g. {@link TextClassifier#TYPE_ADDRESS}.
      */
-    @Nullable
-    public String getEntityType() {
-        return mEntityType;
+    @NonNull
+    public String[] getEntityTypes() {
+        return mEntityTypes;
     }
 
     /**
@@ -327,13 +332,20 @@
     }
 
     /**
+     * Returns the score of the suggestion.
+     */
+    public float getScore() {
+        return mScore;
+    }
+
+    /**
      * Builder to build a text classifier event.
      */
     public static final class Builder {
 
         private final int mEventCategory;
         private final int mEventType;
-        @Nullable private String mEntityType;
+        private String[] mEntityTypes = new String[0];
         @Nullable private TextClassificationContext mEventContext;
         @Nullable private String mResultId;
         private int mEventIndex;
@@ -345,6 +357,7 @@
         private int mRelativeSuggestedWordEndIndex;
         private int[] mActionIndices = new int[0];
         @Nullable private String mLanguage;
+        private float mScore;
 
         /**
          * Creates a builder for building {@link TextClassifierEvent}s.
@@ -358,11 +371,12 @@
         }
 
         /**
-         * Sets the entity type. e.g. {@link TextClassifier#TYPE_ADDRESS}.
+         * Sets the entity types. e.g. {@link TextClassifier#TYPE_ADDRESS}.
          */
         @NonNull
-        public Builder setEntityType(@Nullable String entityType) {
-            mEntityType = entityType;
+        public Builder setEntityTypes(@NonNull String... entityTypes) {
+            mEntityTypes = new String[entityTypes.length];
+            System.arraycopy(entityTypes, 0, mEntityTypes, 0, entityTypes.length);
             return this;
         }
 
@@ -478,6 +492,15 @@
         }
 
         /**
+         * Sets the score of the suggestion.
+         */
+        @NonNull
+        public Builder setScore(float score) {
+            mScore = score;
+            return this;
+        }
+
+        /**
          * Builds and returns a text classifier event.
          */
         @NonNull
@@ -486,7 +509,7 @@
             return new TextClassifierEvent(
                     mEventCategory,
                     mEventType,
-                    mEntityType,
+                    mEntityTypes,
                     mEventContext,
                     mResultId,
                     mEventIndex,
@@ -497,7 +520,8 @@
                     mRelativeSuggestedWordStartIndex,
                     mRelativeSuggestedWordEndIndex,
                     mActionIndices,
-                    mLanguage);
+                    mLanguage,
+                    mScore);
         }
         // TODO: Add build(boolean validate).
     }
@@ -507,7 +531,7 @@
         StringBuilder out = new StringBuilder(128);
         out.append("TextClassifierEvent{");
         out.append("mEventCategory=").append(mEventCategory);
-        out.append(", mEventType=").append(mEventType);
+        out.append(", mEventTypes=").append(Arrays.toString(mEntityTypes));
         out.append(", mEventContext=").append(mEventContext);
         out.append(", mResultId=").append(mResultId);
         out.append(", mEventIndex=").append(mEventIndex);
@@ -519,6 +543,7 @@
         out.append(", mRelativeSuggestedWordEndIndex=").append(mRelativeSuggestedWordEndIndex);
         out.append(", mActionIndices=").append(Arrays.toString(mActionIndices));
         out.append(", mLanguage=").append(mLanguage);
+        out.append(", mScore=").append(mScore);
         out.append("}");
         return out.toString();
     }
diff --git a/core/java/android/view/textclassifier/TextClassifierEventTronLogger.java b/core/java/android/view/textclassifier/TextClassifierEventTronLogger.java
index 439e594..5563dfc 100644
--- a/core/java/android/view/textclassifier/TextClassifierEventTronLogger.java
+++ b/core/java/android/view/textclassifier/TextClassifierEventTronLogger.java
@@ -15,12 +15,15 @@
  */
 package android.view.textclassifier;
 
-import static com.android.internal.logging.nano.MetricsProto.MetricsEvent.FIELD_SELECTION_ENTITY_TYPE;
-import static com.android.internal.logging.nano.MetricsProto.MetricsEvent.FIELD_SELECTION_SESSION_ID;
-import static com.android.internal.logging.nano.MetricsProto.MetricsEvent.FIELD_SELECTION_WIDGET_TYPE;
-import static com.android.internal.logging.nano.MetricsProto.MetricsEvent.FIELD_SELECTION_WIDGET_VERSION;
 import static com.android.internal.logging.nano.MetricsProto.MetricsEvent.FIELD_TEXTCLASSIFIER_MODEL;
 import static com.android.internal.logging.nano.MetricsProto.MetricsEvent.FIELD_TEXT_CLASSIFIER_EVENT_TIME;
+import static com.android.internal.logging.nano.MetricsProto.MetricsEvent.FIELD_TEXT_CLASSIFIER_FIRST_ENTITY_TYPE;
+import static com.android.internal.logging.nano.MetricsProto.MetricsEvent.FIELD_TEXT_CLASSIFIER_SCORE;
+import static com.android.internal.logging.nano.MetricsProto.MetricsEvent.FIELD_TEXT_CLASSIFIER_SECOND_ENTITY_TYPE;
+import static com.android.internal.logging.nano.MetricsProto.MetricsEvent.FIELD_TEXT_CLASSIFIER_SESSION_ID;
+import static com.android.internal.logging.nano.MetricsProto.MetricsEvent.FIELD_TEXT_CLASSIFIER_THIRD_ENTITY_TYPE;
+import static com.android.internal.logging.nano.MetricsProto.MetricsEvent.FIELD_TEXT_CLASSIFIER_WIDGET_TYPE;
+import static com.android.internal.logging.nano.MetricsProto.MetricsEvent.FIELD_TEXT_CLASSIFIER_WIDGET_VERSION;
 
 import android.metrics.LogMaker;
 
@@ -60,16 +63,30 @@
             return;
         }
         final LogMaker log = new LogMaker(category)
-                .setType(getLogType(event))
-                .addTaggedData(FIELD_SELECTION_SESSION_ID, event.getResultId())
+                .setSubtype(getLogType(event))
+                .addTaggedData(FIELD_TEXT_CLASSIFIER_SESSION_ID, event.getResultId())
                 .addTaggedData(FIELD_TEXT_CLASSIFIER_EVENT_TIME, event.getEventTime())
                 .addTaggedData(FIELD_TEXTCLASSIFIER_MODEL,
                         SelectionSessionLogger.SignatureParser.getModelName(event.getResultId()))
-                .addTaggedData(FIELD_SELECTION_ENTITY_TYPE, event.getEntityType());
+                .addTaggedData(FIELD_TEXT_CLASSIFIER_SCORE, event.getScore());
+
+        String[] entityTypes = event.getEntityTypes();
+        // TRON does not support a field of list type, and thus workaround by store them
+        // in three separate fields. This is no longer an issue once we have moved to Westworld.
+        if (entityTypes.length >= 1) {
+            log.addTaggedData(FIELD_TEXT_CLASSIFIER_FIRST_ENTITY_TYPE, entityTypes[0]);
+        }
+        if (entityTypes.length >= 2) {
+            log.addTaggedData(FIELD_TEXT_CLASSIFIER_SECOND_ENTITY_TYPE, entityTypes[1]);
+        }
+        if (entityTypes.length >= 3) {
+            log.addTaggedData(FIELD_TEXT_CLASSIFIER_THIRD_ENTITY_TYPE, entityTypes[2]);
+        }
         TextClassificationContext eventContext = event.getEventContext();
         if (eventContext != null) {
-            log.addTaggedData(FIELD_SELECTION_WIDGET_TYPE, eventContext.getWidgetType());
-            log.addTaggedData(FIELD_SELECTION_WIDGET_VERSION, eventContext.getWidgetVersion());
+            log.addTaggedData(FIELD_TEXT_CLASSIFIER_WIDGET_TYPE, eventContext.getWidgetType());
+            log.addTaggedData(FIELD_TEXT_CLASSIFIER_WIDGET_VERSION,
+                    eventContext.getWidgetVersion());
             log.setPackageName(eventContext.getPackageName());
         }
         mMetricsLogger.write(log);
@@ -94,6 +111,8 @@
                 return MetricsEvent.ACTION_TEXT_CLASSIFIER_ACTIONS_SHOWN;
             case TextClassifierEvent.TYPE_MANUAL_REPLY:
                 return MetricsEvent.ACTION_TEXT_CLASSIFIER_MANUAL_REPLY;
+            case TextClassifierEvent.TYPE_ACTIONS_GENERATED:
+                return MetricsEvent.ACTION_TEXT_CLASSIFIER_ACTIONS_GENERATED;
             default:
                 return MetricsEvent.VIEW_UNKNOWN;
         }
@@ -127,14 +146,22 @@
         if (!Log.ENABLE_FULL_LOGGING) {
             return;
         }
-        final String id = String.valueOf(log.getTaggedData(FIELD_SELECTION_SESSION_ID));
+        final String id = String.valueOf(log.getTaggedData(FIELD_TEXT_CLASSIFIER_SESSION_ID));
         final String categoryName = toCategoryName(log.getCategory());
-        final String eventName = toEventName(log.getType());
-        final String widgetType = String.valueOf(log.getTaggedData(FIELD_SELECTION_WIDGET_TYPE));
+        final String eventName = toEventName(log.getSubtype());
+        final String widgetType =
+                String.valueOf(log.getTaggedData(FIELD_TEXT_CLASSIFIER_WIDGET_TYPE));
         final String widgetVersion =
-                String.valueOf(log.getTaggedData(FIELD_SELECTION_WIDGET_VERSION));
+                String.valueOf(log.getTaggedData(FIELD_TEXT_CLASSIFIER_WIDGET_VERSION));
         final String model = String.valueOf(log.getTaggedData(FIELD_TEXTCLASSIFIER_MODEL));
-        final String entityType = String.valueOf(log.getTaggedData(FIELD_SELECTION_ENTITY_TYPE));
+        final String firstEntityType =
+                String.valueOf(log.getTaggedData(FIELD_TEXT_CLASSIFIER_FIRST_ENTITY_TYPE));
+        final String secondEntityType =
+                String.valueOf(log.getTaggedData(FIELD_TEXT_CLASSIFIER_SECOND_ENTITY_TYPE));
+        final String thirdEntityType =
+                String.valueOf(log.getTaggedData(FIELD_TEXT_CLASSIFIER_THIRD_ENTITY_TYPE));
+        final String score =
+                String.valueOf(log.getTaggedData(FIELD_TEXT_CLASSIFIER_SCORE));
 
         StringBuilder builder = new StringBuilder();
         builder.append("writeEvent: ");
@@ -144,7 +171,10 @@
         builder.append(", widgetType=").append(widgetType);
         builder.append(", widgetVersion=").append(widgetVersion);
         builder.append(", model=").append(model);
-        builder.append(", entityType=").append(entityType);
+        builder.append(", firstEntityType=").append(firstEntityType);
+        builder.append(", secondEntityType=").append(secondEntityType);
+        builder.append(", thirdEntityType=").append(thirdEntityType);
+        builder.append(", score=").append(score);
 
         Log.v(TAG, builder.toString());
     }
diff --git a/core/java/android/view/textclassifier/TextClassifierImpl.java b/core/java/android/view/textclassifier/TextClassifierImpl.java
index 9ab963e..7782079 100644
--- a/core/java/android/view/textclassifier/TextClassifierImpl.java
+++ b/core/java/android/view/textclassifier/TextClassifierImpl.java
@@ -78,6 +78,9 @@
  */
 public final class TextClassifierImpl implements TextClassifier {
 
+    /** @hide */
+    public static final String ACTIONS_INTENTS = "actions-intents";
+
     private static final String LOG_TAG = DEFAULT_LOG_TAG;
 
     private static final boolean DEBUG = false;
@@ -564,9 +567,11 @@
             }
         }
 
-        // 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(
                 mContext, classifiedText, isForeignText(classifiedText, foreignTextThreshold),
                 referenceTime, highestScoringResult)) {
@@ -586,12 +591,22 @@
                 isPrimaryAction = false;
             }
             builder.addAction(action);
+            sourceIntents.add(labeledIntent.getIntent());
         }
 
-        return builder.setId(createId(text, start, end)).build();
+        final Bundle extras = new Bundle();
+        extras.putParcelableArrayList(ACTIONS_INTENTS, sourceIntents);
+
+        return builder.setId(createId(text, start, end))
+                .setExtras(extras)
+                .build();
     }
 
     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/webkit/WebViewZygote.java b/core/java/android/webkit/WebViewZygote.java
index 3a1c457..de1f3df 100644
--- a/core/java/android/webkit/WebViewZygote.java
+++ b/core/java/android/webkit/WebViewZygote.java
@@ -150,6 +150,7 @@
         }
 
         try {
+            String abi = sPackage.applicationInfo.primaryCpuAbi;
             sZygote = Process.ZYGOTE_PROCESS.startChildZygote(
                     "com.android.internal.os.WebViewZygoteInit",
                     "webview_zygote",
@@ -158,39 +159,40 @@
                     null,  // gids
                     0,  // runtimeFlags
                     "webview_zygote",  // seInfo
-                    sPackage.applicationInfo.primaryCpuAbi,  // abi
+                    abi,  // abi
                     TextUtils.join(",", Build.SUPPORTED_ABIS),
                     null, // instructionSet
                     Process.FIRST_ISOLATED_UID,
                     Process.LAST_ISOLATED_UID);
-
-            // All the work below is usually done by LoadedApk, but the zygote can't talk to
-            // PackageManager or construct a LoadedApk since it's single-threaded pre-fork, so
-            // doesn't have an ActivityThread and can't use Binder.
-            // Instead, figure out the paths here, in the system server where we have access to
-            // the package manager. Reuse the logic from LoadedApk to determine the correct
-            // paths and pass them to the zygote as strings.
-            final List<String> zipPaths = new ArrayList<>(10);
-            final List<String> libPaths = new ArrayList<>(10);
-            LoadedApk.makePaths(null, false, sPackage.applicationInfo, zipPaths, libPaths);
-            final String librarySearchPath = TextUtils.join(File.pathSeparator, libPaths);
-            final String zip = (zipPaths.size() == 1) ? zipPaths.get(0) :
-                    TextUtils.join(File.pathSeparator, zipPaths);
-
-            String libFileName = WebViewFactory.getWebViewLibrary(sPackage.applicationInfo);
-
-            // In the case where the ApplicationInfo has been modified by the stub WebView,
-            // we need to use the original ApplicationInfo to determine what the original classpath
-            // would have been to use as a cache key.
-            LoadedApk.makePaths(null, false, sPackageOriginalAppInfo, zipPaths, null);
-            final String cacheKey = (zipPaths.size() == 1) ? zipPaths.get(0) :
-                    TextUtils.join(File.pathSeparator, zipPaths);
-
             ZygoteProcess.waitForConnectionToZygote(sZygote.getPrimarySocketAddress());
 
-            Log.d(LOGTAG, "Preloading package " + zip + " " + librarySearchPath);
-            sZygote.preloadPackageForAbi(zip, librarySearchPath, libFileName, cacheKey,
-                                         Build.SUPPORTED_ABIS[0]);
+            if (sPackageOriginalAppInfo.sourceDir.equals(sPackage.applicationInfo.sourceDir)) {
+                // No stub WebView is involved here, so we can preload the package the "clean" way
+                // using the ApplicationInfo.
+                sZygote.preloadApp(sPackage.applicationInfo, abi);
+            } else {
+                // Legacy path to support the stub WebView.
+                // Reuse the logic from LoadedApk to determine the correct paths and pass them to
+                // the zygote as strings.
+                final List<String> zipPaths = new ArrayList<>(10);
+                final List<String> libPaths = new ArrayList<>(10);
+                LoadedApk.makePaths(null, false, sPackage.applicationInfo, zipPaths, libPaths);
+                final String librarySearchPath = TextUtils.join(File.pathSeparator, libPaths);
+                final String zip = (zipPaths.size() == 1) ? zipPaths.get(0) :
+                        TextUtils.join(File.pathSeparator, zipPaths);
+
+                String libFileName = WebViewFactory.getWebViewLibrary(sPackage.applicationInfo);
+
+                // Use the original ApplicationInfo to determine what the original classpath would
+                // have been to use as a cache key.
+                LoadedApk.makePaths(null, false, sPackageOriginalAppInfo, zipPaths, null);
+                final String cacheKey = (zipPaths.size() == 1) ? zipPaths.get(0) :
+                        TextUtils.join(File.pathSeparator, zipPaths);
+
+                Log.d(LOGTAG, "Preloading package " + zip + " " + librarySearchPath);
+                sZygote.preloadPackageForAbi(zip, librarySearchPath, libFileName, cacheKey,
+                                             Build.SUPPORTED_ABIS[0]);
+            }
         } catch (Exception e) {
             Log.e(LOGTAG, "Error connecting to webview zygote", e);
             stopZygoteLocked();
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/AssistUtils.java b/core/java/com/android/internal/app/AssistUtils.java
index 7c371cb..d0102a7 100644
--- a/core/java/com/android/internal/app/AssistUtils.java
+++ b/core/java/com/android/internal/app/AssistUtils.java
@@ -17,13 +17,10 @@
 package com.android.internal.app;
 
 import android.annotation.NonNull;
-import android.app.SearchManager;
 import android.content.ComponentName;
 import android.content.Context;
-import android.content.Intent;
 import android.content.pm.ApplicationInfo;
 import android.content.pm.PackageManager;
-import android.content.pm.ResolveInfo;
 import android.os.Bundle;
 import android.os.IBinder;
 import android.os.RemoteException;
@@ -31,8 +28,6 @@
 import android.provider.Settings;
 import android.util.Log;
 
-import com.android.internal.R;
-
 import java.util.ArrayList;
 import java.util.Set;
 
@@ -44,14 +39,6 @@
 
     private static final String TAG = "AssistUtils";
 
-    /**
-     * Sentinel value for "no default assistant specified."
-     *
-     * Empty string is already used to represent an explicit setting of No Assistant. null cannot
-     * be used because we can't represent a null value in XML.
-     */
-    private static final String UNSET = "#+UNSET";
-
     private final Context mContext;
     private final IVoiceInteractionManagerService mVoiceInteractionManagerService;
 
@@ -186,37 +173,9 @@
                 Settings.Secure.ASSISTANT, userId);
         if (setting != null) {
             return ComponentName.unflattenFromString(setting);
-        }
-
-        final String defaultSetting = mContext.getResources().getString(
-                R.string.config_defaultAssistantComponentName);
-        if (defaultSetting != null && !defaultSetting.equals(UNSET)) {
-            return ComponentName.unflattenFromString(defaultSetting);
-        }
-
-        // Fallback to keep backward compatible behavior when there is no user setting.
-        if (activeServiceSupportsAssistGesture()) {
-            return getActiveServiceComponentName();
-        }
-
-        if (UNSET.equals(defaultSetting)) {
+        } else {
             return null;
         }
-
-        final SearchManager searchManager =
-                (SearchManager) mContext.getSystemService(Context.SEARCH_SERVICE);
-        if (searchManager == null) {
-            return null;
-        }
-        final Intent intent = searchManager.getAssistIntent(false);
-        PackageManager pm = mContext.getPackageManager();
-        ResolveInfo info = pm.resolveActivityAsUser(intent, PackageManager.MATCH_DEFAULT_ONLY,
-                userId);
-        if (info != null) {
-            return new ComponentName(info.activityInfo.applicationInfo.packageName,
-                    info.activityInfo.name);
-        }
-        return null;
     }
 
     public static boolean isPreinstalledAssistant(Context context, ComponentName assistant) {
diff --git a/core/java/com/android/internal/app/ChooserActivity.java b/core/java/com/android/internal/app/ChooserActivity.java
index 30137e38..803462d 100644
--- a/core/java/com/android/internal/app/ChooserActivity.java
+++ b/core/java/com/android/internal/app/ChooserActivity.java
@@ -18,6 +18,10 @@
 
 import android.app.Activity;
 import android.app.ActivityManager;
+import android.app.prediction.AppPredictionContext;
+import android.app.prediction.AppPredictionManager;
+import android.app.prediction.AppPredictor;
+import android.app.prediction.AppTarget;
 import android.content.ComponentName;
 import android.content.Context;
 import android.content.Intent;
@@ -98,6 +102,20 @@
 
     private static final boolean DEBUG = false;
 
+
+    /**
+     * If {@link #USE_SHORTCUT_MANAGER_FOR_DIRECT_TARGETS} and this is set to true,
+     * {@link AppPredictionManager} will be queried for direct share targets.
+     */
+    // TODO(b/123089490): Replace with system flag
+    private static final boolean USE_PREDICTION_MANAGER_FOR_DIRECT_TARGETS = false;
+    // TODO(b/123088566) Share these in a better way.
+    private static final String APP_PREDICTION_SHARE_UI_SURFACE = "share";
+    private static final int APP_PREDICTION_SHARE_TARGET_QUERY_PACKAGE_LIMIT = 20;
+    public static final String APP_PREDICTION_INTENT_FILTER_KEY = "intent_filter";
+    private AppPredictor mAppPredictor;
+    private AppPredictor.Callback mAppPredictorCallback;
+
     /**
      * If set to true, use ShortcutManager to retrieve the matching direct share targets, instead of
      * binding to every ChooserTargetService implementation.
@@ -309,6 +327,35 @@
         mChooserShownTime = System.currentTimeMillis();
         final long systemCost = mChooserShownTime - intentReceivedTime;
         MetricsLogger.histogram(null, "system_cost_for_smart_sharing", (int) systemCost);
+
+        if (USE_PREDICTION_MANAGER_FOR_DIRECT_TARGETS) {
+            final IntentFilter filter = getTargetIntentFilter();
+            Bundle extras = new Bundle();
+            extras.putParcelable(APP_PREDICTION_INTENT_FILTER_KEY, filter);
+            AppPredictionManager appPredictionManager =
+                    getSystemService(AppPredictionManager.class);
+            mAppPredictor = appPredictionManager.createAppPredictionSession(
+                new AppPredictionContext.Builder(this)
+                    .setPredictedTargetCount(APP_PREDICTION_SHARE_TARGET_QUERY_PACKAGE_LIMIT)
+                    .setUiSurface(APP_PREDICTION_SHARE_UI_SURFACE)
+                    .setExtras(extras)
+                    .build());
+            mAppPredictorCallback = resultList -> {
+                final List<DisplayResolveInfo> driList =
+                        getDisplayResolveInfos(mChooserListAdapter);
+                final List<ShortcutManager.ShareShortcutInfo> shareShortcutInfos =
+                        new ArrayList<>();
+                for (AppTarget appTarget : resultList) {
+                    shareShortcutInfos.add(new ShortcutManager.ShareShortcutInfo(
+                            appTarget.getShortcutInfo(),
+                            new ComponentName(
+                                appTarget.getPackageName(), appTarget.getClassName())));
+                }
+                sendShareShortcutInfoList(shareShortcutInfos, driList);
+            };
+            mAppPredictor.registerPredictionUpdates(this.getMainExecutor(), mAppPredictorCallback);
+        }
+
         if (DEBUG) {
             Log.d(TAG, "System Time Cost is " + systemCost);
         }
@@ -339,6 +386,10 @@
         }
         unbindRemainingServices();
         mChooserHandler.removeMessages(CHOOSER_TARGET_SERVICE_RESULT);
+        if (USE_PREDICTION_MANAGER_FOR_DIRECT_TARGETS) {
+            mAppPredictor.unregisterPredictionUpdates(mAppPredictorCallback);
+            mAppPredictor.destroy();
+        }
     }
 
     @Override
@@ -513,6 +564,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 +574,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 +657,10 @@
         }
     }
 
-    private void queryDirectShareTargets(ChooserListAdapter adapter) {
-        final IntentFilter filter = getTargetIntentFilter();
-        if (filter == null) {
-            return;
-        }
-
+    private List<DisplayResolveInfo> getDisplayResolveInfos(ChooserListAdapter adapter) {
         // Need to keep the original DisplayResolveInfos to be able to reconstruct ServiceResultInfo
         // and use the old code path. This Ugliness should go away when Sharesheet is refactored.
-        final List<DisplayResolveInfo> driList = new ArrayList<>();
+        List<DisplayResolveInfo> driList = new ArrayList<>();
         int targetsToQuery = 0;
         for (int i = 0, n = adapter.getDisplayResolveInfoCount(); i < n; i++) {
             final DisplayResolveInfo dri = adapter.getDisplayResolveInfo(i);
@@ -628,42 +680,59 @@
                 break;
             }
         }
+        return driList;
+    }
+
+    private void queryDirectShareTargets(ChooserListAdapter adapter) {
+        if (USE_PREDICTION_MANAGER_FOR_DIRECT_TARGETS) {
+            mAppPredictor.requestPredictionUpdate();
+            return;
+        }
+        final IntentFilter filter = getTargetIntentFilter();
+        if (filter == null) {
+            return;
+        }
+        final List<DisplayResolveInfo> driList = getDisplayResolveInfos(adapter);
 
         AsyncTask.execute(() -> {
             ShortcutManager sm = (ShortcutManager) getSystemService(Context.SHORTCUT_SERVICE);
             List<ShortcutManager.ShareShortcutInfo> resultList = sm.getShareTargets(filter);
-
-            // Match ShareShortcutInfos with DisplayResolveInfos to be able to use the old code path
-            // for direct share targets. After ShareSheet is refactored we should use the
-            // ShareShortcutInfos directly.
-            boolean resultMessageSent = false;
-            for (int i = 0; i < driList.size(); i++) {
-                List<ChooserTarget> chooserTargets = new ArrayList<>();
-                for (int j = 0; j < resultList.size(); j++) {
-                    if (driList.get(i).getResolvedComponentName().equals(
-                            resultList.get(j).getTargetComponent())) {
-                        chooserTargets.add(convertToChooserTarget(resultList.get(j)));
-                    }
-                }
-                if (chooserTargets.isEmpty()) {
-                    continue;
-                }
-
-                final Message msg = Message.obtain();
-                msg.what = SHORTCUT_MANAGER_SHARE_TARGET_RESULT;
-                msg.obj = new ServiceResultInfo(driList.get(i), chooserTargets, null);
-                mChooserHandler.sendMessage(msg);
-                resultMessageSent = true;
-            }
-
-            if (resultMessageSent) {
-                final Message msg = Message.obtain();
-                msg.what = SHORTCUT_MANAGER_SHARE_TARGET_RESULT_COMPLETED;
-                mChooserHandler.sendMessage(msg);
-            }
+            sendShareShortcutInfoList(resultList, driList);
         });
     }
 
+    private void sendShareShortcutInfoList(
+                List<ShortcutManager.ShareShortcutInfo> resultList,
+                List<DisplayResolveInfo> driList) {
+        // Match ShareShortcutInfos with DisplayResolveInfos to be able to use the old code path
+        // for direct share targets. After ShareSheet is refactored we should use the
+        // ShareShortcutInfos directly.
+        boolean resultMessageSent = false;
+        for (int i = 0; i < driList.size(); i++) {
+            List<ChooserTarget> chooserTargets = new ArrayList<>();
+            for (int j = 0; j < resultList.size(); j++) {
+                if (driList.get(i).getResolvedComponentName().equals(
+                            resultList.get(j).getTargetComponent())) {
+                    chooserTargets.add(convertToChooserTarget(resultList.get(j)));
+                }
+            }
+            if (chooserTargets.isEmpty()) {
+                continue;
+            }
+            final Message msg = Message.obtain();
+            msg.what = SHORTCUT_MANAGER_SHARE_TARGET_RESULT;
+            msg.obj = new ServiceResultInfo(driList.get(i), chooserTargets, null);
+            mChooserHandler.sendMessage(msg);
+            resultMessageSent = true;
+        }
+
+        if (resultMessageSent) {
+            final Message msg = Message.obtain();
+            msg.what = SHORTCUT_MANAGER_SHARE_TARGET_RESULT_COMPLETED;
+            mChooserHandler.sendMessage(msg);
+        }
+    }
+
     private ChooserTarget convertToChooserTarget(ShortcutManager.ShareShortcutInfo shareShortcut) {
         ShortcutInfo shortcutInfo = shareShortcut.getShortcutInfo();
         Bundle extras = new Bundle();
@@ -718,6 +787,7 @@
         // Do nothing. We'll send the voice stuff ourselves.
     }
 
+    // TODO(b/123377860) Send clicked ShortcutInfo to mAppPredictor
     void updateModelAndChooserCounts(TargetInfo info) {
         if (info != null) {
             final ResolveInfo ri = info.getResolveInfo();
diff --git a/core/java/com/android/internal/app/ColorDisplayController.java b/core/java/com/android/internal/app/ColorDisplayController.java
index c093fe5..2ac0e4d 100644
--- a/core/java/com/android/internal/app/ColorDisplayController.java
+++ b/core/java/com/android/internal/app/ColorDisplayController.java
@@ -16,28 +16,20 @@
 
 package com.android.internal.app;
 
-import android.annotation.IntDef;
 import android.annotation.NonNull;
 import android.app.ActivityManager;
 import android.content.ContentResolver;
 import android.content.Context;
 import android.database.ContentObserver;
-import android.metrics.LogMaker;
+import android.hardware.display.ColorDisplayManager;
+import android.hardware.display.ColorDisplayManager.AutoMode;
+import android.hardware.display.ColorDisplayManager.ColorMode;
 import android.net.Uri;
 import android.os.Handler;
 import android.os.Looper;
-import android.os.SystemProperties;
 import android.provider.Settings.Secure;
-import android.provider.Settings.System;
 import android.util.Slog;
 
-import com.android.internal.R;
-import com.android.internal.logging.MetricsLogger;
-import com.android.internal.logging.nano.MetricsProto.MetricsEvent;
-
-import java.lang.annotation.Retention;
-import java.lang.annotation.RetentionPolicy;
-import java.time.LocalDateTime;
 import java.time.LocalTime;
 
 /**
@@ -51,67 +43,12 @@
     private static final String TAG = "ColorDisplayController";
     private static final boolean DEBUG = false;
 
-    @Retention(RetentionPolicy.SOURCE)
-    @IntDef({ AUTO_MODE_DISABLED, AUTO_MODE_CUSTOM, AUTO_MODE_TWILIGHT })
-    public @interface AutoMode {}
-
-    /**
-     * Auto mode value to prevent Night display from being automatically activated. It can still
-     * be activated manually via {@link #setActivated(boolean)}.
-     *
-     * @see #setAutoMode(int)
-     */
-    public static final int AUTO_MODE_DISABLED = 0;
-    /**
-     * Auto mode value to automatically activate Night display at a specific start and end time.
-     *
-     * @see #setAutoMode(int)
-     * @see #setCustomStartTime(LocalTime)
-     * @see #setCustomEndTime(LocalTime)
-     */
-    public static final int AUTO_MODE_CUSTOM = 1;
-    /**
-     * Auto mode value to automatically activate Night display from sunset to sunrise.
-     *
-     * @see #setAutoMode(int)
-     */
-    public static final int AUTO_MODE_TWILIGHT = 2;
-
-    @Retention(RetentionPolicy.SOURCE)
-    @IntDef({ COLOR_MODE_NATURAL, COLOR_MODE_BOOSTED, COLOR_MODE_SATURATED, COLOR_MODE_AUTOMATIC })
-    public @interface ColorMode {}
-
-    /**
-     * Color mode with natural colors.
-     *
-     * @see #setColorMode(int)
-     */
-    public static final int COLOR_MODE_NATURAL = 0;
-    /**
-     * Color mode with boosted colors.
-     *
-     * @see #setColorMode(int)
-     */
-    public static final int COLOR_MODE_BOOSTED = 1;
-    /**
-     * Color mode with saturated colors.
-     *
-     * @see #setColorMode(int)
-     */
-    public static final int COLOR_MODE_SATURATED = 2;
-    /**
-     * Color mode with automatic colors.
-     *
-     * @see #setColorMode(int)
-     */
-    public static final int COLOR_MODE_AUTOMATIC = 3;
-
     private final Context mContext;
     private final int mUserId;
+    private final ColorDisplayManager mColorDisplayManager;
 
     private ContentObserver mContentObserver;
     private Callback mCallback;
-    private MetricsLogger mMetricsLogger;
 
     public ColorDisplayController(@NonNull Context context) {
         this(context, ActivityManager.getCurrentUser());
@@ -120,14 +57,14 @@
     public ColorDisplayController(@NonNull Context context, int userId) {
         mContext = context.getApplicationContext();
         mUserId = userId;
+        mColorDisplayManager = mContext.getSystemService(ColorDisplayManager.class);
     }
 
     /**
      * Returns {@code true} when Night display is activated (the display is tinted red).
      */
     public boolean isActivated() {
-        return Secure.getIntForUser(mContext.getContentResolver(),
-                Secure.NIGHT_DISPLAY_ACTIVATED, 0, mUserId) == 1;
+        return mColorDisplayManager.isNightDisplayActivated();
     }
 
     /**
@@ -137,40 +74,16 @@
      * @return {@code true} if the activated value was set successfully
      */
     public boolean setActivated(boolean activated) {
-        if (isActivated() != activated) {
-            Secure.putStringForUser(mContext.getContentResolver(),
-                    Secure.NIGHT_DISPLAY_LAST_ACTIVATED_TIME,
-                    LocalDateTime.now().toString(),
-                    mUserId);
-        }
-        return Secure.putIntForUser(mContext.getContentResolver(),
-                Secure.NIGHT_DISPLAY_ACTIVATED, activated ? 1 : 0, mUserId);
+        return mColorDisplayManager.setNightDisplayActivated(activated);
     }
 
     /**
      * Returns the current auto mode value controlling when Night display will be automatically
-     * activated. One of {@link #AUTO_MODE_DISABLED}, {@link #AUTO_MODE_CUSTOM}, or
-     * {@link #AUTO_MODE_TWILIGHT}.
+     * activated. One of {@link ColorDisplayManager#AUTO_MODE_DISABLED}, {@link
+     * ColorDisplayManager#AUTO_MODE_CUSTOM_TIME} or {@link ColorDisplayManager#AUTO_MODE_TWILIGHT}.
      */
     public @AutoMode int getAutoMode() {
-        int autoMode = Secure.getIntForUser(mContext.getContentResolver(),
-                Secure.NIGHT_DISPLAY_AUTO_MODE, -1, mUserId);
-        if (autoMode == -1) {
-            if (DEBUG) {
-                Slog.d(TAG, "Using default value for setting: " + Secure.NIGHT_DISPLAY_AUTO_MODE);
-            }
-            autoMode = mContext.getResources().getInteger(
-                    R.integer.config_defaultNightDisplayAutoMode);
-        }
-
-        if (autoMode != AUTO_MODE_DISABLED
-                && autoMode != AUTO_MODE_CUSTOM
-                && autoMode != AUTO_MODE_TWILIGHT) {
-            Slog.e(TAG, "Invalid autoMode: " + autoMode);
-            autoMode = AUTO_MODE_DISABLED;
-        }
-
-        return autoMode;
+        return mColorDisplayManager.getNightDisplayAutoMode();
     }
 
     /**
@@ -178,138 +91,64 @@
      * never been set.
      */
     public int getAutoModeRaw() {
-        return Secure.getIntForUser(mContext.getContentResolver(), Secure.NIGHT_DISPLAY_AUTO_MODE,
-                -1, mUserId);
+        return mColorDisplayManager.getNightDisplayAutoModeRaw();
     }
 
     /**
      * Sets the current auto mode value controlling when Night display will be automatically
-     * activated. One of {@link #AUTO_MODE_DISABLED}, {@link #AUTO_MODE_CUSTOM}, or
-     * {@link #AUTO_MODE_TWILIGHT}.
+     * activated. One of {@link ColorDisplayManager#AUTO_MODE_DISABLED}, {@link
+     * ColorDisplayManager#AUTO_MODE_CUSTOM_TIME} or {@link ColorDisplayManager#AUTO_MODE_TWILIGHT}.
      *
      * @param autoMode the new auto mode to use
      * @return {@code true} if new auto mode was set successfully
      */
     public boolean setAutoMode(@AutoMode int autoMode) {
-        if (autoMode != AUTO_MODE_DISABLED
-                && autoMode != AUTO_MODE_CUSTOM
-                && autoMode != AUTO_MODE_TWILIGHT) {
-            throw new IllegalArgumentException("Invalid autoMode: " + autoMode);
-        }
-
-        if (getAutoMode() != autoMode) {
-            Secure.putStringForUser(mContext.getContentResolver(),
-                    Secure.NIGHT_DISPLAY_LAST_ACTIVATED_TIME,
-                    null,
-                    mUserId);
-            getMetricsLogger().write(new LogMaker(
-                    MetricsEvent.ACTION_NIGHT_DISPLAY_AUTO_MODE_CHANGED)
-                    .setType(MetricsEvent.TYPE_ACTION)
-                    .setSubtype(autoMode));
-        }
-
-        return Secure.putIntForUser(mContext.getContentResolver(),
-                Secure.NIGHT_DISPLAY_AUTO_MODE, autoMode, mUserId);
+        return mColorDisplayManager.setNightDisplayAutoMode(autoMode);
     }
 
     /**
-     * Returns the local time when Night display will be automatically activated when using
-     * {@link #AUTO_MODE_CUSTOM}.
+     * Returns the local time when Night display will be automatically activated when using {@link
+     * ColorDisplayManager#AUTO_MODE_CUSTOM_TIME}.
      */
     public @NonNull LocalTime getCustomStartTime() {
-        int startTimeValue = Secure.getIntForUser(mContext.getContentResolver(),
-                Secure.NIGHT_DISPLAY_CUSTOM_START_TIME, -1, mUserId);
-        if (startTimeValue == -1) {
-            if (DEBUG) {
-                Slog.d(TAG, "Using default value for setting: "
-                        + Secure.NIGHT_DISPLAY_CUSTOM_START_TIME);
-            }
-            startTimeValue = mContext.getResources().getInteger(
-                    R.integer.config_defaultNightDisplayCustomStartTime);
-        }
-
-        return LocalTime.ofSecondOfDay(startTimeValue / 1000);
+        return mColorDisplayManager.getNightDisplayCustomStartTime();
     }
 
     /**
-     * Sets the local time when Night display will be automatically activated when using
-     * {@link #AUTO_MODE_CUSTOM}.
+     * Sets the local time when Night display will be automatically activated when using {@link
+     * ColorDisplayManager#AUTO_MODE_CUSTOM_TIME}.
      *
      * @param startTime the local time to automatically activate Night display
      * @return {@code true} if the new custom start time was set successfully
      */
     public boolean setCustomStartTime(@NonNull LocalTime startTime) {
-        if (startTime == null) {
-            throw new IllegalArgumentException("startTime cannot be null");
-        }
-        getMetricsLogger().write(new LogMaker(
-                MetricsEvent.ACTION_NIGHT_DISPLAY_AUTO_MODE_CUSTOM_TIME_CHANGED)
-                .setType(MetricsEvent.TYPE_ACTION)
-                .setSubtype(0));
-        return Secure.putIntForUser(mContext.getContentResolver(),
-                Secure.NIGHT_DISPLAY_CUSTOM_START_TIME, startTime.toSecondOfDay() * 1000, mUserId);
+        return mColorDisplayManager.setNightDisplayCustomStartTime(startTime);
     }
 
     /**
-     * Returns the local time when Night display will be automatically deactivated when using
-     * {@link #AUTO_MODE_CUSTOM}.
+     * Returns the local time when Night display will be automatically deactivated when using {@link
+     * ColorDisplayManager#AUTO_MODE_CUSTOM_TIME}.
      */
     public @NonNull LocalTime getCustomEndTime() {
-        int endTimeValue = Secure.getIntForUser(mContext.getContentResolver(),
-                Secure.NIGHT_DISPLAY_CUSTOM_END_TIME, -1, mUserId);
-        if (endTimeValue == -1) {
-            if (DEBUG) {
-                Slog.d(TAG, "Using default value for setting: "
-                        + Secure.NIGHT_DISPLAY_CUSTOM_END_TIME);
-            }
-            endTimeValue = mContext.getResources().getInteger(
-                    R.integer.config_defaultNightDisplayCustomEndTime);
-        }
-
-        return LocalTime.ofSecondOfDay(endTimeValue / 1000);
+        return mColorDisplayManager.getNightDisplayCustomEndTime();
     }
 
     /**
-     * Sets the local time when Night display will be automatically deactivated when using
-     * {@link #AUTO_MODE_CUSTOM}.
+     * Sets the local time when Night display will be automatically deactivated when using {@link
+     * ColorDisplayManager#AUTO_MODE_CUSTOM_TIME}.
      *
      * @param endTime the local time to automatically deactivate Night display
      * @return {@code true} if the new custom end time was set successfully
      */
     public boolean setCustomEndTime(@NonNull LocalTime endTime) {
-        if (endTime == null) {
-            throw new IllegalArgumentException("endTime cannot be null");
-        }
-        getMetricsLogger().write(new LogMaker(
-                MetricsEvent.ACTION_NIGHT_DISPLAY_AUTO_MODE_CUSTOM_TIME_CHANGED)
-                .setType(MetricsEvent.TYPE_ACTION)
-                .setSubtype(1));
-        return Secure.putIntForUser(mContext.getContentResolver(),
-                Secure.NIGHT_DISPLAY_CUSTOM_END_TIME, endTime.toSecondOfDay() * 1000, mUserId);
+        return mColorDisplayManager.setNightDisplayCustomEndTime(endTime);
     }
 
     /**
      * Returns the color temperature (in Kelvin) to tint the display when activated.
      */
     public int getColorTemperature() {
-        int colorTemperature = Secure.getIntForUser(mContext.getContentResolver(),
-                Secure.NIGHT_DISPLAY_COLOR_TEMPERATURE, -1, mUserId);
-        if (colorTemperature == -1) {
-            if (DEBUG) {
-                Slog.d(TAG, "Using default value for setting: "
-                        + Secure.NIGHT_DISPLAY_COLOR_TEMPERATURE);
-            }
-            colorTemperature = getDefaultColorTemperature();
-        }
-        final int minimumTemperature = getMinimumColorTemperature();
-        final int maximumTemperature = getMaximumColorTemperature();
-        if (colorTemperature < minimumTemperature) {
-            colorTemperature = minimumTemperature;
-        } else if (colorTemperature > maximumTemperature) {
-            colorTemperature = maximumTemperature;
-        }
-
-        return colorTemperature;
+        return mColorDisplayManager.getNightDisplayColorTemperature();
     }
 
     /**
@@ -319,79 +158,14 @@
      * @return {@code true} if new temperature was set successfully.
      */
     public boolean setColorTemperature(int colorTemperature) {
-        return Secure.putIntForUser(mContext.getContentResolver(),
-                Secure.NIGHT_DISPLAY_COLOR_TEMPERATURE, colorTemperature, mUserId);
-    }
-
-    /**
-     * Get the current color mode from system properties, or return -1.
-     *
-     * See com.android.server.display.DisplayTransformManager.
-     */
-    private @ColorMode int getCurrentColorModeFromSystemProperties() {
-        final int displayColorSetting = SystemProperties.getInt("persist.sys.sf.native_mode", 0);
-        if (displayColorSetting == 0) {
-            return "1.0".equals(SystemProperties.get("persist.sys.sf.color_saturation"))
-                    ? COLOR_MODE_NATURAL : COLOR_MODE_BOOSTED;
-        } else if (displayColorSetting == 1) {
-            return COLOR_MODE_SATURATED;
-        } else if (displayColorSetting == 2) {
-            return COLOR_MODE_AUTOMATIC;
-        } else {
-            return -1;
-        }
-    }
-
-    private boolean isColorModeAvailable(@ColorMode int colorMode) {
-        final int[] availableColorModes = mContext.getResources().getIntArray(
-                R.array.config_availableColorModes);
-        if (availableColorModes != null) {
-            for (int mode : availableColorModes) {
-                if (mode == colorMode) {
-                    return true;
-                }
-            }
-        }
-        return false;
+        return mColorDisplayManager.setNightDisplayColorTemperature(colorTemperature);
     }
 
     /**
      * Get the current color mode.
      */
     public int getColorMode() {
-        if (getAccessibilityTransformActivated()) {
-            if (isColorModeAvailable(COLOR_MODE_SATURATED)) {
-                return COLOR_MODE_SATURATED;
-            } else if (isColorModeAvailable(COLOR_MODE_AUTOMATIC)) {
-                return COLOR_MODE_AUTOMATIC;
-            }
-        }
-
-        int colorMode = System.getIntForUser(mContext.getContentResolver(),
-                System.DISPLAY_COLOR_MODE, -1, mUserId);
-        if (colorMode == -1) {
-            // There might be a system property controlling color mode that we need to respect; if
-            // not, this will set a suitable default.
-            colorMode = getCurrentColorModeFromSystemProperties();
-        }
-
-        // This happens when a color mode is no longer available (e.g., after system update or B&R)
-        // or the device does not support any color mode.
-        if (!isColorModeAvailable(colorMode)) {
-            if (colorMode == COLOR_MODE_BOOSTED && isColorModeAvailable(COLOR_MODE_NATURAL)) {
-                colorMode = COLOR_MODE_NATURAL;
-            } else if (colorMode == COLOR_MODE_SATURATED
-                    && isColorModeAvailable(COLOR_MODE_AUTOMATIC)) {
-                colorMode = COLOR_MODE_AUTOMATIC;
-            } else if (colorMode == COLOR_MODE_AUTOMATIC
-                    && isColorModeAvailable(COLOR_MODE_SATURATED)) {
-                colorMode = COLOR_MODE_SATURATED;
-            } else {
-                colorMode = -1;
-            }
-        }
-
-        return colorMode;
+        return mColorDisplayManager.getColorMode();
     }
 
     /**
@@ -400,47 +174,21 @@
      * @param colorMode the color mode
      */
     public void setColorMode(@ColorMode int colorMode) {
-        if (!isColorModeAvailable(colorMode)) {
-            throw new IllegalArgumentException("Invalid colorMode: " + colorMode);
-        }
-        System.putIntForUser(mContext.getContentResolver(), System.DISPLAY_COLOR_MODE, colorMode,
-                mUserId);
+        mColorDisplayManager.setColorMode(colorMode);
     }
 
     /**
      * Returns the minimum allowed color temperature (in Kelvin) to tint the display when activated.
      */
     public int getMinimumColorTemperature() {
-        return mContext.getResources().getInteger(
-                R.integer.config_nightDisplayColorTemperatureMin);
+        return ColorDisplayManager.getMinimumColorTemperature(mContext);
     }
 
     /**
      * Returns the maximum allowed color temperature (in Kelvin) to tint the display when activated.
      */
     public int getMaximumColorTemperature() {
-        return mContext.getResources().getInteger(
-                R.integer.config_nightDisplayColorTemperatureMax);
-    }
-
-    /**
-     * Returns the default color temperature (in Kelvin) to tint the display when activated.
-     */
-    public int getDefaultColorTemperature() {
-        return mContext.getResources().getInteger(
-                R.integer.config_nightDisplayColorTemperatureDefault);
-    }
-
-    /**
-     * Returns true if any Accessibility color transforms are enabled.
-     */
-    public boolean getAccessibilityTransformActivated() {
-        final ContentResolver cr = mContext.getContentResolver();
-        return
-            Secure.getIntForUser(cr, Secure.ACCESSIBILITY_DISPLAY_INVERSION_ENABLED,
-                    0, mUserId) == 1
-            || Secure.getIntForUser(cr, Secure.ACCESSIBILITY_DISPLAY_DALTONIZER_ENABLED,
-                    0, mUserId) == 1;
+        return ColorDisplayManager.getMaximumColorTemperature(mContext);
     }
 
     private void onSettingChanged(@NonNull String setting) {
@@ -465,13 +213,6 @@
                 case Secure.NIGHT_DISPLAY_COLOR_TEMPERATURE:
                     mCallback.onColorTemperatureChanged(getColorTemperature());
                     break;
-                case System.DISPLAY_COLOR_MODE:
-                    mCallback.onDisplayColorModeChanged(getColorMode());
-                    break;
-                case Secure.ACCESSIBILITY_DISPLAY_INVERSION_ENABLED:
-                case Secure.ACCESSIBILITY_DISPLAY_DALTONIZER_ENABLED:
-                    mCallback.onAccessibilityTransformChanged(getAccessibilityTransformActivated());
-                    break;
             }
         }
     }
@@ -514,25 +255,10 @@
                         false /* notifyForDescendants */, mContentObserver, mUserId);
                 cr.registerContentObserver(Secure.getUriFor(Secure.NIGHT_DISPLAY_COLOR_TEMPERATURE),
                         false /* notifyForDescendants */, mContentObserver, mUserId);
-                cr.registerContentObserver(System.getUriFor(System.DISPLAY_COLOR_MODE),
-                        false /* notifyForDecendants */, mContentObserver, mUserId);
-                cr.registerContentObserver(
-                        Secure.getUriFor(Secure.ACCESSIBILITY_DISPLAY_INVERSION_ENABLED),
-                        false /* notifyForDecendants */, mContentObserver, mUserId);
-                cr.registerContentObserver(
-                        Secure.getUriFor(Secure.ACCESSIBILITY_DISPLAY_DALTONIZER_ENABLED),
-                        false /* notifyForDecendants */, mContentObserver, mUserId);
             }
         }
     }
 
-    private MetricsLogger getMetricsLogger() {
-        if (mMetricsLogger == null) {
-            mMetricsLogger = new MetricsLogger();
-        }
-        return mMetricsLogger;
-    }
-
     /**
      * Callback invoked whenever the Night display settings are changed.
      */
@@ -568,19 +294,5 @@
          * @param colorTemperature the color temperature to tint the screen
          */
         default void onColorTemperatureChanged(int colorTemperature) {}
-
-        /**
-         * Callback invoked when the color mode changes.
-         *
-         * @param displayColorMode the color mode
-         */
-        default void onDisplayColorModeChanged(int displayColorMode) {}
-
-        /**
-         * Callback invoked when Accessibility color transforms change.
-         *
-         * @param state the state Accessibility color transforms (true of active)
-         */
-        default void onAccessibilityTransformChanged(boolean state) {}
     }
 }
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/app/IVoiceInteractionManagerService.aidl b/core/java/com/android/internal/app/IVoiceInteractionManagerService.aidl
index 5088cca..b85488ff 100644
--- a/core/java/com/android/internal/app/IVoiceInteractionManagerService.aidl
+++ b/core/java/com/android/internal/app/IVoiceInteractionManagerService.aidl
@@ -151,4 +151,19 @@
      */
     void getActiveServiceSupportedActions(in List<String> voiceActions,
      in IVoiceActionCheckCallback callback);
+
+    /**
+     * Sets the transcribed voice to the given string.
+     */
+    void setTranscription(String transcription);
+
+    /**
+     * Indicates that the transcription session is finished.
+     */
+    void clearTranscription(boolean immediate);
+
+    /**
+     * Sets the voice state indication based upon the given value.
+     */
+    void setVoiceState(int state);
 }
diff --git a/core/java/com/android/internal/app/IVoiceInteractionSessionListener.aidl b/core/java/com/android/internal/app/IVoiceInteractionSessionListener.aidl
index 87749d2..674ad5b 100644
--- a/core/java/com/android/internal/app/IVoiceInteractionSessionListener.aidl
+++ b/core/java/com/android/internal/app/IVoiceInteractionSessionListener.aidl
@@ -26,4 +26,20 @@
      * Called when a voice session is hidden.
      */
     void onVoiceSessionHidden();
+
+    /**
+     * Called when voice assistant transcription has been updated to the given string.
+     */
+    void onTranscriptionUpdate(in String transcription);
+
+    /**
+     * Called when voice transcription is completed.
+     */
+    void onTranscriptionComplete(in boolean immediate);
+
+    /**
+     * Called when the voice assistant's state has changed. Values are from
+     * VoiceInteractionService's VOICE_STATE* constants.
+     */
+    void onVoiceStateChange(in int state);
  }
\ No newline at end of file
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/net/NetworkStatsFactory.java b/core/java/com/android/internal/net/NetworkStatsFactory.java
index 9bacf9b..f848346 100644
--- a/core/java/com/android/internal/net/NetworkStatsFactory.java
+++ b/core/java/com/android/internal/net/NetworkStatsFactory.java
@@ -64,6 +64,9 @@
 
     private boolean mUseBpfStats;
 
+    // A persistent Snapshot since device start for eBPF stats
+    private final NetworkStats mPersistSnapshot;
+
     // TODO: only do adjustments in NetworkStatsService and remove this.
     /**
      * (Stacked interface) -> (base interface) association for all connected ifaces since boot.
@@ -135,6 +138,7 @@
         mStatsXtIfaceFmt = new File(procRoot, "net/xt_qtaguid/iface_stat_fmt");
         mStatsXtUid = new File(procRoot, "net/xt_qtaguid/stats");
         mUseBpfStats = useBpfStats;
+        mPersistSnapshot = new NetworkStats(SystemClock.elapsedRealtime(), -1);
     }
 
     public NetworkStats readBpfNetworkStatsDev() throws IOException {
@@ -268,6 +272,7 @@
         return stats;
     }
 
+    // TODO: delete the lastStats parameter
     private NetworkStats readNetworkStatsDetailInternal(int limitUid, String[] limitIfaces,
             int limitTag, NetworkStats lastStats) throws IOException {
         if (USE_NATIVE_PARSING) {
@@ -278,16 +283,28 @@
             } else {
                 stats = new NetworkStats(SystemClock.elapsedRealtime(), -1);
             }
-            if (nativeReadNetworkStatsDetail(stats, mStatsXtUid.getAbsolutePath(), limitUid,
-                    limitIfaces, limitTag, mUseBpfStats) != 0) {
-                throw new IOException("Failed to parse network stats");
+            if (mUseBpfStats) {
+                if (nativeReadNetworkStatsDetail(stats, mStatsXtUid.getAbsolutePath(), UID_ALL,
+                        null, TAG_ALL, mUseBpfStats) != 0) {
+                    throw new IOException("Failed to parse network stats");
+                }
+                mPersistSnapshot.setElapsedRealtime(stats.getElapsedRealtime());
+                mPersistSnapshot.combineAllValues(stats);
+                NetworkStats result = mPersistSnapshot.clone();
+                result.filter(limitUid, limitIfaces, limitTag);
+                return result;
+            } else {
+                if (nativeReadNetworkStatsDetail(stats, mStatsXtUid.getAbsolutePath(), limitUid,
+                        limitIfaces, limitTag, mUseBpfStats) != 0) {
+                    throw new IOException("Failed to parse network stats");
+                }
+                if (SANITY_CHECK_NATIVE) {
+                    final NetworkStats javaStats = javaReadNetworkStatsDetail(mStatsXtUid, limitUid,
+                            limitIfaces, limitTag);
+                    assertEquals(javaStats, stats);
+                }
+                return stats;
             }
-            if (SANITY_CHECK_NATIVE) {
-                final NetworkStats javaStats = javaReadNetworkStatsDetail(mStatsXtUid, limitUid,
-                        limitIfaces, limitTag);
-                assertEquals(javaStats, stats);
-            }
-            return stats;
         } else {
             return javaReadNetworkStatsDetail(mStatsXtUid, limitUid, limitIfaces, limitTag);
         }
diff --git a/core/java/com/android/internal/net/VpnConfig.java b/core/java/com/android/internal/net/VpnConfig.java
index fd03b3f..da8605e 100644
--- a/core/java/com/android/internal/net/VpnConfig.java
+++ b/core/java/com/android/internal/net/VpnConfig.java
@@ -28,6 +28,7 @@
 import android.net.IpPrefix;
 import android.net.LinkAddress;
 import android.net.Network;
+import android.net.ProxyInfo;
 import android.net.RouteInfo;
 import android.os.Parcel;
 import android.os.Parcelable;
@@ -104,6 +105,7 @@
     public boolean allowIPv4;
     public boolean allowIPv6;
     public Network[] underlyingNetworks;
+    public ProxyInfo proxyInfo;
 
     public void updateAllowedFamilies(InetAddress address) {
         if (address instanceof Inet4Address) {
@@ -164,6 +166,7 @@
         out.writeInt(allowIPv4 ? 1 : 0);
         out.writeInt(allowIPv6 ? 1 : 0);
         out.writeTypedArray(underlyingNetworks, flags);
+        out.writeParcelable(proxyInfo, flags);
     }
 
     public static final Parcelable.Creator<VpnConfig> CREATOR =
@@ -189,6 +192,7 @@
             config.allowIPv4 = in.readInt() != 0;
             config.allowIPv6 = in.readInt() != 0;
             config.underlyingNetworks = in.createTypedArray(Network.CREATOR);
+            config.proxyInfo = in.readParcelable(null);
             return config;
         }
 
@@ -220,6 +224,7 @@
                 .append(", allowIPv4=").append(allowIPv4)
                 .append(", allowIPv6=").append(allowIPv6)
                 .append(", underlyingNetworks=").append(Arrays.toString(underlyingNetworks))
+                .append(", proxyInfo=").append(proxyInfo.toString())
                 .append("}")
                 .toString();
     }
diff --git a/core/java/com/android/internal/net/VpnInfo.java b/core/java/com/android/internal/net/VpnInfo.java
index a676dac..b1a41287 100644
--- a/core/java/com/android/internal/net/VpnInfo.java
+++ b/core/java/com/android/internal/net/VpnInfo.java
@@ -32,11 +32,11 @@
 
     @Override
     public String toString() {
-        return "VpnInfo{" +
-                "ownerUid=" + ownerUid +
-                ", vpnIface='" + vpnIface + '\'' +
-                ", primaryUnderlyingIface='" + primaryUnderlyingIface + '\'' +
-                '}';
+        return "VpnInfo{"
+                + "ownerUid=" + ownerUid
+                + ", vpnIface='" + vpnIface + '\''
+                + ", primaryUnderlyingIface='" + primaryUnderlyingIface + '\''
+                + '}';
     }
 
     @Override
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/BinderCallsStats.java b/core/java/com/android/internal/os/BinderCallsStats.java
index 6454352..2c272de 100644
--- a/core/java/com/android/internal/os/BinderCallsStats.java
+++ b/core/java/com/android/internal/os/BinderCallsStats.java
@@ -49,7 +49,7 @@
  * per thread, uid or call description.
  */
 public class BinderCallsStats implements BinderInternal.Observer {
-    public static final boolean ENABLED_DEFAULT = false;
+    public static final boolean ENABLED_DEFAULT = true;
     public static final boolean DETAILED_TRACKING_DEFAULT = true;
     public static final int PERIODIC_SAMPLING_INTERVAL_DEFAULT = 100;
     public static final int MAX_BINDER_CALL_STATS_COUNT_DEFAULT = 5000;
diff --git a/core/java/com/android/internal/os/WebViewZygoteInit.java b/core/java/com/android/internal/os/WebViewZygoteInit.java
index 0b329d7..c8d30b2 100644
--- a/core/java/com/android/internal/os/WebViewZygoteInit.java
+++ b/core/java/com/android/internal/os/WebViewZygoteInit.java
@@ -17,8 +17,9 @@
 package com.android.internal.os;
 
 import android.app.ApplicationLoaders;
+import android.app.LoadedApk;
+import android.content.pm.ApplicationInfo;
 import android.net.LocalSocket;
-import android.os.Build;
 import android.text.TextUtils;
 import android.util.Log;
 import android.webkit.WebViewFactory;
@@ -66,6 +67,34 @@
         }
 
         @Override
+        protected boolean canPreloadApp() {
+            return true;
+        }
+
+        @Override
+        protected void handlePreloadApp(ApplicationInfo appInfo) {
+            Log.i(TAG, "Beginning application preload for " + appInfo.packageName);
+            LoadedApk loadedApk = new LoadedApk(null, appInfo, null, null, false, true, false);
+            ClassLoader loader = loadedApk.getClassLoader();
+            doPreload(loader, WebViewFactory.getWebViewLibrary(appInfo));
+
+            // Add the APK to the Zygote's list of allowed files for children.
+            Zygote.nativeAllowFileAcrossFork(appInfo.sourceDir);
+            if (appInfo.splitSourceDirs != null) {
+                for (String path : appInfo.splitSourceDirs) {
+                    Zygote.nativeAllowFileAcrossFork(path);
+                }
+            }
+            if (appInfo.sharedLibraryFiles != null) {
+                for (String path : appInfo.sharedLibraryFiles) {
+                    Zygote.nativeAllowFileAcrossFork(path);
+                }
+            }
+
+            Log.i(TAG, "Application preload done");
+        }
+
+        @Override
         protected void handlePreloadPackage(String packagePath, String libsPath, String libFileName,
                 String cacheKey) {
             Log.i(TAG, "Beginning package preload");
@@ -76,16 +105,22 @@
             ClassLoader loader = ApplicationLoaders.getDefault().createAndCacheWebViewClassLoader(
                     packagePath, libsPath, cacheKey);
 
-            // Load the native library using WebViewLibraryLoader to share the RELRO data with other
-            // processes.
-            WebViewLibraryLoader.loadNativeLibrary(loader, libFileName);
-
             // Add the APK to the Zygote's list of allowed files for children.
             String[] packageList = TextUtils.split(packagePath, File.pathSeparator);
             for (String packageEntry : packageList) {
                 Zygote.nativeAllowFileAcrossFork(packageEntry);
             }
 
+            doPreload(loader, libFileName);
+
+            Log.i(TAG, "Package preload done");
+        }
+
+        private void doPreload(ClassLoader loader, String libFileName) {
+            // Load the native library using WebViewLibraryLoader to share the RELRO data with other
+            // processes.
+            WebViewLibraryLoader.loadNativeLibrary(loader, libFileName);
+
             // Once we have the classloader, look up the WebViewFactoryProvider implementation and
             // call preloadInZygote() on it to give it the opportunity to preload the native library
             // and perform any other initialisation work that should be shared among the children.
@@ -114,8 +149,6 @@
             } catch (IOException ioe) {
                 throw new IllegalStateException("Error writing to command socket", ioe);
             }
-
-            Log.i(TAG, "Package preload done");
         }
     }
 
diff --git a/core/java/com/android/internal/os/Zygote.java b/core/java/com/android/internal/os/Zygote.java
index f609f2f..069413f 100644
--- a/core/java/com/android/internal/os/Zygote.java
+++ b/core/java/com/android/internal/os/Zygote.java
@@ -117,7 +117,13 @@
     /** Number of bytes sent to the Zygote over blastula pipes or the pool event FD */
     public static final int BLASTULA_MANAGEMENT_MESSAGE_BYTES = 8;
 
-    /** If the blastula pool should be created and used to start applications */
+    /**
+     * If the blastula pool should be created and used to start applications.
+     *
+     * Setting this value to false will disable the creation, maintenance, and use of the blastula
+     * pool.  When the blastula pool is disabled the application lifecycle will be identical to
+     * previous versions of Android.
+     */
     public static final boolean BLASTULA_POOL_ENABLED = false;
 
     /**
@@ -187,6 +193,11 @@
     // TODO (chriswailes): This must be updated at the same time as sBlastulaPoolMax.
     static int sBlastulaPoolRefillThreshold = (sBlastulaPoolMax / 2);
 
+    /**
+     * @hide for internal use only
+     */
+    public static final int SOCKET_BUFFER_SIZE = 256;
+
     private static LocalServerSocket sBlastulaPoolSocket = null;
 
     /** a prototype instance for a future List.toArray() */
@@ -234,14 +245,14 @@
     public static int forkAndSpecialize(int uid, int gid, int[] gids, int runtimeFlags,
             int[][] rlimits, int mountExternal, String seInfo, String niceName, int[] fdsToClose,
             int[] fdsToIgnore, boolean startChildZygote, String instructionSet, String appDataDir,
-            String packageName, String[] packagesForUid, String[] visibleVolIds) {
+            String packageName, String[] packagesForUID, String[] visibleVolIDs) {
         VM_HOOKS.preFork();
         // Resets nice priority for zygote process.
         resetNicePriority();
         int pid = nativeForkAndSpecialize(
                 uid, gid, gids, runtimeFlags, rlimits, mountExternal, seInfo, niceName, fdsToClose,
                 fdsToIgnore, startChildZygote, instructionSet, appDataDir, packageName,
-                packagesForUid, visibleVolIds);
+                packagesForUID, visibleVolIDs);
         // Enable tracing as soon as possible for the child process.
         if (pid == 0) {
             Trace.setTracingEnabled(true, runtimeFlags);
@@ -256,7 +267,7 @@
     private static native int nativeForkAndSpecialize(int uid, int gid, int[] gids,
             int runtimeFlags, int[][] rlimits, int mountExternal, String seInfo, String niceName,
             int[] fdsToClose, int[] fdsToIgnore, boolean startChildZygote, String instructionSet,
-            String appDataDir, String packageName, String[] packagesForUid, String[] visibleVolIds);
+            String appDataDir, String packageName, String[] packagesForUID, String[] visibleVolIDs);
 
     /**
      * Specialize a Blastula instance.  The current VM must have been started
@@ -281,12 +292,12 @@
      */
     public static void specializeBlastula(int uid, int gid, int[] gids, int runtimeFlags,
             int[][] rlimits, int mountExternal, String seInfo, String niceName,
-            boolean startChildZygote, String instructionSet, String appDataDir,
-            String packageName, String[] packagesForUid, String[] visibleVolIds) {
+            boolean startChildZygote, String instructionSet, String appDataDir, String packageName,
+            String[] packagesForUID, String[] visibleVolIDs) {
 
         nativeSpecializeBlastula(uid, gid, gids, runtimeFlags, rlimits, mountExternal, seInfo,
                                  niceName, startChildZygote, instructionSet, appDataDir,
-                                 packageName, packagesForUid, visibleVolIds);
+                                 packageName, packagesForUID, visibleVolIDs);
 
         // Enable tracing as soon as possible for the child process.
         Trace.setTracingEnabled(true, runtimeFlags);
@@ -306,7 +317,7 @@
     private static native void nativeSpecializeBlastula(int uid, int gid, int[] gids,
             int runtimeFlags, int[][] rlimits, int mountExternal, String seInfo, String niceName,
             boolean startChildZygote, String instructionSet, String appDataDir, String packageName,
-            String[] packagesForUid, String[] visibleVolIds);
+            String[] packagesForUID, String[] visibleVolIDs);
 
     /**
      * Called to do any initialization before starting an application.
diff --git a/core/java/com/android/internal/os/ZygoteArguments.java b/core/java/com/android/internal/os/ZygoteArguments.java
index df89b26..24a08ca 100644
--- a/core/java/com/android/internal/os/ZygoteArguments.java
+++ b/core/java/com/android/internal/os/ZygoteArguments.java
@@ -339,6 +339,8 @@
                 mMountExternal = Zygote.MOUNT_EXTERNAL_FULL;
             }  else if (arg.equals("--mount-external-installer")) {
                 mMountExternal = Zygote.MOUNT_EXTERNAL_INSTALLER;
+            }  else if (arg.equals("--mount-external-legacy")) {
+                mMountExternal = Zygote.MOUNT_EXTERNAL_LEGACY;
             } else if (arg.equals("--query-abi-list")) {
                 mAbiListQuery = true;
             } else if (arg.equals("--get-pid")) {
diff --git a/core/java/com/android/internal/os/ZygoteConnection.java b/core/java/com/android/internal/os/ZygoteConnection.java
index 78ecee1..ffbe8eb 100644
--- a/core/java/com/android/internal/os/ZygoteConnection.java
+++ b/core/java/com/android/internal/os/ZygoteConnection.java
@@ -242,7 +242,7 @@
             fdsToClose[0] = fd.getInt$();
         }
 
-        fd = zygoteServer.getServerSocketFileDescriptor();
+        fd = zygoteServer.getZygoteSocketFileDescriptor();
 
         if (fd != null) {
             fdsToClose[1] = fd.getInt$();
diff --git a/core/java/com/android/internal/os/ZygoteInit.java b/core/java/com/android/internal/os/ZygoteInit.java
index 2f00c07..e3e55ed 100644
--- a/core/java/com/android/internal/os/ZygoteInit.java
+++ b/core/java/com/android/internal/os/ZygoteInit.java
@@ -269,7 +269,7 @@
 
         try {
             BufferedReader br =
-                    new BufferedReader(new InputStreamReader(is), 256);
+                    new BufferedReader(new InputStreamReader(is), Zygote.SOCKET_BUFFER_SIZE);
 
             int count = 0;
             String line;
@@ -750,7 +750,7 @@
             throw new RuntimeException("Failed to setpgid(0,0)", ex);
         }
 
-        final Runnable caller;
+        Runnable caller;
         try {
             // Report Zygote start time to tron unless it is a runtime restart
             if (!"1".equals(SystemProperties.get("sys.boot_completed"))) {
@@ -786,7 +786,17 @@
                 throw new RuntimeException("No ABI list supplied.");
             }
 
-            zygoteServer.registerServerSocketFromEnv(socketName);
+            // TODO (chriswailes): Wrap these three calls in a helper function?
+            final String blastulaSocketName =
+                    socketName.equals(ZygoteProcess.ZYGOTE_SOCKET_NAME)
+                            ? ZygoteProcess.BLASTULA_POOL_SOCKET_NAME
+                            : ZygoteProcess.BLASTULA_POOL_SECONDARY_SOCKET_NAME;
+
+            zygoteServer.createZygoteSocket(socketName);
+            Zygote.createBlastulaSocket(blastulaSocketName);
+
+            Zygote.getSocketFDs(socketName.equals(ZygoteProcess.ZYGOTE_SOCKET_NAME));
+
             // In some configurations, we avoid preloading resources and classes eagerly.
             // In such cases, we will preload things prior to our first fork.
             if (!enableLazyPreload) {
@@ -829,11 +839,18 @@
                 }
             }
 
-            Log.i(TAG, "Accepting command socket connections");
+            // If the return value is null then this is the zygote process
+            // returning to the normal control flow.  If it returns a Runnable
+            // object then this is a blastula that has finished specializing.
+            caller = Zygote.initBlastulaPool();
 
-            // The select loop returns early in the child process after a fork and
-            // loops forever in the zygote.
-            caller = zygoteServer.runSelectLoop(abiList);
+            if (caller == null) {
+                Log.i(TAG, "Accepting command socket connections");
+
+                // The select loop returns early in the child process after a fork and
+                // loops forever in the zygote.
+                caller = zygoteServer.runSelectLoop(abiList);
+            }
         } catch (Throwable ex) {
             Log.e(TAG, "System zygote died with exception", ex);
             throw ex;
diff --git a/core/java/com/android/internal/os/ZygoteServer.java b/core/java/com/android/internal/os/ZygoteServer.java
index c1bfde1..a78c095 100644
--- a/core/java/com/android/internal/os/ZygoteServer.java
+++ b/core/java/com/android/internal/os/ZygoteServer.java
@@ -26,6 +26,8 @@
 import android.util.Log;
 import android.util.Slog;
 
+import java.io.ByteArrayInputStream;
+import java.io.DataInputStream;
 import java.io.FileDescriptor;
 import java.io.IOException;
 import java.util.ArrayList;
@@ -40,18 +42,17 @@
  * client protocol.
  */
 class ZygoteServer {
+    // TODO (chriswailes): Change this so it is set with Zygote or ZygoteSecondary as appropriate
     public static final String TAG = "ZygoteServer";
 
-    private static final String ANDROID_SOCKET_PREFIX = "ANDROID_SOCKET_";
-
     /**
      * Listening socket that accepts new server connections.
      */
-    private LocalServerSocket mServerSocket;
+    private LocalServerSocket mZygoteSocket;
 
     /**
-     * Whether or not mServerSocket's underlying FD should be closed directly.
-     * If mServerSocket is created with an existing FD, closing the socket does
+     * Whether or not mZygoteSocket's underlying FD should be closed directly.
+     * If mZygoteSocket is created with an existing FD, closing the socket does
      * not close the FD and it must be closed explicitly. If the socket is created
      * with a name instead, then closing the socket will close the underlying FD
      * and it should not be double-closed.
@@ -70,31 +71,17 @@
     }
 
     /**
-     * Registers a server socket for zygote command connections. This locates the server socket
-     * file descriptor through an ANDROID_SOCKET_ environment variable.
+     * Creates a managed object representing the Zygote socket that has already
+     * been initialized and bound by init.
+     *
+     * TODO (chriswailes): Move the name selection logic into this function.
      *
      * @throws RuntimeException when open fails
      */
-    void registerServerSocketFromEnv(String socketName) {
-        if (mServerSocket == null) {
-            int fileDesc;
-            final String fullSocketName = ANDROID_SOCKET_PREFIX + socketName;
-            try {
-                String env = System.getenv(fullSocketName);
-                fileDesc = Integer.parseInt(env);
-            } catch (RuntimeException ex) {
-                throw new RuntimeException(fullSocketName + " unset or invalid", ex);
-            }
-
-            try {
-                FileDescriptor fd = new FileDescriptor();
-                fd.setInt$(fileDesc);
-                mServerSocket = new LocalServerSocket(fd);
-                mCloseSocketFd = true;
-            } catch (IOException ex) {
-                throw new RuntimeException(
-                        "Error binding to local socket '" + fileDesc + "'", ex);
-            }
+    void createZygoteSocket(String socketName) {
+        if (mZygoteSocket == null) {
+            mZygoteSocket = Zygote.createManagedSocketFromInitSocket(socketName);
+            mCloseSocketFd = true;
         }
     }
 
@@ -103,9 +90,9 @@
      * at the specified name in the abstract socket namespace.
      */
     void registerServerSocketAtAbstractName(String socketName) {
-        if (mServerSocket == null) {
+        if (mZygoteSocket == null) {
             try {
-                mServerSocket = new LocalServerSocket(socketName);
+                mZygoteSocket = new LocalServerSocket(socketName);
                 mCloseSocketFd = false;
             } catch (IOException ex) {
                 throw new RuntimeException(
@@ -120,7 +107,7 @@
      */
     private ZygoteConnection acceptCommandPeer(String abiList) {
         try {
-            return createNewConnection(mServerSocket.accept(), abiList);
+            return createNewConnection(mZygoteSocket.accept(), abiList);
         } catch (IOException ex) {
             throw new RuntimeException(
                     "IOException during accept()", ex);
@@ -138,9 +125,9 @@
      */
     void closeServerSocket() {
         try {
-            if (mServerSocket != null) {
-                FileDescriptor fd = mServerSocket.getFileDescriptor();
-                mServerSocket.close();
+            if (mZygoteSocket != null) {
+                FileDescriptor fd = mZygoteSocket.getFileDescriptor();
+                mZygoteSocket.close();
                 if (fd != null && mCloseSocketFd) {
                     Os.close(fd);
                 }
@@ -151,7 +138,7 @@
             Log.e(TAG, "Zygote:  error closing descriptor", ex);
         }
 
-        mServerSocket = null;
+        mZygoteSocket = null;
     }
 
     /**
@@ -160,8 +147,8 @@
      * closure after a child process is forked off.
      */
 
-    FileDescriptor getServerSocketFileDescriptor() {
-        return mServerSocket.getFileDescriptor();
+    FileDescriptor getZygoteSocketFileDescriptor() {
+        return mZygoteSocket.getFileDescriptor();
     }
 
     /**
@@ -170,36 +157,67 @@
      * worth at a time.
      */
     Runnable runSelectLoop(String abiList) {
-        ArrayList<FileDescriptor> fds = new ArrayList<FileDescriptor>();
+        ArrayList<FileDescriptor> socketFDs = new ArrayList<FileDescriptor>();
         ArrayList<ZygoteConnection> peers = new ArrayList<ZygoteConnection>();
 
-        fds.add(mServerSocket.getFileDescriptor());
+        socketFDs.add(mZygoteSocket.getFileDescriptor());
         peers.add(null);
 
         while (true) {
-            StructPollfd[] pollFds = new StructPollfd[fds.size()];
-            for (int i = 0; i < pollFds.length; ++i) {
-                pollFds[i] = new StructPollfd();
-                pollFds[i].fd = fds.get(i);
-                pollFds[i].events = (short) POLLIN;
+            int[] blastulaPipeFDs = Zygote.getBlastulaPipeFDs();
+
+            // Space for all of the socket FDs, the Blastula Pool Event FD, and
+            // all of the open blastula read pipe FDs.
+            StructPollfd[] pollFDs =
+                new StructPollfd[socketFDs.size() + 1 + blastulaPipeFDs.length];
+
+            int pollIndex = 0;
+            for (FileDescriptor socketFD : socketFDs) {
+                pollFDs[pollIndex] = new StructPollfd();
+                pollFDs[pollIndex].fd = socketFD;
+                pollFDs[pollIndex].events = (short) POLLIN;
+                ++pollIndex;
             }
+
+            final int blastulaPoolEventFDIndex = pollIndex;
+            pollFDs[pollIndex] = new StructPollfd();
+            pollFDs[pollIndex].fd = Zygote.sBlastulaPoolEventFD;
+            pollFDs[pollIndex].events = (short) POLLIN;
+            ++pollIndex;
+
+            for (int blastulaPipeFD : blastulaPipeFDs) {
+                FileDescriptor managedFd = new FileDescriptor();
+                managedFd.setInt$(blastulaPipeFD);
+
+                pollFDs[pollIndex] = new StructPollfd();
+                pollFDs[pollIndex].fd = managedFd;
+                pollFDs[pollIndex].events = (short) POLLIN;
+                ++pollIndex;
+            }
+
             try {
-                Os.poll(pollFds, -1);
+                Os.poll(pollFDs, -1);
             } catch (ErrnoException ex) {
                 throw new RuntimeException("poll failed", ex);
             }
-            for (int i = pollFds.length - 1; i >= 0; --i) {
-                if ((pollFds[i].revents & POLLIN) == 0) {
+
+            while (--pollIndex >= 0) {
+                if ((pollFDs[pollIndex].revents & POLLIN) == 0) {
                     continue;
                 }
 
-                if (i == 0) {
+                if (pollIndex == 0) {
+                    // Zygote server socket
+
                     ZygoteConnection newPeer = acceptCommandPeer(abiList);
                     peers.add(newPeer);
-                    fds.add(newPeer.getFileDescriptor());
-                } else {
+                    socketFDs.add(newPeer.getFileDescriptor());
+
+                } else if (pollIndex < blastulaPoolEventFDIndex) {
+                    // Session socket accepted from the Zygote server socket
+
                     try {
-                        ZygoteConnection connection = peers.get(i);
+                        ZygoteConnection connection = peers.get(pollIndex);
                         final Runnable command = connection.processOneCommand(this);
 
                         if (mIsForkChild) {
@@ -217,12 +235,12 @@
                             }
 
                             // We don't know whether the remote side of the socket was closed or
-                            // not until we attempt to read from it from processOneCommand. This shows up as
-                            // a regular POLLIN event in our regular processing loop.
+                            // not until we attempt to read from it from processOneCommand. This
+                            // shows up as a regular POLLIN event in our regular processing loop.
                             if (connection.isClosedByPeer()) {
                                 connection.closeSocket();
-                                peers.remove(i);
-                                fds.remove(i);
+                                peers.remove(pollIndex);
+                                socketFDs.remove(pollIndex);
                             }
                         }
                     } catch (Exception e) {
@@ -234,13 +252,13 @@
 
                             Slog.e(TAG, "Exception executing zygote command: ", e);
 
-                            // Make sure the socket is closed so that the other end knows immediately
-                            // that something has gone wrong and doesn't time out waiting for a
-                            // response.
-                            ZygoteConnection conn = peers.remove(i);
+                            // Make sure the socket is closed so that the other end knows
+                            // immediately that something has gone wrong and doesn't time out
+                            // waiting for a response.
+                            ZygoteConnection conn = peers.remove(pollIndex);
                             conn.closeSocket();
 
-                            fds.remove(i);
+                            socketFDs.remove(pollIndex);
                         } else {
                             // We're in the child so any exception caught here has happened post
                             // fork and before we execute ActivityThread.main (or any other main()
@@ -254,6 +272,55 @@
                         // is returned.
                         mIsForkChild = false;
                     }
+                } else {
+                    // Either the blastula pool event FD or a blastula reporting pipe.
+
+                    // If this is the event FD the payload will be the number of blastulas removed.
+                    // If this is a reporting pipe FD the payload will be the PID of the blastula
+                    // that was just specialized.
+                    long messagePayload = -1;
+
+                    try {
+                        byte[] buffer = new byte[Zygote.BLASTULA_MANAGEMENT_MESSAGE_BYTES];
+                        int readBytes = Os.read(pollFDs[pollIndex].fd, buffer, 0, buffer.length);
+
+                        if (readBytes == Zygote.BLASTULA_MANAGEMENT_MESSAGE_BYTES) {
+                            DataInputStream inputStream =
+                                    new DataInputStream(new ByteArrayInputStream(buffer));
+
+                            messagePayload = inputStream.readLong();
+                        } else {
+                            Log.e(TAG, "Incomplete read from blastula management FD of size "
+                                    + readBytes);
+                            continue;
+                        }
+                    } catch (Exception ex) {
+                        if (pollIndex == blastulaPoolEventFDIndex) {
+                            Log.e(TAG, "Failed to read from blastula pool event FD: "
+                                    + ex.getMessage());
+                        } else {
+                            Log.e(TAG, "Failed to read from blastula reporting pipe: "
+                                    + ex.getMessage());
+                        }
+
+                        continue;
+                    }
+
+                    if (pollIndex > blastulaPoolEventFDIndex) {
+                        Zygote.removeBlastulaTableEntry((int) messagePayload);
+                    }
+
+                    int[] sessionSocketRawFDs =
+                            socketFDs.subList(1, socketFDs.size())
+                                .stream()
+                                .mapToInt(fd -> fd.getInt$())
+                                .toArray();
+
+                    final Runnable command = Zygote.fillBlastulaPool(sessionSocketRawFDs);
+
+                    if (command != null) {
+                        return command;
+                    }
                 }
             }
         }
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/usb/DumpUtils.java b/core/java/com/android/internal/usb/DumpUtils.java
index 240c2e7..3260136 100644
--- a/core/java/com/android/internal/usb/DumpUtils.java
+++ b/core/java/com/android/internal/usb/DumpUtils.java
@@ -196,6 +196,15 @@
         }
     }
 
+    private static void writeContaminantPresenceStatus(@NonNull DualDumpOutputStream dump,
+            @NonNull String idName, long id, int contaminantPresenceStatus) {
+        if (dump.isProto()) {
+            dump.write(idName, id, contaminantPresenceStatus);
+        } else {
+            dump.write(idName, id,
+                    UsbPort.contaminantPresenceStatusToString(contaminantPresenceStatus));
+        }
+    }
 
     public static void writePortStatus(@NonNull DualDumpOutputStream dump, @NonNull String idName,
             long id, @NonNull UsbPortStatus status) {
@@ -232,6 +241,10 @@
             dump.end(roleCombinationToken);
         }
 
+        writeContaminantPresenceStatus(dump, "contaminant_presence_status",
+                UsbPortStatusProto.CONTAMINANT_PRESENCE_STATUS,
+                status.getContaminantDetectionStatus());
+
         dump.end(token);
     }
 }
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/java/com/android/server/SystemConfig.java b/core/java/com/android/server/SystemConfig.java
index 841e5b6..3537465 100644
--- a/core/java/com/android/server/SystemConfig.java
+++ b/core/java/com/android/server/SystemConfig.java
@@ -140,6 +140,10 @@
     // without throttling, as read from the configuration files.
     final ArraySet<String> mAllowUnthrottledLocation = new ArraySet<>();
 
+    // These are the packages that are white-listed to be able to retrieve location even when user
+    // location settings are off, for emergency purposes, as read from the configuration files.
+    final ArraySet<String> mAllowIgnoreLocationSettings = new ArraySet<>();
+
     // These are the action strings of broadcasts which are whitelisted to
     // be delivered anonymously even to apps which target O+.
     final ArraySet<String> mAllowImplicitBroadcasts = new ArraySet<>();
@@ -255,6 +259,10 @@
         return mAllowUnthrottledLocation;
     }
 
+    public ArraySet<String> getAllowIgnoreLocationSettings() {
+        return mAllowIgnoreLocationSettings;
+    }
+
     public ArraySet<String> getLinkedApps() {
         return mLinkedApps;
     }
@@ -682,6 +690,20 @@
                         }
                         XmlUtils.skipCurrentTag(parser);
                     } break;
+                    case "allow-ignore-location-settings": {
+                        if (allowAll) {
+                            String pkgname = parser.getAttributeValue(null, "package");
+                            if (pkgname == null) {
+                                Slog.w(TAG, "<" + name + "> without package in "
+                                        + permFile + " at " + parser.getPositionDescription());
+                            } else {
+                                mAllowIgnoreLocationSettings.add(pkgname);
+                            }
+                        } else {
+                            logNotAllowedInPartition(name, permFile, parser);
+                        }
+                        XmlUtils.skipCurrentTag(parser);
+                    } break;
                     case "allow-implicit-broadcast": {
                         if (allowAll) {
                             String action = parser.getAttributeValue(null, "action");
diff --git a/core/jni/Android.bp b/core/jni/Android.bp
index be12700..0466aaa 100644
--- a/core/jni/Android.bp
+++ b/core/jni/Android.bp
@@ -275,6 +275,7 @@
         "libselinux",
         "libandroidicu",
         "libmedia",
+        "libmedia_helper",
         "libmediametrics",
         "libmeminfo",
         "libaudioclient",
diff --git a/core/jni/AndroidRuntime.cpp b/core/jni/AndroidRuntime.cpp
index 18d9b5a..f458299 100644
--- a/core/jni/AndroidRuntime.cpp
+++ b/core/jni/AndroidRuntime.cpp
@@ -152,6 +152,8 @@
 extern int register_android_graphics_text_LineBreaker(JNIEnv *env);
 extern int register_android_view_DisplayEventReceiver(JNIEnv* env);
 extern int register_android_view_DisplayListCanvas(JNIEnv* env);
+extern int register_android_view_InputApplicationHandle(JNIEnv* env);
+extern int register_android_view_InputWindowHandle(JNIEnv* env);
 extern int register_android_view_TextureLayer(JNIEnv* env);
 extern int register_android_view_RenderNode(JNIEnv* env);
 extern int register_android_view_RenderNodeAnimator(JNIEnv* env);
@@ -1370,6 +1372,8 @@
     REG_JNI(register_android_view_RenderNode),
     REG_JNI(register_android_view_RenderNodeAnimator),
     REG_JNI(register_android_view_DisplayListCanvas),
+    REG_JNI(register_android_view_InputApplicationHandle),
+    REG_JNI(register_android_view_InputWindowHandle),
     REG_JNI(register_android_view_TextureLayer),
     REG_JNI(register_android_view_ThreadedRenderer),
     REG_JNI(register_android_view_Surface),
diff --git a/core/jni/android/graphics/Bitmap.cpp b/core/jni/android/graphics/Bitmap.cpp
index ad51c47..5de0883 100755
--- a/core/jni/android/graphics/Bitmap.cpp
+++ b/core/jni/android/graphics/Bitmap.cpp
@@ -19,6 +19,7 @@
 #include <hwui/Paint.h>
 #include <hwui/Bitmap.h>
 #include <renderthread/RenderProxy.h>
+#include <utils/Color.h>
 
 #include <android_runtime/android_hardware_HardwareBuffer.h>
 
@@ -602,6 +603,14 @@
     return static_cast<jint>(bitmap->getGenerationID());
 }
 
+static jboolean Bitmap_isConfigF16(JNIEnv* env, jobject, jlong bitmapHandle) {
+    LocalScopedBitmap bitmap(bitmapHandle);
+    if (bitmap->info().colorType() == kRGBA_F16_SkColorType) {
+        return JNI_TRUE;
+    }
+    return JNI_FALSE;
+}
+
 static jboolean Bitmap_isPremultiplied(JNIEnv* env, jobject, jlong bitmapHandle) {
     LocalScopedBitmap bitmap(bitmapHandle);
     if (bitmap->info().alphaType() == kPremul_SkAlphaType) {
@@ -1120,7 +1129,8 @@
     sp<GraphicBuffer> buffer(graphicBufferForJavaObject(env, graphicBuffer));
     // To support any color space, we need to pass an additional ColorSpace argument to
     // java Bitmap.createHardwareBitmap.
-    sk_sp<Bitmap> bitmap = Bitmap::createFrom(buffer, SkColorSpace::MakeSRGB());
+    SkColorType ct = uirenderer::PixelFormatToColorType(buffer->getPixelFormat());
+    sk_sp<Bitmap> bitmap = Bitmap::createFrom(buffer, ct, SkColorSpace::MakeSRGB());
     if (!bitmap.get()) {
         ALOGW("failed to create hardware bitmap from graphic buffer");
         return NULL;
@@ -1133,7 +1143,8 @@
     AHardwareBuffer* hwBuf = android_hardware_HardwareBuffer_getNativeHardwareBuffer(env,
         hardwareBuffer);
     sp<GraphicBuffer> buffer(AHardwareBuffer_to_GraphicBuffer(hwBuf));
-    sk_sp<Bitmap> bitmap = Bitmap::createFrom(buffer,
+    SkColorType ct = uirenderer::PixelFormatToColorType(buffer->getPixelFormat());
+    sk_sp<Bitmap> bitmap = Bitmap::createFrom(buffer, ct,
             GraphicsJNI::getNativeColorSpace(colorSpacePtr));
     if (!bitmap.get()) {
         ALOGW("failed to create hardware bitmap from hardware buffer");
@@ -1193,6 +1204,7 @@
     {   "nativeErase",              "(JJJ)V", (void*)Bitmap_eraseLong },
     {   "nativeRowBytes",           "(J)I", (void*)Bitmap_rowBytes },
     {   "nativeConfig",             "(J)I", (void*)Bitmap_config },
+    {   "nativeIsConfigF16",        "(J)Z", (void*)Bitmap_isConfigF16 },
     {   "nativeHasAlpha",           "(J)Z", (void*)Bitmap_hasAlpha },
     {   "nativeIsPremultiplied",    "(J)Z", (void*)Bitmap_isPremultiplied},
     {   "nativeSetHasAlpha",        "(JZZ)V", (void*)Bitmap_setHasAlpha},
diff --git a/core/jni/android/graphics/Paint.cpp b/core/jni/android/graphics/Paint.cpp
index 7679c5b..cc22ff0 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,21 @@
 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) {
+    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 +102,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 +291,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 +325,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 +587,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 +650,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 +659,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 +683,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 +877,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 +951,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 +963,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_hardware_input_InputApplicationHandle.cpp b/core/jni/android_hardware_input_InputApplicationHandle.cpp
index 10005dd..1ab5843 100644
--- a/core/jni/android_hardware_input_InputApplicationHandle.cpp
+++ b/core/jni/android_hardware_input_InputApplicationHandle.cpp
@@ -93,7 +93,7 @@
 
 // --- Global functions ---
 
-sp<InputApplicationHandle> android_server_InputApplicationHandle_getHandle(
+sp<InputApplicationHandle> android_view_InputApplicationHandle_getHandle(
         JNIEnv* env, jobject inputApplicationHandleObj) {
     if (!inputApplicationHandleObj) {
         return NULL;
@@ -108,7 +108,7 @@
     } else {
         jweak objWeak = env->NewWeakGlobalRef(inputApplicationHandleObj);
         handle = new NativeInputApplicationHandle(objWeak);
-        handle->incStrong((void*)android_server_InputApplicationHandle_getHandle);
+        handle->incStrong((void*)android_view_InputApplicationHandle_getHandle);
         env->SetLongField(inputApplicationHandleObj, gInputApplicationHandleClassInfo.ptr,
                 reinterpret_cast<jlong>(handle));
     }
@@ -118,7 +118,7 @@
 
 // --- JNI ---
 
-static void android_server_InputApplicationHandle_nativeDispose(JNIEnv* env, jobject obj) {
+static void android_view_InputApplicationHandle_nativeDispose(JNIEnv* env, jobject obj) {
     AutoMutex _l(gHandleMutex);
 
     jlong ptr = env->GetLongField(obj, gInputApplicationHandleClassInfo.ptr);
@@ -126,7 +126,7 @@
         env->SetLongField(obj, gInputApplicationHandleClassInfo.ptr, 0);
 
         NativeInputApplicationHandle* handle = reinterpret_cast<NativeInputApplicationHandle*>(ptr);
-        handle->decStrong((void*)android_server_InputApplicationHandle_getHandle);
+        handle->decStrong((void*)android_view_InputApplicationHandle_getHandle);
     }
 }
 
@@ -134,7 +134,7 @@
 static const JNINativeMethod gInputApplicationHandleMethods[] = {
     /* name, signature, funcPtr */
     { "nativeDispose", "()V",
-            (void*) android_server_InputApplicationHandle_nativeDispose },
+            (void*) android_view_InputApplicationHandle_nativeDispose },
 };
 
 #define FIND_CLASS(var, className) \
@@ -145,7 +145,7 @@
         var = env->GetFieldID(clazz, fieldName, fieldDescriptor); \
         LOG_FATAL_IF(! (var), "Unable to find field " fieldName);
 
-int register_android_server_InputApplicationHandle(JNIEnv* env) {
+int register_android_view_InputApplicationHandle(JNIEnv* env) {
     int res = jniRegisterNativeMethods(env, "android/view/InputApplicationHandle",
             gInputApplicationHandleMethods, NELEM(gInputApplicationHandleMethods));
     (void) res;  // Faked use when LOG_NDEBUG.
diff --git a/core/jni/android_hardware_input_InputApplicationHandle.h b/core/jni/android_hardware_input_InputApplicationHandle.h
index 7115611..5abeab4 100644
--- a/core/jni/android_hardware_input_InputApplicationHandle.h
+++ b/core/jni/android_hardware_input_InputApplicationHandle.h
@@ -14,8 +14,8 @@
  * limitations under the License.
  */
 
-#ifndef _ANDROID_SERVER_INPUT_APPLICATION_HANDLE_H
-#define _ANDROID_SERVER_INPUT_APPLICATION_HANDLE_H
+#ifndef _ANDROID_VIEW_INPUT_APPLICATION_HANDLE_H
+#define _ANDROID_VIEW_INPUT_APPLICATION_HANDLE_H
 
 #include <string>
 
@@ -40,9 +40,9 @@
 };
 
 
-extern sp<InputApplicationHandle> android_server_InputApplicationHandle_getHandle(
+extern sp<InputApplicationHandle> android_view_InputApplicationHandle_getHandle(
         JNIEnv* env, jobject inputApplicationHandleObj);
 
 } // namespace android
 
-#endif // _ANDROID_SERVER_INPUT_APPLICATION_HANDLE_H
+#endif // _ANDROID_VIEW_INPUT_APPLICATION_HANDLE_H
diff --git a/core/jni/android_hardware_input_InputWindowHandle.cpp b/core/jni/android_hardware_input_InputWindowHandle.cpp
index 76920f5..c0e45b1 100644
--- a/core/jni/android_hardware_input_InputWindowHandle.cpp
+++ b/core/jni/android_hardware_input_InputWindowHandle.cpp
@@ -159,7 +159,7 @@
             gInputWindowHandleClassInfo.inputApplicationHandle);
     if (inputApplicationHandleObj) {
         sp<InputApplicationHandle> inputApplicationHandle =
-            android_server_InputApplicationHandle_getHandle(env, inputApplicationHandleObj);
+            android_view_InputApplicationHandle_getHandle(env, inputApplicationHandleObj);
         if (inputApplicationHandle != nullptr) {
             inputApplicationHandle->updateInfo();
             mInfo.applicationInfo = *(inputApplicationHandle->getInfo());
@@ -174,7 +174,7 @@
 
 // --- Global functions ---
 
-sp<NativeInputWindowHandle> android_server_InputWindowHandle_getHandle(
+sp<NativeInputWindowHandle> android_view_InputWindowHandle_getHandle(
         JNIEnv* env, jobject inputWindowHandleObj) {
     if (!inputWindowHandleObj) {
         return NULL;
@@ -189,7 +189,7 @@
     } else {
         jweak objWeak = env->NewWeakGlobalRef(inputWindowHandleObj);
         handle = new NativeInputWindowHandle(objWeak);
-        handle->incStrong((void*)android_server_InputWindowHandle_getHandle);
+        handle->incStrong((void*)android_view_InputWindowHandle_getHandle);
         env->SetLongField(inputWindowHandleObj, gInputWindowHandleClassInfo.ptr,
                 reinterpret_cast<jlong>(handle));
     }
@@ -199,7 +199,7 @@
 
 // --- JNI ---
 
-static void android_server_InputWindowHandle_nativeDispose(JNIEnv* env, jobject obj) {
+static void android_view_InputWindowHandle_nativeDispose(JNIEnv* env, jobject obj) {
     AutoMutex _l(gHandleMutex);
 
     jlong ptr = env->GetLongField(obj, gInputWindowHandleClassInfo.ptr);
@@ -207,7 +207,7 @@
         env->SetLongField(obj, gInputWindowHandleClassInfo.ptr, 0);
 
         NativeInputWindowHandle* handle = reinterpret_cast<NativeInputWindowHandle*>(ptr);
-        handle->decStrong((void*)android_server_InputWindowHandle_getHandle);
+        handle->decStrong((void*)android_view_InputWindowHandle_getHandle);
     }
 }
 
@@ -215,7 +215,7 @@
 static const JNINativeMethod gInputWindowHandleMethods[] = {
     /* name, signature, funcPtr */
     { "nativeDispose", "()V",
-            (void*) android_server_InputWindowHandle_nativeDispose },
+            (void*) android_view_InputWindowHandle_nativeDispose },
 };
 
 #define FIND_CLASS(var, className) \
@@ -226,7 +226,7 @@
         var = env->GetFieldID(clazz, fieldName, fieldDescriptor); \
         LOG_FATAL_IF(! (var), "Unable to find field " fieldName);
 
-int register_android_server_InputWindowHandle(JNIEnv* env) {
+int register_android_view_InputWindowHandle(JNIEnv* env) {
     int res = jniRegisterNativeMethods(env, "android/view/InputWindowHandle",
             gInputWindowHandleMethods, NELEM(gInputWindowHandleMethods));
     (void) res;  // Faked use when LOG_NDEBUG.
diff --git a/core/jni/android_hardware_input_InputWindowHandle.h b/core/jni/android_hardware_input_InputWindowHandle.h
index 54b89f5..de5bd6e 100644
--- a/core/jni/android_hardware_input_InputWindowHandle.h
+++ b/core/jni/android_hardware_input_InputWindowHandle.h
@@ -14,8 +14,8 @@
  * limitations under the License.
  */
 
-#ifndef _ANDROID_SERVER_INPUT_WINDOW_HANDLE_H
-#define _ANDROID_SERVER_INPUT_WINDOW_HANDLE_H
+#ifndef _ANDROID_VIEW_INPUT_WINDOW_HANDLE_H
+#define _ANDROID_VIEW_INPUT_WINDOW_HANDLE_H
 
 #include <input/InputWindow.h>
 
@@ -38,9 +38,9 @@
 };
 
 
-extern sp<NativeInputWindowHandle> android_server_InputWindowHandle_getHandle(
+extern sp<NativeInputWindowHandle> android_view_InputWindowHandle_getHandle(
         JNIEnv* env, jobject inputWindowHandleObj);
 
 } // namespace android
 
-#endif // _ANDROID_SERVER_INPUT_WINDOW_HANDLE_H
+#endif // _ANDROID_VIEW_INPUT_WINDOW_HANDLE_H
diff --git a/core/jni/android_media_AudioTrack.cpp b/core/jni/android_media_AudioTrack.cpp
index 1065738..a0d99ec 100644
--- a/core/jni/android_media_AudioTrack.cpp
+++ b/core/jni/android_media_AudioTrack.cpp
@@ -23,6 +23,7 @@
 #include "core_jni_helpers.h"
 
 #include <utils/Log.h>
+#include <media/AudioParameter.h>
 #include <media/AudioSystem.h>
 #include <media/AudioTrack.h>
 
@@ -1311,6 +1312,33 @@
 }
 
 // ----------------------------------------------------------------------------
+static void android_media_AudioTrack_set_delay_padding(JNIEnv *env,  jobject thiz,
+        jint delayInFrames, jint paddingInFrames) {
+    sp<AudioTrack> lpTrack = getAudioTrack(env, thiz);
+    if (lpTrack == NULL) {
+        jniThrowException(env, "java/lang/IllegalStateException",
+                "AudioTrack not initialized");
+        return;
+    }
+    AudioParameter param = AudioParameter();
+    param.addInt(String8(AUDIO_OFFLOAD_CODEC_DELAY_SAMPLES), (int) delayInFrames);
+    param.addInt(String8(AUDIO_OFFLOAD_CODEC_PADDING_SAMPLES), (int) paddingInFrames);
+    lpTrack->setParameters(param.toString());
+}
+
+static void android_media_AudioTrack_set_eos(JNIEnv *env,  jobject thiz) {
+    sp<AudioTrack> lpTrack = getAudioTrack(env, thiz);
+    if (lpTrack == NULL) {
+        jniThrowException(env, "java/lang/IllegalStateException",
+                          "AudioTrack not initialized");
+        return;
+    }
+    AudioParameter param = AudioParameter();
+    param.addInt(String8("EOS"), 1);
+    lpTrack->setParameters(param.toString());
+}
+
+// ----------------------------------------------------------------------------
 // ----------------------------------------------------------------------------
 static const JNINativeMethod gMethods[] = {
     // name,              signature,     funcPtr
@@ -1385,6 +1413,8 @@
                                         (void *)android_media_AudioTrack_get_volume_shaper_state},
     {"native_setPresentation", "(II)I", (void *)android_media_AudioTrack_setPresentation},
     {"native_getPortId", "()I", (void *)android_media_AudioTrack_get_port_id},
+    {"native_set_delay_padding", "(II)V", (void *)android_media_AudioTrack_set_delay_padding},
+    {"native_set_eos",        "()V",    (void *)android_media_AudioTrack_set_eos},
 };
 
 
diff --git a/core/jni/android_media_MediaMetricsJNI.cpp b/core/jni/android_media_MediaMetricsJNI.cpp
deleted file mode 100644
index 38f7a7e2..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_util_FileObserver.cpp b/core/jni/android_util_FileObserver.cpp
index 6f975b2..d25192a 100644
--- a/core/jni/android_util_FileObserver.cpp
+++ b/core/jni/android_util_FileObserver.cpp
@@ -16,6 +16,8 @@
 */
 
 #include <nativehelper/JNIHelp.h>
+#include <nativehelper/ScopedPrimitiveArray.h>
+#include <nativehelper/ScopedUtfChars.h>
 #include "jni.h"
 #include "utils/Log.h"
 #include "utils/misc.h"
@@ -98,31 +100,45 @@
 #endif
 }
 
-static jint android_os_fileobserver_startWatching(JNIEnv* env, jobject object, jint fd, jstring pathString, jint mask)
+static void android_os_fileobserver_startWatching(JNIEnv* env, jobject object, jint fd,
+                                                       jobjectArray pathStrings, jint mask,
+                                                       jintArray wfdArray)
 {
-    int res = -1;
+    ScopedIntArrayRW wfds(env, wfdArray);
+    if (wfds.get() == nullptr) {
+        jniThrowException(env, "java/lang/IllegalStateException", "Failed to get ScopedIntArrayRW");
+    }
 
 #if defined(__linux__)
 
     if (fd >= 0)
     {
-        const char* path = env->GetStringUTFChars(pathString, NULL);
+        size_t count = wfds.size();
+        for (jsize i = 0; i < count; ++i) {
+            jstring pathString = (jstring) env->GetObjectArrayElement(pathStrings, i);
 
-        res = inotify_add_watch(fd, path, mask);
+            ScopedUtfChars path(env, pathString);
 
-        env->ReleaseStringUTFChars(pathString, path);
+            wfds[i] = inotify_add_watch(fd, path.c_str(), mask);
+        }
     }
 
 #endif
-
-    return res;
 }
 
-static void android_os_fileobserver_stopWatching(JNIEnv* env, jobject object, jint fd, jint wfd)
+static void android_os_fileobserver_stopWatching(JNIEnv* env, jobject object,
+                                                 jint fd, jintArray wfdArray)
 {
 #if defined(__linux__)
 
-    inotify_rm_watch((int)fd, (uint32_t)wfd);
+    ScopedIntArrayRO wfds(env, wfdArray);
+    if (wfds.get() == nullptr) {
+        jniThrowException(env, "java/lang/IllegalStateException", "Failed to get ScopedIntArrayRO");
+    }
+    size_t count = wfds.size();
+    for (size_t i = 0; i < count; ++i) {
+        inotify_rm_watch((int)fd, (uint32_t)wfds[i]);
+    }
 
 #endif
 }
@@ -131,8 +147,8 @@
      /* name, signature, funcPtr */
     { "init", "()I", (void*)android_os_fileobserver_init },
     { "observe", "(I)V", (void*)android_os_fileobserver_observe },
-    { "startWatching", "(ILjava/lang/String;I)I", (void*)android_os_fileobserver_startWatching },
-    { "stopWatching", "(II)V", (void*)android_os_fileobserver_stopWatching }
+    { "startWatching", "(I[Ljava/lang/String;I[I)V", (void*)android_os_fileobserver_startWatching },
+    { "stopWatching", "(I[I)V", (void*)android_os_fileobserver_stopWatching }
 
 };
 
diff --git a/core/jni/android_view_SurfaceControl.cpp b/core/jni/android_view_SurfaceControl.cpp
index 0453195..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;
@@ -360,7 +370,7 @@
         jlong nativeObject, jobject inputWindow) {
     auto transaction = reinterpret_cast<SurfaceComposerClient::Transaction*>(transactionObj);
 
-    sp<NativeInputWindowHandle> handle = android_server_InputWindowHandle_getHandle(
+    sp<NativeInputWindowHandle> handle = android_view_InputWindowHandle_getHandle(
             env, inputWindow);
     handle->updateInfo();
 
@@ -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/android_view_ThreadedRenderer.cpp b/core/jni/android_view_ThreadedRenderer.cpp
index 318ec9b..4052919 100644
--- a/core/jni/android_view_ThreadedRenderer.cpp
+++ b/core/jni/android_view_ThreadedRenderer.cpp
@@ -1038,8 +1038,9 @@
         // Continue I guess?
     }
 
+    SkColorType ct = uirenderer::PixelFormatToColorType(buffer->getPixelFormat());
     sk_sp<SkColorSpace> cs = uirenderer::DataSpaceToColorSpace(bufferItem.mDataSpace);
-    sk_sp<Bitmap> bitmap = Bitmap::createFrom(buffer, cs);
+    sk_sp<Bitmap> bitmap = Bitmap::createFrom(buffer, ct, cs);
     return bitmap::createBitmap(env, bitmap.release(),
             android::bitmap::kBitmapCreateFlag_Premultiplied);
 }
diff --git a/core/jni/com_android_internal_os_Zygote.cpp b/core/jni/com_android_internal_os_Zygote.cpp
index 2e7184b..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)));
       }
     }
   }
@@ -1472,7 +1472,7 @@
     fds_to_close.insert(fds_to_close.end(), blastula_pipes.begin(), blastula_pipes.end());
     fds_to_ignore.insert(fds_to_ignore.end(), blastula_pipes.begin(), blastula_pipes.end());
 
-//    fds_to_close.push_back(gBlastulaPoolSocketFD);
+    fds_to_close.push_back(gBlastulaPoolSocketFD);
 
     if (gBlastulaPoolEventFD != -1) {
       fds_to_close.push_back(gBlastulaPoolEventFD);
@@ -1498,7 +1498,7 @@
   std::vector<int> fds_to_close(MakeBlastulaPipeReadFDVector()),
                    fds_to_ignore(fds_to_close);
 
-//  fds_to_close.push_back(gBlastulaPoolSocketFD);
+  fds_to_close.push_back(gBlastulaPoolSocketFD);
 
   if (gBlastulaPoolEventFD != -1) {
     fds_to_close.push_back(gBlastulaPoolEventFD);
diff --git a/core/jni/fd_utils.cpp b/core/jni/fd_utils.cpp
index 53dde80..4b37f13 100644
--- a/core/jni/fd_utils.cpp
+++ b/core/jni/fd_utils.cpp
@@ -72,6 +72,7 @@
       return true;
   }
 
+  // Framework jars are allowed.
   static const char* kFrameworksPrefix = "/system/framework/";
   static const char* kJarSuffix = ".jar";
   if (android::base::StartsWith(path, kFrameworksPrefix)
@@ -79,6 +80,13 @@
     return true;
   }
 
+  // Jars from the runtime apex are allowed.
+  static const char* kRuntimeApexPrefix = "/apex/com.android.runtime/javalib/";
+  if (android::base::StartsWith(path, kRuntimeApexPrefix)
+      && android::base::EndsWith(path, kJarSuffix)) {
+    return true;
+  }
+
   // Whitelist files needed for Runtime Resource Overlay, like these:
   // /system/vendor/overlay/framework-res.apk
   // /system/vendor/overlay-subdir/pg/framework-res.apk
@@ -415,13 +423,13 @@
 }
 
 void FileDescriptorInfo::DetachSocket(fail_fn_t fail_fn) const {
-  const int dev_null_fd = open("/dev/null", O_RDWR);
+  const int dev_null_fd = open("/dev/null", O_RDWR | O_CLOEXEC);
   if (dev_null_fd < 0) {
     fail_fn(std::string("Failed to open /dev/null: ").append(strerror(errno)));
   }
 
-  if (dup2(dev_null_fd, fd) == -1) {
-    fail_fn(android::base::StringPrintf("Failed dup2 on socket descriptor %d: %s",
+  if (dup3(dev_null_fd, fd, O_CLOEXEC) == -1) {
+    fail_fn(android::base::StringPrintf("Failed dup3 on socket descriptor %d: %s",
                                         fd,
                                         strerror(errno)));
   }
diff --git a/core/proto/android/app/settings_enums.proto b/core/proto/android/app/settings_enums.proto
index 6cdba33..eb716ac 100644
--- a/core/proto/android/app/settings_enums.proto
+++ b/core/proto/android/app/settings_enums.proto
@@ -2183,4 +2183,10 @@
     // OPEN: Settings > Display > Adaptive sleep
     // OS: Q
     SETTINGS_ADAPTIVE_SLEEP = 1628;
+
+    // OPEN: Settings > System > Aware
+    SETTINGS_AWARE = 1632;
+
+    // OPEN: Settings > System > Aware > Disable > Dialog
+    DIALOG_AWARE_DISABLE = 1633;
 }
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 4158577..7e7942e 100644
--- a/core/proto/android/providers/settings/global.proto
+++ b/core/proto/android/providers/settings/global.proto
@@ -449,6 +449,10 @@
         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_whitelist = 12;
+        // ANGLE - List of Apps that can check ANGLE rules
+        optional SettingProto angle_whitelist = 13;
     }
     optional Gpu gpu = 59;
 
@@ -518,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;
 
@@ -535,6 +541,16 @@
         // Whether automatic battery saver mode is controlled via percentage,
         // {@link #DYNAMIC_POWER_SAVINGS_ENABLED} or disabled.
         optional SettingProto automatic_power_saver_mode = 4 [ (android.privacy).dest = DEST_AUTOMATIC];
+        // If 1, battery saver (low_power_mode) will be re-activated after the device is
+        // unplugged from a charger or rebooted.
+        optional SettingProto sticky_enabled = 5;
+        // Whether sticky battery saver should be deactivated once the battery level has reached the
+        // threshold specified by sticky_disable_level.
+        optional SettingProto sticky_auto_disable_enabled = 6;
+        // When a device is unplugged from a changer (or is rebooted), do not re-activate battery
+        // saver even if {@link #LOW_POWER_MODE_STICKY} is 1, if the battery level is equal to or
+        // above this threshold.
+        optional SettingProto sticky_auto_disable_level = 7;
     }
     optional LowPowerMode low_power_mode = 70;
 
@@ -731,8 +747,7 @@
     // Defines global runtime overrides to window policy.
     optional SettingProto policy_control = 92;
     optional SettingProto power_manager_constants = 93;
-    // If true, out-of-the-box execution for priv apps is enabled.
-    optional SettingProto priv_app_oob_enabled = 94 [ (android.privacy).dest = DEST_AUTOMATIC ];
+    reserved 94; // Used to be priv_app_oob_enabled
 
     message PrepaidSetup {
         option (android.msg_privacy).dest = DEST_EXPLICIT;
diff --git a/core/proto/android/providers/settings/secure.proto b/core/proto/android/providers/settings/secure.proto
index 4bfd4d2..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;
 
@@ -528,8 +531,11 @@
 
     optional SettingProto skip_gesture_enabled = 74 [ (android.privacy).dest = DEST_AUTOMATIC ];
     optional SettingProto silence_gesture_enabled = 75 [ (android.privacy).dest = DEST_AUTOMATIC ];
+    optional SettingProto theme_customization_overlay_packages = 76 [ (android.privacy).dest = DEST_AUTOMATIC ];
+
+    optional SettingProto aware_enabled = 77 [ (android.privacy).dest = DEST_AUTOMATIC ];
 
     // Please insert fields in alphabetical order and group them into messages
     // if possible (to avoid reaching the method limit).
-    // Next tag = 76;
+    // Next tag = 78;
 }
diff --git a/core/proto/android/server/connectivity/data_stall_event.proto b/core/proto/android/server/connectivity/data_stall_event.proto
index b70bb67..21717d8 100644
--- a/core/proto/android/server/connectivity/data_stall_event.proto
+++ b/core/proto/android/server/connectivity/data_stall_event.proto
@@ -41,7 +41,7 @@
   RADIO_TECHNOLOGY_UMTS = 3;
   RADIO_TECHNOLOGY_IS95A = 4;
   RADIO_TECHNOLOGY_IS95B = 5;
-  RADIO_TECHNOLOGY_1xRTT = 6;
+  RADIO_TECHNOLOGY_1XRTT = 6;
   RADIO_TECHNOLOGY_EVDO_0 = 7;
   RADIO_TECHNOLOGY_EVDO_A = 8;
   RADIO_TECHNOLOGY_HSDPA = 9;
diff --git a/core/proto/android/server/jobscheduler.proto b/core/proto/android/server/jobscheduler.proto
index e68f9db..188769d 100644
--- a/core/proto/android/server/jobscheduler.proto
+++ b/core/proto/android/server/jobscheduler.proto
@@ -31,6 +31,7 @@
 import "frameworks/base/core/proto/android/server/forceappstandbytracker.proto";
 import "frameworks/base/libs/incident/proto/android/privacy.proto";
 
+// Next tag: 21
 message JobSchedulerServiceDumpProto {
     option (.android.msg_privacy).dest = DEST_AUTOMATIC;
 
@@ -139,9 +140,13 @@
     // The current limit on the number of concurrent JobServiceContext entries
     // we want to keep actively running a job.
     optional int32 max_active_jobs = 13;
+
+    // Dump from JobConcurrencyManager.
+    optional JobConcurrencyManagerProto concurrency_manager = 20;
 }
 
 // A com.android.server.job.JobSchedulerService.Constants object.
+// Next tag: 29
 message ConstantsProto {
     option (.android.msg_privacy).dest = DEST_AUTOMATIC;
 
@@ -273,7 +278,39 @@
     }
     optional QuotaController quota_controller = 24;
 
-    // Next tag: 26
+    // Max number of jobs, when screen is ON.
+    optional MaxJobCountsPerMemoryTrimLevelProto max_job_counts_screen_on = 26;
+
+    // Max number of jobs, when screen is OFF.
+    optional MaxJobCountsPerMemoryTrimLevelProto max_job_counts_screen_off = 27;
+
+    // In this time after screen turns on, we increase job concurrency.
+    optional int32 screen_off_job_concurrency_increase_delay_ms = 28;
+}
+
+// Next tag: 4
+message MaxJobCountsProto {
+    option (.android.msg_privacy).dest = DEST_AUTOMATIC;
+
+    // Total number of jobs to run simultaneously.
+    optional int32 total_jobs = 1;
+
+    // Max number of BG (== owned by non-TOP apps) jobs to run simultaneously.
+    optional int32 max_bg = 2;
+
+    // We try to run at least this many BG (== owned by non-TOP apps) jobs, when there are any
+    // pending, rather than always running the TOTAL number of FG jobs.
+    optional int32 min_bg = 3;
+}
+
+// Next tag: 5
+message MaxJobCountsPerMemoryTrimLevelProto {
+    option (.android.msg_privacy).dest = DEST_AUTOMATIC;
+
+    optional MaxJobCountsProto normal = 1;
+    optional MaxJobCountsProto moderate = 2;
+    optional MaxJobCountsProto low = 3;
+    optional MaxJobCountsProto critical = 4;
 }
 
 message StateControllerProto {
@@ -807,3 +844,46 @@
 
     // Next tag: 28
 }
+
+// Dump from com.android.server.job.JobConcurrencyManager.
+// Next tag: 7
+message JobConcurrencyManagerProto {
+    option (.android.msg_privacy).dest = DEST_AUTOMATIC;
+
+    // Whether the device is interactive (== screen on) now or not.
+    optional bool current_interactive = 1;
+    // Similar to current_interactive, screen on or not, but it takes into account the off timeout.
+    optional bool effective_interactive = 2;
+    // How many milliseconds have passed since the last screen on. (i.e. 1000 == 1 sec ago)
+    optional int64 time_since_last_screen_on_ms = 3;
+    // How many milliseconds have passed since the last screen off. (i.e. 1000 == 1 sec ago)
+    optional int64 time_since_last_screen_off_ms = 4;
+    // Current max number of jobs.
+    optional JobCountTrackerProto job_count_tracker = 5;
+    // Current memory trim level.
+    optional int32 memory_trim_level = 6;
+}
+
+// Dump from com.android.server.job.JobConcurrencyManager.JobCountTracker.
+// Next tag: 8
+message JobCountTrackerProto {
+    option (.android.msg_privacy).dest = DEST_AUTOMATIC;
+
+    // Number of total jos that can run simultaneously.
+    optional int32 config_num_max_total_jobs = 1;
+    // Number of background jos that can run simultaneously.
+    optional int32 config_num_max_bg_jobs = 2;
+    // Out of total jobs, this many background jobs should be guaranteed to be executed, even if
+    // there are the config_num_max_total_jobs count of foreground jobs pending.
+    optional int32 config_num_min_bg_jobs = 3;
+
+    // Number of running foreground jobs.
+    optional int32 num_running_fg_jobs = 4;
+    // Number of running background jobs.
+    optional int32 num_running_bg_jobs = 5;
+
+    // Number of pending foreground jobs.
+    optional int32 num_pending_fg_jobs = 6;
+    // Number of pending background jobs.
+    optional int32 num_pending_bg_jobs = 7;
+}
diff --git a/core/proto/android/server/powermanagerservice.proto b/core/proto/android/server/powermanagerservice.proto
index cee556a..af0a942 100644
--- a/core/proto/android/server/powermanagerservice.proto
+++ b/core/proto/android/server/powermanagerservice.proto
@@ -350,4 +350,12 @@
     // The value of Global.LOW_POWER_MODE_TRIGGER_LEVEL. This is a cached value, so it could
     // be slightly different from what's in GlobalSettingsProto.LowPowerMode.
     optional int32 setting_battery_saver_trigger_threshold = 11;
+
+    // The value of Global.LOW_POWER_MODE_STICKY_AUTO_DISABLE_ENABLED. This is a cached value, so
+    // it could be slightly different from what's in GlobalSettingsProto.LowPowerMode.
+    optional bool setting_battery_saver_sticky_auto_disable_enabled = 12;
+
+    // The value of Global.LOW_POWER_MODE_STICKY_AUTO_DISABLE_LEVEL. This is a cached value, so it
+    // could be slightly different from what's in GlobalSettingsProto.LowPowerMode.
+    optional int32 setting_battery_saver_sticky_auto_disable_threshold = 13;
 }
diff --git a/core/proto/android/service/usb.proto b/core/proto/android/service/usb.proto
index f7dcee2..367c540 100644
--- a/core/proto/android/service/usb.proto
+++ b/core/proto/android/service/usb.proto
@@ -228,6 +228,15 @@
     repeated Mode supported_modes = 2;
 }
 
+/* Same as android.hardware.usb.V1_2.Constants.ContaminantPresenceStatus */
+enum ContaminantPresenceStatus {
+    CONTAMINANT_STATUS_UNKNOWN = 0;
+    CONTAMINANT_STATUS_NOT_SUPPORTED = 1;
+    CONTAMINANT_STATUS_DISABLED = 2;
+    CONTAMINANT_STATUS_NOT_DETECTED = 3;
+    CONTAMINANT_STATUS_DETECTED = 4;
+}
+
 message UsbPortStatusProto {
     option (android.msg_privacy).dest = DEST_AUTOMATIC;
 
@@ -250,6 +259,7 @@
     optional PowerRole power_role = 3;
     optional DataRole data_role = 4;
     repeated UsbPortStatusRoleCombinationProto role_combinations = 5;
+    optional ContaminantPresenceStatus contaminant_presence_status = 6;
 }
 
 message UsbPortStatusRoleCombinationProto {
diff --git a/core/proto/android/stats/devicepolicy/device_policy_enums.proto b/core/proto/android/stats/devicepolicy/device_policy_enums.proto
index 82460ec..a8e64c6 100644
--- a/core/proto/android/stats/devicepolicy/device_policy_enums.proto
+++ b/core/proto/android/stats/devicepolicy/device_policy_enums.proto
@@ -92,8 +92,7 @@
   SET_UNINSTALL_BLOCKED = 67;
   SET_PACKAGES_SUSPENDED = 68;
   ON_LOCK_TASK_MODE_ENTERING = 69;
-  ADD_CROSS_PROFILE_CALENDAR_PACKAGE = 70;
-  REMOVE_CROSS_PROFILE_CALENDAR_PACKAGE = 71;
+  SET_CROSS_PROFILE_CALENDAR_PACKAGES = 70;
   GET_USER_PASSWORD_COMPLEXITY_LEVEL = 72;
   INSTALL_SYSTEM_UPDATE = 73;
   INSTALL_SYSTEM_UPDATE_ERROR = 74;
diff --git a/core/proto/android/view/remote_animation_target.proto b/core/proto/android/view/remote_animation_target.proto
index fb4d5bd..808c514 100644
--- a/core/proto/android/view/remote_animation_target.proto
+++ b/core/proto/android/view/remote_animation_target.proto
@@ -42,4 +42,6 @@
     optional .android.graphics.PointProto position = 8;
     optional .android.graphics.RectProto source_container_bounds = 9;
     optional .android.app.WindowConfigurationProto window_configuration = 10;
+    optional .android.view.SurfaceControlProto start_leash = 11;
+    optional .android.graphics.RectProto start_bounds = 12;
 }
diff --git a/core/res/AndroidManifest.xml b/core/res/AndroidManifest.xml
index 96b8dc2..25baa92 100644
--- a/core/res/AndroidManifest.xml
+++ b/core/res/AndroidManifest.xml
@@ -43,7 +43,7 @@
     <protected-broadcast android:name="android.intent.action.PACKAGE_FULLY_REMOVED" />
     <protected-broadcast android:name="android.intent.action.PACKAGE_CHANGED" />
     <protected-broadcast android:name="android.intent.action.PACKAGE_ENABLE_ROLLBACK" />
-    <protected-broadcast android:name="android.intent.action.PACKAGE_ROLLBACK_EXECUTED" />
+    <protected-broadcast android:name="android.intent.action.ROLLBACK_COMMITTED" />
     <protected-broadcast android:name="android.intent.action.PACKAGE_RESTARTED" />
     <protected-broadcast android:name="android.intent.action.PACKAGE_DATA_CLEARED" />
     <protected-broadcast android:name="android.intent.action.PACKAGE_FIRST_LAUNCH" />
@@ -487,6 +487,7 @@
     <protected-broadcast android:name="android.security.action.TRUST_STORE_CHANGED" />
     <protected-broadcast android:name="android.security.action.KEYCHAIN_CHANGED" />
     <protected-broadcast android:name="android.security.action.KEY_ACCESS_CHANGED" />
+    <protected-broadcast android:name="android.telecom.action.NUISANCE_CALL_STATUS_CHANGED" />
     <protected-broadcast android:name="android.telecom.action.PHONE_ACCOUNT_REGISTERED" />
     <protected-broadcast android:name="android.telecom.action.PHONE_ACCOUNT_UNREGISTERED" />
     <protected-broadcast android:name="android.telecom.action.SHOW_MISSED_CALLS_NOTIFICATION" />
@@ -1653,6 +1654,11 @@
     <permission android:name="android.permission.WIFI_SET_DEVICE_MOBILITY_STATE"
         android:protectionLevel="signature|privileged" />
 
+    <!-- #SystemApi @hide Allows privileged system APK to update Wifi usability stats and score.
+         <p>Not for use by third-party applications. -->
+    <permission android:name="android.permission.WIFI_UPDATE_USABILITY_STATS_SCORE"
+        android:protectionLevel="signature|privileged" />
+
     <!-- ======================================= -->
     <!-- Permissions for short range, peripheral networks -->
     <!-- ======================================= -->
@@ -2113,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" />
@@ -3323,7 +3329,7 @@
     <permission android:name="com.android.permission.INSTALL_EXISTING_PACKAGES"
         android:protectionLevel="signature|privileged" />
 
-    <!-- @SystemApi Allows an application to clear user data.
+    <!-- @SystemApi @TestApi Allows an application to clear user data.
          <p>Not for use by third-party applications
          @hide
     -->
diff --git a/core/res/res/values/attrs_manifest.xml b/core/res/res/values/attrs_manifest.xml
index 8ef264a..de6468d 100644
--- a/core/res/res/values/attrs_manifest.xml
+++ b/core/res/res/values/attrs_manifest.xml
@@ -950,6 +950,17 @@
          <p>The default value of this attribute is <code>false</code>. -->
     <attr name="allowEmbedded" format="boolean" />
 
+    <!-- @hide @SystemApi Specifies whether this {@link android.app.Activity} should be shown on
+         top of the lock screen whenever the lockscreen is up and this activity has another
+         activity behind it with the {@link android.R.attr#showWhenLocked} attribute set. That
+         is, this activity is only visible on the lock screen if there is another activity with
+         the {@link android.R.attr#showWhenLocked} attribute visible at the same time on the
+         lock screen. A use case for this is permission dialogs, that should only be visible on
+         the lock screen if their requesting activity is also visible.
+
+         <p>The default value of this attribute is <code>false</code>. -->
+    <attr name="inheritShowWhenLocked" format="boolean" />
+
     <!-- Descriptive text for the associated data. -->
     <attr name="description" format="reference" />
 
@@ -2415,18 +2426,6 @@
         <attr name="showForAllUsers" />
 
         <attr name="showWhenLocked" />
-        <!-- @hide @SystemApi Specifies whether this {@link android.app.Activity} should be shown on
-             top of the lock screen whenever the lockscreen is up and this activity has another
-             activity behind it with the {@link android.R.attr#showWhenLocked} attribute set. That
-             is, this activity is only visible on the lock screen if there is another activity with
-             the {@link android.R.attr#showWhenLocked} attribute visible at the same time on the
-             lock screen. A use case for this is permission dialogs, that should only be visible on
-             the lock screen if their requesting activity is also visible.
-
-         The default value of this attribute is <code>false</code>. -->
-    <attr name="inheritShowWhenLocked" format="boolean" />
-
-
         <attr name="inheritShowWhenLocked" />
         <attr name="turnScreenOn" />
 
diff --git a/core/res/res/values/config.xml b/core/res/res/values/config.xml
index 1a21306..c05795d 100644
--- a/core/res/res/values/config.xml
+++ b/core/res/res/values/config.xml
@@ -126,6 +126,9 @@
          be sent during a change to the audio output device. -->
     <bool name="config_sendAudioBecomingNoisy">true</bool>
 
+    <!-- Whether Hearing Aid profile is supported -->
+    <bool name="config_hearing_aid_profile_supported">true</bool>
+
     <!-- Flag to disable all transition animations -->
     <bool name="config_disableTransitionAnimation">false</bool>
 
@@ -954,7 +957,7 @@
     <!-- Default mode to control how Night display is automatically activated.
          One of the following values (see ColorDisplayController.java):
              0 - AUTO_MODE_DISABLED
-             1 - AUTO_MODE_CUSTOM
+             1 - AUTO_MODE_CUSTOM_TIME
              2 - AUTO_MODE_TWILIGHT
     -->
     <integer name="config_defaultNightDisplayAutoMode">0</integer>
@@ -2208,6 +2211,10 @@
          has expired, then assume the device is receiving insufficient current to charge
          effectively and terminate the dream.  Use -1 to disable this safety feature.  -->
     <integer name="config_dreamsBatteryLevelDrainCutoff">5</integer>
+    <!-- Limit of how long the device can remain unlocked due to attention checking.  -->
+    <integer name="config_attentionMaximumExtension">240000</integer> <!-- 4 minutes -->
+    <!-- How long we should wait until we give up on receiving an attention API callback.  -->
+    <integer name="config_attentionApiTimeout">2000</integer> <!-- 2 seconds -->
 
     <!-- ComponentName of a dream to show whenever the system would otherwise have
          gone to sleep.  When the PowerManager is asked to go to sleep, it will instead
@@ -3726,9 +3733,6 @@
     <!-- Whether or not the "SMS app service" feature is enabled -->
     <bool name="config_useSmsAppService">true</bool>
 
-    <!-- Component name for default assistant on this device -->
-    <string name="config_defaultAssistantComponentName">#+UNSET</string>
-
     <!-- Class name for the InputEvent compatibility processor override.
          Empty string means use the default compatibility processor
          (android.view.InputEventCompatProcessor). -->
@@ -3759,4 +3763,7 @@
 
     <!-- Whether cbrs is supported on the device or not -->
     <bool translatable="false" name="config_cbrs_supported">false</bool>
+
+    <!-- Whether or not aware is enabled by default -->
+    <bool name="config_awareSettingAvailable">false</bool>
 </resources>
diff --git a/core/res/res/values/public.xml b/core/res/res/values/public.xml
index 777886a..ec1bac1 100644
--- a/core/res/res/values/public.xml
+++ b/core/res/res/values/public.xml
@@ -2944,6 +2944,8 @@
     </public-group>
 
     <public-group type="style" first-id="0x010302e2">
+        <!-- @hide @SystemApi -->
+        <public name="Theme.DeviceDefault.DocumentsUI" />
     </public-group>
 
     <public-group type="id" first-id="0x01020046">
@@ -2986,7 +2988,7 @@
     </public-group>
 
     <public-group type="array" first-id="0x01070006">
-      <!-- @hide @SystemApi -->
+      <!-- @hide @TestApi @SystemApi -->
       <public name="config_defaultRoleHolders" />
     </public-group>
 
diff --git a/core/res/res/values/strings.xml b/core/res/res/values/strings.xml
index f2b4b9c..a761baf 100644
--- a/core/res/res/values/strings.xml
+++ b/core/res/res/values/strings.xml
@@ -3387,6 +3387,9 @@
     <!-- A notification is shown when the user connects to a Wi-Fi network and the system detects that that network has no Internet access. This is the notification's message. -->
     <string name="wifi_no_internet_detailed">Tap for options</string>
 
+    <!-- A notification is shown after the user logs in to a captive portal network, to indicate that the network should now have internet connectivity. This is the message of notification. [CHAR LIMIT=50] -->
+    <string name="captive_portal_logged_in_detailed">Connected</string>
+
     <!-- A notification is shown when the user's softap config has been changed due to underlying
          hardware restrictions. This is the notifications's title.
          [CHAR_LIMIT=NONE] -->
@@ -3573,6 +3576,15 @@
     <string name="adb_active_notification_message">Tap to turn off USB debugging</string>
     <string name="adb_active_notification_message" product="tv">Select to disable USB debugging.</string>
 
+    <!-- Title of notification shown when contaminant is detected on the USB port. [CHAR LIMIT=NONE] -->
+    <string name="usb_contaminant_detected_title">Liquid or debris in USB port</string>
+    <!-- Message of notification shown when contaminant is detected on the USB port. [CHAR LIMIT=NONE] -->
+    <string name="usb_contaminant_detected_message">USB port is automatically disabled. Tap to learn more.</string>
+    <!-- Title of notification shown when contaminant is no longer detected on the USB port. [CHAR LIMIT=NONE] -->
+    <string name="usb_contaminant_not_detected_title">Safe to use USB port</string>
+    <!-- Message of notification shown when contaminant is no longer detected on the USB port. [CHAR LIMIT=NONE] -->
+    <string name="usb_contaminant_not_detected_message">Phone no longer detects liquid or debris.</string>
+
     <!-- Title of notification shown to indicate that bug report is being collected. -->
     <string name="taking_remote_bugreport_notification_title">Taking bug report\u2026</string>
     <!-- Title of notification shown to ask for user consent for sharing a bugreport that was requested remotely by the IT administrator. -->
diff --git a/core/res/res/values/symbols.xml b/core/res/res/values/symbols.xml
index 83982c7..f79e22d 100644
--- a/core/res/res/values/symbols.xml
+++ b/core/res/res/values/symbols.xml
@@ -437,6 +437,7 @@
   <java-symbol type="integer" name="config_bluetooth_operating_voltage_mv" />
   <java-symbol type="bool" name="config_bluetooth_pan_enable_autoconnect" />
   <java-symbol type="bool" name="config_bluetooth_reload_supported_profiles_when_enabled" />
+  <java-symbol type="bool" name="config_hearing_aid_profile_supported" />
   <java-symbol type="integer" name="config_cursorWindowSize" />
   <java-symbol type="integer" name="config_drawLockTimeoutMillis" />
   <java-symbol type="integer" name="config_doublePressOnPowerBehavior" />
@@ -694,6 +695,7 @@
   <java-symbol type="string" name="capability_title_canControlMagnification" />
   <java-symbol type="string" name="capability_desc_canPerformGestures" />
   <java-symbol type="string" name="capability_title_canPerformGestures" />
+  <java-symbol type="string" name="captive_portal_logged_in_detailed" />
   <java-symbol type="string" name="cfTemplateForwarded" />
   <java-symbol type="string" name="cfTemplateForwardedTime" />
   <java-symbol type="string" name="cfTemplateNotForwarded" />
@@ -2104,6 +2106,10 @@
   <java-symbol type="string" name="usb_supplying_notification_title" />
   <java-symbol type="string" name="usb_unsupported_audio_accessory_title" />
   <java-symbol type="string" name="usb_unsupported_audio_accessory_message" />
+  <java-symbol type="string" name="usb_contaminant_detected_title" />
+  <java-symbol type="string" name="usb_contaminant_detected_message" />
+  <java-symbol type="string" name="usb_contaminant_not_detected_title" />
+  <java-symbol type="string" name="usb_contaminant_not_detected_message" />
   <java-symbol type="string" name="config_UsbDeviceConnectionHandling_component" />
   <java-symbol type="string" name="vpn_text" />
   <java-symbol type="string" name="vpn_text_long" />
@@ -3522,8 +3528,6 @@
 
   <java-symbol type="bool" name="config_useSmsAppService" />
 
-  <java-symbol type="string" name="config_defaultAssistantComponentName" />
-
   <java-symbol type="id" name="transition_overlay_view_tag" />
 
   <java-symbol type="dimen" name="rounded_corner_radius" />
@@ -3548,4 +3552,10 @@
 
   <!-- For CBRS -->
   <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/res/res/values/themes_device_defaults.xml b/core/res/res/values/themes_device_defaults.xml
index 75a727b..1603508 100644
--- a/core/res/res/values/themes_device_defaults.xml
+++ b/core/res/res/values/themes_device_defaults.xml
@@ -1711,4 +1711,6 @@
         <item name="notificationHeaderTextAppearance">@style/TextAppearance.DeviceDefault.Notification.Info</item>
     </style>
 
+    <!-- @hide DeviceDefault theme for the DocumentsUI app.  -->
+    <style name="Theme.DeviceDefault.DocumentsUI" parent="Theme.DeviceDefault.DayNight" />
 </resources>
diff --git a/core/tests/BroadcastRadioTests/src/android/hardware/radio/tests/functional/RadioTunerTest.java b/core/tests/BroadcastRadioTests/src/android/hardware/radio/tests/functional/RadioTunerTest.java
index d2bd1e1..11eb158 100644
--- a/core/tests/BroadcastRadioTests/src/android/hardware/radio/tests/functional/RadioTunerTest.java
+++ b/core/tests/BroadcastRadioTests/src/android/hardware/radio/tests/functional/RadioTunerTest.java
@@ -18,13 +18,13 @@
 import static org.junit.Assert.*;
 import static org.junit.Assume.*;
 import static org.mockito.Matchers.any;
+import static org.mockito.Matchers.anyBoolean;
+import static org.mockito.Matchers.anyInt;
 import static org.mockito.Mockito.after;
-import static org.mockito.Mockito.atLeast;
 import static org.mockito.Mockito.atMost;
 import static org.mockito.Mockito.never;
 import static org.mockito.Mockito.timeout;
 import static org.mockito.Mockito.verify;
-import static org.mockito.Mockito.verifyNoMoreInteractions;
 import static org.testng.Assert.assertThrows;
 
 import android.Manifest;
@@ -119,10 +119,9 @@
     }
 
     private void resetCallback() {
-        verify(mCallback, atLeast(0)).onMetadataChanged(any());
-        verify(mCallback, atLeast(0)).onProgramInfoChanged(any());
-        verify(mCallback, atLeast(0)).onProgramListChanged();
-        verifyNoMoreInteractions(mCallback);
+        verify(mCallback, never()).onError(anyInt());
+        verify(mCallback, never()).onTuneFailed(anyInt(), any());
+        verify(mCallback, never()).onControlChanged(anyBoolean());
         Mockito.reset(mCallback);
     }
 
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/app/servertransaction/TransactionParcelTests.java b/core/tests/coretests/src/android/app/servertransaction/TransactionParcelTests.java
index 8604b0c..bdf3aa2 100644
--- a/core/tests/coretests/src/android/app/servertransaction/TransactionParcelTests.java
+++ b/core/tests/coretests/src/android/app/servertransaction/TransactionParcelTests.java
@@ -38,7 +38,6 @@
 import android.content.pm.ServiceInfo;
 import android.content.res.CompatibilityInfo;
 import android.content.res.Configuration;
-import android.net.Uri;
 import android.os.Binder;
 import android.os.Bundle;
 import android.os.Debug;
@@ -501,7 +500,7 @@
         }
 
         @Override
-        public void setHttpProxy(String s, String s1, String s2, Uri uri) throws RemoteException {
+        public void updateHttpProxy() throws RemoteException {
         }
 
         @Override
diff --git a/core/tests/coretests/src/android/content/pm/PackageBackwardCompatibilityTest.java b/core/tests/coretests/src/android/content/pm/PackageBackwardCompatibilityTest.java
index 3d7aab0..ad9814b 100644
--- a/core/tests/coretests/src/android/content/pm/PackageBackwardCompatibilityTest.java
+++ b/core/tests/coretests/src/android/content/pm/PackageBackwardCompatibilityTest.java
@@ -48,21 +48,11 @@
     }
 
     /**
-     * Detect when the org.apache.http.legacy is not on the bootclasspath.
-     *
-     * <p>This test will be ignored when org.apache.http.legacy is not on the bootclasspath and
-     * succeed otherwise. This allows a developer to ensure that the tests are being
-     */
-    @Test
-    public void detectWhenOAHLisOnBCP() {
-        Assume.assumeTrue(PackageBackwardCompatibility.bootClassPathContainsOAHL());
-    }
-
-    /**
      * Detect when the android.test.base is not on the bootclasspath.
      *
      * <p>This test will be ignored when org.apache.http.legacy is not on the bootclasspath and
-     * succeed otherwise. This allows a developer to ensure that the tests are being
+     * succeed otherwise. This allows a developer to ensure that the tests are being run in the
+     * correct environment.
      */
     @Test
     public void detectWhenATBisOnBCP() {
@@ -85,9 +75,7 @@
         if (!PackageBackwardCompatibility.bootClassPathContainsATB()) {
             expected.add(ANDROID_TEST_BASE);
         }
-        if (!PackageBackwardCompatibility.bootClassPathContainsOAHL()) {
-            expected.add(ORG_APACHE_HTTP_LEGACY);
-        }
+        expected.add(ORG_APACHE_HTTP_LEGACY);
 
         PackageBuilder after = builder()
                 .targetSdkVersion(Build.VERSION_CODES.O)
@@ -98,30 +86,6 @@
 
     /**
      * Ensures that the {@link PackageBackwardCompatibility} uses
-     * {@link RemoveUnnecessaryOrgApacheHttpLegacyLibraryTest}
-     * when necessary.
-     *
-     * <p>More comprehensive tests for that class can be found in
-     * {@link RemoveUnnecessaryOrgApacheHttpLegacyLibraryTest}.
-     */
-    @Test
-    public void org_apache_http_legacy_in_usesLibraries() {
-        Assume.assumeTrue("Test requires that "
-                        + ORG_APACHE_HTTP_LEGACY + " is on the bootclasspath",
-                PackageBackwardCompatibility.bootClassPathContainsOAHL());
-
-        PackageBuilder before = builder()
-                .requiredLibraries(ORG_APACHE_HTTP_LEGACY);
-
-        // org.apache.http.legacy should be removed from the libraries because it is provided
-        // on the bootclasspath and providing both increases start up cost unnecessarily.
-        PackageBuilder after = builder();
-
-        checkBackwardsCompatibility(before, after);
-    }
-
-    /**
-     * Ensures that the {@link PackageBackwardCompatibility} uses
      * {@link RemoveUnnecessaryAndroidTestBaseLibrary}
      * when necessary.
      *
diff --git a/core/tests/coretests/src/android/content/pm/PackageParserTest.java b/core/tests/coretests/src/android/content/pm/PackageParserTest.java
index c5454a6..300394d 100644
--- a/core/tests/coretests/src/android/content/pm/PackageParserTest.java
+++ b/core/tests/coretests/src/android/content/pm/PackageParserTest.java
@@ -527,12 +527,16 @@
                 R.raw.com_android_tzdata);
         PackageInfo pi = PackageParser.generatePackageInfoFromApex(apexFile, false);
         assertEquals("com.google.android.tzdata", pi.packageName);
+        assertEquals("com.google.android.tzdata", pi.applicationInfo.packageName);
         assertEquals(1, pi.getLongVersionCode());
+        assertEquals(1, pi.applicationInfo.longVersionCode);
         assertNull(pi.signingInfo);
 
         pi = PackageParser.generatePackageInfoFromApex(apexFile, true);
         assertEquals("com.google.android.tzdata", pi.packageName);
+        assertEquals("com.google.android.tzdata", pi.applicationInfo.packageName);
         assertEquals(1, pi.getLongVersionCode());
+        assertEquals(1, pi.applicationInfo.longVersionCode);
         assertNotNull(pi.signingInfo);
         assertTrue(pi.signingInfo.getApkContentsSigners().length > 0);
     }
diff --git a/core/tests/coretests/src/android/provider/SettingsBackupTest.java b/core/tests/coretests/src/android/provider/SettingsBackupTest.java
index 1431741..bd7f852 100644
--- a/core/tests/coretests/src/android/provider/SettingsBackupTest.java
+++ b/core/tests/coretests/src/android/provider/SettingsBackupTest.java
@@ -131,6 +131,7 @@
                     Settings.Global.AUTOFILL_SMART_SUGGESTION_EMULATION_FLAGS,
                     Settings.Global.AUTOMATIC_POWER_SAVER_MODE,
                     Settings.Global.BACKGROUND_ACTIVITY_STARTS_ENABLED,
+                    Settings.Global.BATTERY_CHARGING_STATE_UPDATE_DELAY,
                     Settings.Global.BATTERY_DISCHARGE_DURATION_THRESHOLD,
                     Settings.Global.BATTERY_DISCHARGE_THRESHOLD,
                     Settings.Global.BATTERY_SAVER_DEVICE_SPECIFIC_CONSTANTS,
@@ -298,6 +299,7 @@
                     Settings.Global.LOCATION_BACKGROUND_THROTTLE_INTERVAL_MS,
                     Settings.Global.LOCATION_BACKGROUND_THROTTLE_PROXIMITY_ALERT_INTERVAL_MS,
                     Settings.Global.LOCATION_BACKGROUND_THROTTLE_PACKAGE_WHITELIST,
+                    Settings.Global.LOCATION_IGNORE_SETTINGS_PACKAGE_WHITELIST,
                     Settings.Global.LOCATION_DISABLE_STATUS_CALLBACKS,
                     Settings.Global.LOCATION_LAST_LOCATION_MAX_AGE_MILLIS,
                     Settings.Global.LOCATION_GLOBAL_KILL_SWITCH,
@@ -388,12 +390,9 @@
                     Settings.Global.POLICY_CONTROL,
                     Settings.Global.POWER_MANAGER_CONSTANTS,
                     Settings.Global.PREFERRED_NETWORK_MODE,
-                    Settings.Global.PRIV_APP_OOB_ENABLED,
-                    Settings.Global.PRIV_APP_OOB_LIST,
                     Settings.Global.PRIVATE_DNS_DEFAULT_MODE,
-                    Settings.Global.PRIVILEGED_DEVICE_IDENTIFIER_CHECK_ENABLED,
                     Settings.Global.PRIVILEGED_DEVICE_IDENTIFIER_NON_PRIV_CHECK_RELAXED,
-                    Settings.Global.PRIVILEGED_DEVICE_IDENTIFIER_TARGET_Q_BEHAVIOR_ENABLED,
+                    Settings.Global.PRIVILEGED_DEVICE_IDENTIFIER_PRIV_CHECK_RELAXED,
                     Settings.Global.PRIVILEGED_DEVICE_IDENTIFIER_3P_CHECK_RELAXED,
                     Settings.Global.PROVISIONING_APN_ALARM_DELAY_IN_MS,
                     Settings.Global.RADIO_BLUETOOTH,
@@ -483,10 +482,12 @@
                     Settings.Global.GLOBAL_SETTINGS_ANGLE_GL_DRIVER_ALL_ANGLE,
                     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_WHITELIST,
                     Settings.Global.GPU_DEBUG_LAYER_APP,
                     Settings.Global.ENABLE_GNSS_RAW_MEAS_FULL_TRACKING,
                     Settings.Global.INSTALL_CARRIER_APP_NOTIFICATION_PERSISTENT,
@@ -564,7 +565,9 @@
                     Settings.Global.APPOP_HISTORY_BASE_INTERVAL_MILLIS,
                     Settings.Global.ENABLE_RADIO_BUG_DETECTION,
                     Settings.Global.RADIO_BUG_WAKELOCK_TIMEOUT_COUNT_THRESHOLD,
-                    Settings.Global.RADIO_BUG_SYSTEM_ERROR_COUNT_THRESHOLD);
+                    Settings.Global.RADIO_BUG_SYSTEM_ERROR_COUNT_THRESHOLD,
+                    Settings.Global.ENABLED_SUBSCRIPTION_FOR_SLOT,
+                    Settings.Global.MODEM_STACK_ENABLED_FOR_SLOT);
     private static final Set<String> BACKUP_BLACKLISTED_SECURE_SETTINGS =
              newHashSet(
                  Settings.Secure.ACCESSIBILITY_SOFT_KEYBOARD_MODE,
diff --git a/core/tests/coretests/src/android/util/LongArrayQueueTest.java b/core/tests/coretests/src/android/util/LongArrayQueueTest.java
new file mode 100644
index 0000000..77e8d60
--- /dev/null
+++ b/core/tests/coretests/src/android/util/LongArrayQueueTest.java
@@ -0,0 +1,238 @@
+/*
+ * 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.util;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertNotEquals;
+import static org.junit.Assert.fail;
+
+import androidx.test.filters.SmallTest;
+import androidx.test.runner.AndroidJUnit4;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+import java.util.NoSuchElementException;
+
+/**
+ * Internal tests for {@link LongArrayQueue}.
+ */
+@SmallTest
+@RunWith(AndroidJUnit4.class)
+public class LongArrayQueueTest {
+
+    private LongArrayQueue mQueueUnderTest;
+
+    @Before
+    public void setUp() {
+        mQueueUnderTest = new LongArrayQueue();
+    }
+
+    @Test
+    public void removeFirstOnEmptyQueue() {
+        try {
+            mQueueUnderTest.removeFirst();
+            fail("removeFirst() succeeded on an empty queue!");
+        } catch (NoSuchElementException e) {
+        }
+        mQueueUnderTest.addLast(5);
+        mQueueUnderTest.removeFirst();
+        try {
+            mQueueUnderTest.removeFirst();
+            fail("removeFirst() succeeded on an empty queue!");
+        } catch (NoSuchElementException e) {
+        }
+    }
+
+    @Test
+    public void addLastRemoveFirstFifo() {
+        mQueueUnderTest.addLast(1);
+        assertEquals(1, mQueueUnderTest.removeFirst());
+        int n = 890;
+        int removes = 0;
+        for (int i = 0; i < n; i++) {
+            mQueueUnderTest.addLast(i);
+            if ((i % 3) == 0) {
+                assertEquals(removes++, mQueueUnderTest.removeFirst());
+            }
+        }
+        while (removes < n) {
+            assertEquals(removes++, mQueueUnderTest.removeFirst());
+        }
+    }
+
+    @Test
+    public void peekFirstOnEmptyQueue() {
+        try {
+            mQueueUnderTest.peekFirst();
+            fail("peekFirst() succeeded on an empty queue!");
+        } catch (NoSuchElementException e) {
+        }
+        mQueueUnderTest.addLast(5);
+        mQueueUnderTest.removeFirst();
+        try {
+            mQueueUnderTest.peekFirst();
+            fail("peekFirst() succeeded on an empty queue!");
+        } catch (NoSuchElementException e) {
+        }
+    }
+
+    @Test
+    public void peekFirstChanges() {
+        mQueueUnderTest.addLast(1);
+        assertEquals(1, mQueueUnderTest.peekFirst());
+        mQueueUnderTest.addLast(2);
+        mQueueUnderTest.addLast(3);
+        mQueueUnderTest.addLast(4);
+        // addLast() has no effect on peekFirst().
+        assertEquals(1, mQueueUnderTest.peekFirst());
+        mQueueUnderTest.removeFirst();
+        mQueueUnderTest.removeFirst();
+        assertEquals(3, mQueueUnderTest.peekFirst());
+    }
+
+    @Test
+    public void peekLastOnEmptyQueue() {
+        try {
+            mQueueUnderTest.peekLast();
+            fail("peekLast() succeeded on an empty queue!");
+        } catch (NoSuchElementException e) {
+        }
+        mQueueUnderTest.addLast(5);
+        mQueueUnderTest.removeFirst();
+        try {
+            mQueueUnderTest.peekLast();
+            fail("peekLast() succeeded on an empty queue!");
+        } catch (NoSuchElementException e) {
+        }
+    }
+
+    @Test
+    public void peekLastChanges() {
+        mQueueUnderTest.addLast(1);
+        assertEquals(1, mQueueUnderTest.peekLast());
+        mQueueUnderTest.addLast(2);
+        mQueueUnderTest.addLast(3);
+        mQueueUnderTest.addLast(4);
+        assertEquals(4, mQueueUnderTest.peekLast());
+        mQueueUnderTest.removeFirst();
+        mQueueUnderTest.removeFirst();
+        // removeFirst() has no effect on peekLast().
+        assertEquals(4, mQueueUnderTest.peekLast());
+    }
+
+    @Test
+    public void peekFirstVsPeekLast() {
+        mQueueUnderTest.addLast(2);
+        assertEquals(mQueueUnderTest.peekFirst(), mQueueUnderTest.peekLast());
+        mQueueUnderTest.addLast(3);
+        assertNotEquals(mQueueUnderTest.peekFirst(), mQueueUnderTest.peekLast());
+        mQueueUnderTest.removeFirst();
+        assertEquals(mQueueUnderTest.peekFirst(), mQueueUnderTest.peekLast());
+    }
+
+    @Test
+    public void peekFirstVsRemoveFirst() {
+        int n = 25;
+        for (int i = 0; i < n; i++) {
+            mQueueUnderTest.addLast(i + 1);
+        }
+        for (int i = 0; i < n; i++) {
+            long peekVal = mQueueUnderTest.peekFirst();
+            assertEquals(peekVal, mQueueUnderTest.removeFirst());
+        }
+    }
+
+    @Test
+    public void sizeOfEmptyQueue() {
+        assertEquals(0, mQueueUnderTest.size());
+        mQueueUnderTest = new LongArrayQueue(1000);
+        // capacity doesn't affect size.
+        assertEquals(0, mQueueUnderTest.size());
+    }
+
+    @Test
+    public void sizeAfterOperations() {
+        final int added = 1200;
+        for (int i = 0; i < added; i++) {
+            mQueueUnderTest.addLast(i);
+        }
+        // each add increments the size by 1.
+        assertEquals(added, mQueueUnderTest.size());
+        mQueueUnderTest.peekLast();
+        mQueueUnderTest.peekFirst();
+        // peeks don't change the size.
+        assertEquals(added, mQueueUnderTest.size());
+        final int removed = 345;
+        for (int i = 0; i < removed; i++) {
+            mQueueUnderTest.removeFirst();
+        }
+        // each remove decrements the size by 1.
+        assertEquals(added - removed, mQueueUnderTest.size());
+        mQueueUnderTest.clear();
+        // clear reduces the size to 0.
+        assertEquals(0, mQueueUnderTest.size());
+    }
+
+    @Test
+    public void getInvalidPositions() {
+        try {
+            mQueueUnderTest.get(0);
+            fail("get(0) succeeded on an empty queue!");
+        } catch (IndexOutOfBoundsException e) {
+        }
+        int n = 520;
+        for (int i = 0; i < 2 * n; i++) {
+            mQueueUnderTest.addLast(i + 1);
+        }
+        for (int i = 0; i < n; i++) {
+            mQueueUnderTest.removeFirst();
+        }
+        try {
+            mQueueUnderTest.get(-3);
+            fail("get(-3) succeeded");
+        } catch (IndexOutOfBoundsException e) {
+        }
+        assertEquals(n, mQueueUnderTest.size());
+        try {
+            mQueueUnderTest.get(n);
+            fail("get(" + n + ") succeeded on a queue with " + n + " elements");
+        } catch (IndexOutOfBoundsException e) {
+        }
+        try {
+            mQueueUnderTest.get(n + 3);
+            fail("get(" + (n + 3) + ") succeeded on a queue with " + n + " elements");
+        } catch (IndexOutOfBoundsException e) {
+        }
+    }
+
+    @Test
+    public void getValidPositions() {
+        int added = 423;
+        int removed = 212;
+        for (int i = 0; i < added; i++) {
+            mQueueUnderTest.addLast(i);
+        }
+        for (int i = 0; i < removed; i++) {
+            mQueueUnderTest.removeFirst();
+        }
+        for (int i = 0; i < (added - removed); i++) {
+            assertEquals(removed + i, mQueueUnderTest.get(i));
+        }
+    }
+}
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/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/contentcapture/ContentCaptureManagerTest.java b/core/tests/coretests/src/android/view/contentcapture/ContentCaptureManagerTest.java
new file mode 100644
index 0000000..312e0e0
--- /dev/null
+++ b/core/tests/coretests/src/android/view/contentcapture/ContentCaptureManagerTest.java
@@ -0,0 +1,51 @@
+/*
+ * 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.contentcapture;
+
+import static org.testng.Assert.assertThrows;
+
+import android.content.Context;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.mockito.Mock;
+import org.mockito.junit.MockitoJUnitRunner;
+
+/**
+ * Unit test for {@link ContentCaptureManager}.
+ *
+ * <p>To run it:
+ * {@code atest FrameworksCoreTests:android.view.contentcapture.ContentCaptureManagerTest}
+ */
+@RunWith(MockitoJUnitRunner.class)
+public class ContentCaptureManagerTest {
+
+    @Mock
+    private Context mMockContext;
+
+    private ContentCaptureManager mManager;
+
+    @Before
+    public void before() {
+        mManager = new ContentCaptureManager(mMockContext, null);
+    }
+
+    @Test
+    public void testRemoveUserData_invalid() {
+        assertThrows(NullPointerException.class, () -> mManager.removeUserData(null));
+    }
+}
diff --git a/core/tests/coretests/src/android/view/contentcapture/ContentCaptureSessionTest.java b/core/tests/coretests/src/android/view/contentcapture/ContentCaptureSessionTest.java
index c2eb18b..bfa6e06 100644
--- a/core/tests/coretests/src/android/view/contentcapture/ContentCaptureSessionTest.java
+++ b/core/tests/coretests/src/android/view/contentcapture/ContentCaptureSessionTest.java
@@ -31,7 +31,7 @@
 import org.mockito.junit.MockitoJUnitRunner;
 
 /**
- * Unit test for {@link ContentCaptureSessionTest}.
+ * Unit tests for {@link ContentCaptureSession}.
  *
  * <p>To run it:
  * {@code atest FrameworksCoreTests:android.view.contentcapture.ContentCaptureSessionTest}
diff --git a/core/tests/coretests/src/android/view/contentcapture/UserDataRemovalRequestTest.java b/core/tests/coretests/src/android/view/contentcapture/UserDataRemovalRequestTest.java
new file mode 100644
index 0000000..bebb2a8
--- /dev/null
+++ b/core/tests/coretests/src/android/view/contentcapture/UserDataRemovalRequestTest.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 android.view.contentcapture;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import static org.testng.Assert.assertThrows;
+
+import android.net.Uri;
+
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.mockito.Mock;
+import org.mockito.junit.MockitoJUnitRunner;
+
+/**
+ * Unit test for {@link UserDataRemovalRequest}.
+ *
+ * <p>To run it:
+ * {@code atest FrameworksCoreTests:android.view.contentcapture.UserDataRemovalRequestTest}
+ */
+@RunWith(MockitoJUnitRunner.class)
+public class UserDataRemovalRequestTest {
+
+    @Mock
+    private final Uri mUri = Uri.parse("content://com.example/");
+
+    private UserDataRemovalRequest.Builder mBuilder = new UserDataRemovalRequest.Builder();
+
+    @Test
+    public void testBuilder_addUri_invalid() {
+        assertThrows(NullPointerException.class, () -> mBuilder.addUri(null, false));
+    }
+
+    @Test
+    public void testBuilder_addUri_valid() {
+        assertThat(mBuilder.addUri(mUri, false)).isNotNull();
+        assertThat(mBuilder.addUri(Uri.parse("content://com.example2"), true)).isNotNull();
+    }
+
+    @Test
+    public void testBuilder_addUriAfterForEverything() {
+        assertThat(mBuilder.forEverything()).isNotNull();
+        assertThrows(IllegalStateException.class, () -> mBuilder.addUri(mUri, false));
+    }
+
+    @Test
+    public void testBuilder_forEverythingAfterAddingUri() {
+        assertThat(mBuilder.addUri(mUri, false)).isNotNull();
+        assertThrows(IllegalStateException.class, () -> mBuilder.forEverything());
+    }
+
+    @Test
+    public void testBuild_invalid() {
+        assertThrows(IllegalStateException.class, () -> mBuilder.build());
+    }
+
+    @Test
+    public void testBuild_valid() {
+        assertThat(new UserDataRemovalRequest.Builder().forEverything().build())
+                .isNotNull();
+        assertThat(new UserDataRemovalRequest.Builder().addUri(mUri, false).build())
+                .isNotNull();
+    }
+
+    @Test
+    public void testNoMoreInteractionsAfterBuild() {
+        assertThat(mBuilder.forEverything().build()).isNotNull();
+
+        assertThrows(IllegalStateException.class, () -> mBuilder.addUri(mUri, false));
+        assertThrows(IllegalStateException.class, () -> mBuilder.forEverything());
+        assertThrows(IllegalStateException.class, () -> mBuilder.build());
+
+    }
+}
diff --git a/core/tests/coretests/src/android/view/contentcapture/ViewNodeTest.java b/core/tests/coretests/src/android/view/contentcapture/ViewNodeTest.java
index eadde62..b84a098 100644
--- a/core/tests/coretests/src/android/view/contentcapture/ViewNodeTest.java
+++ b/core/tests/coretests/src/android/view/contentcapture/ViewNodeTest.java
@@ -42,7 +42,7 @@
 import java.util.Locale;
 
 /**
- * Unit test for {@link ViewNode}.
+ * Unit tests for {@link ViewNode}.
  *
  * <p>To run it: {@code atest FrameworksCoreTests:android.view.contentcapture.ViewNodeTest}
  */
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 7009fb2..5e58f82 100644
--- a/core/tests/coretests/src/android/view/textclassifier/TextClassifierTest.java
+++ b/core/tests/coretests/src/android/view/textclassifier/TextClassifierTest.java
@@ -262,6 +262,9 @@
         assertEquals(
                 context.getString(com.android.internal.R.string.translate),
                 classification.getActions().get(0).getTitle());
+        Intent intent = (Intent) classification.getExtras()
+                .getParcelableArrayList(TextClassifierImpl.ACTIONS_INTENTS).get(0);
+        assertEquals(Intent.ACTION_TRANSLATE, intent.getAction());
 
         LocaleList.setDefault(originalLocales);
     }
@@ -375,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 =
@@ -404,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/core/tests/coretests/src/android/view/textclassifier/logging/TextClassifierEventTronLoggerTest.java b/core/tests/coretests/src/android/view/textclassifier/logging/TextClassifierEventTronLoggerTest.java
index b1b7416..73af5674 100644
--- a/core/tests/coretests/src/android/view/textclassifier/logging/TextClassifierEventTronLoggerTest.java
+++ b/core/tests/coretests/src/android/view/textclassifier/logging/TextClassifierEventTronLoggerTest.java
@@ -18,9 +18,10 @@
 
 import static com.android.internal.logging.nano.MetricsProto.MetricsEvent.ACTION_TEXT_SELECTION_SMART_SHARE;
 import static com.android.internal.logging.nano.MetricsProto.MetricsEvent.CONVERSATION_ACTIONS;
-import static com.android.internal.logging.nano.MetricsProto.MetricsEvent.FIELD_SELECTION_ENTITY_TYPE;
-import static com.android.internal.logging.nano.MetricsProto.MetricsEvent.FIELD_SELECTION_WIDGET_TYPE;
 import static com.android.internal.logging.nano.MetricsProto.MetricsEvent.FIELD_TEXT_CLASSIFIER_EVENT_TIME;
+import static com.android.internal.logging.nano.MetricsProto.MetricsEvent.FIELD_TEXT_CLASSIFIER_FIRST_ENTITY_TYPE;
+import static com.android.internal.logging.nano.MetricsProto.MetricsEvent.FIELD_TEXT_CLASSIFIER_SCORE;
+import static com.android.internal.logging.nano.MetricsProto.MetricsEvent.FIELD_TEXT_CLASSIFIER_WIDGET_TYPE;
 
 import static com.google.common.truth.Truth.assertThat;
 
@@ -71,7 +72,8 @@
                 new TextClassifierEvent.Builder(
                         TextClassifierEvent.CATEGORY_CONVERSATION_ACTIONS,
                         TextClassifierEvent.TYPE_SMART_ACTION)
-                        .setEntityType(ConversationAction.TYPE_CALL_PHONE)
+                        .setEntityTypes(ConversationAction.TYPE_CALL_PHONE)
+                        .setScore(0.5f)
                         .setEventTime(EVENT_TIME)
                         .setEventContext(textClassificationContext)
                         .build();
@@ -83,15 +85,18 @@
         LogMaker logMaker = captor.getValue();
         assertThat(logMaker.getCategory()).isEqualTo(
                 CONVERSATION_ACTIONS);
-        assertThat(logMaker.getType()).isEqualTo(
+        assertThat(logMaker.getSubtype()).isEqualTo(
                 ACTION_TEXT_SELECTION_SMART_SHARE);
-        assertThat(logMaker.getTaggedData(FIELD_SELECTION_ENTITY_TYPE))
+        assertThat(logMaker.getTaggedData(FIELD_TEXT_CLASSIFIER_FIRST_ENTITY_TYPE))
                 .isEqualTo(ConversationAction.TYPE_CALL_PHONE);
+        assertThat((float) logMaker.getTaggedData(FIELD_TEXT_CLASSIFIER_SCORE))
+                .isWithin(0.00001f).of(0.5f);
         assertThat(logMaker.getTaggedData(FIELD_TEXT_CLASSIFIER_EVENT_TIME))
                 .isEqualTo(EVENT_TIME);
         assertThat(logMaker.getPackageName()).isEqualTo(PACKAGE_NAME);
-        assertThat(logMaker.getTaggedData(FIELD_SELECTION_WIDGET_TYPE))
+        assertThat(logMaker.getTaggedData(FIELD_TEXT_CLASSIFIER_WIDGET_TYPE))
                 .isEqualTo(WIDGET_TYPE);
+
     }
 
     @Test
diff --git a/graphics/java/android/graphics/Bitmap.java b/graphics/java/android/graphics/Bitmap.java
index bfbdbc5..8636949 100644
--- a/graphics/java/android/graphics/Bitmap.java
+++ b/graphics/java/android/graphics/Bitmap.java
@@ -1711,20 +1711,22 @@
      */
     @Nullable
     public final ColorSpace getColorSpace() {
-        // A reconfigure can change the configuration and rgba16f is
-        // always linear scRGB at this time
-        if (getConfig() == Config.RGBA_F16) {
-            // Reset the color space for potential future reconfigurations
-            mColorSpace = null;
-            return ColorSpace.get(ColorSpace.Named.LINEAR_EXTENDED_SRGB);
-        }
-
+        checkRecycled("getColorSpace called on a recycled bitmap");
         // Cache the color space retrieval since it can be fairly expensive
         if (mColorSpace == null) {
-            if (nativeIsSRGB(mNativePtr)) {
+            if (nativeIsConfigF16(mNativePtr)) {
+                // an F16 bitmaps is intended to always be linear extended, but due to
+                // inconsistencies in Bitmap.create() functions it is possible to have
+                // rendered into a bitmap in non-linear sRGB.
+                if (nativeIsSRGB(mNativePtr)) {
+                    mColorSpace = ColorSpace.get(ColorSpace.Named.EXTENDED_SRGB);
+                } else {
+                    mColorSpace = ColorSpace.get(ColorSpace.Named.LINEAR_EXTENDED_SRGB);
+                }
+            } else if (nativeIsSRGB(mNativePtr)) {
                 mColorSpace = ColorSpace.get(ColorSpace.Named.SRGB);
-            } else if (getConfig() == Config.HARDWARE && nativeIsSRGBLinear(mNativePtr)) {
-                mColorSpace = ColorSpace.get(ColorSpace.Named.LINEAR_EXTENDED_SRGB);
+            } else if (nativeIsSRGBLinear(mNativePtr)) {
+                mColorSpace = ColorSpace.get(ColorSpace.Named.LINEAR_SRGB);
             } else {
                 float[] xyz = new float[9];
                 float[] params = new float[7];
@@ -2127,6 +2129,7 @@
     private static native void nativeErase(long nativeBitmap, long colorSpacePtr, long color);
     private static native int nativeRowBytes(long nativeBitmap);
     private static native int nativeConfig(long nativeBitmap);
+    private static native boolean nativeIsConfigF16(long nativeBitmap);
 
     private static native int nativeGetPixel(long nativeBitmap, int x, int y);
     private static native void nativeGetPixels(long nativeBitmap, int[] pixels,
diff --git a/graphics/java/android/graphics/ColorSpace.java b/graphics/java/android/graphics/ColorSpace.java
index 4755d45..c9e4694 100644
--- a/graphics/java/android/graphics/ColorSpace.java
+++ b/graphics/java/android/graphics/ColorSpace.java
@@ -1821,6 +1821,8 @@
      * @param cct The correlated color temperature, in Kelvin
      * @return Corresponding XYZ values
      * @throws IllegalArgumentException If cct is invalid
+     *
+     * @hide
      */
     @NonNull
     @Size(3)
@@ -1851,6 +1853,8 @@
      * @param srcWhitePoint The white point to adapt from
      * @param dstWhitePoint The white point to adapt to
      * @return A 3x3 matrix as a non-null array of 9 floats
+     *
+     * @hide
      */
     @NonNull
     @Size(9)
diff --git a/libs/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/DeviceInfo.cpp b/libs/hwui/DeviceInfo.cpp
index 56b1885..4c67513 100644
--- a/libs/hwui/DeviceInfo.cpp
+++ b/libs/hwui/DeviceInfo.cpp
@@ -62,10 +62,8 @@
     return displayInfo;
 }
 
-static void queryWideColorGamutPreference(SkColorSpace::Gamut* colorGamut,
-                                          sk_sp<SkColorSpace>* colorSpace, SkColorType* colorType) {
+static void queryWideColorGamutPreference(sk_sp<SkColorSpace>* colorSpace, SkColorType* colorType) {
     if (Properties::isolatedProcess) {
-        *colorGamut = SkColorSpace::Gamut::kSRGB_Gamut;
         *colorSpace = SkColorSpace::MakeSRGB();
         *colorType = SkColorType::kN32_SkColorType;
         return;
@@ -78,16 +76,13 @@
     LOG_ALWAYS_FATAL_IF(status, "Failed to get composition preference, error %d", status);
     switch (wcgDataspace) {
         case ui::Dataspace::DISPLAY_P3:
-            *colorGamut = SkColorSpace::Gamut::kDCIP3_D65_Gamut;
             *colorSpace = SkColorSpace::MakeRGB(SkNamedTransferFn::kSRGB, SkNamedGamut::kDCIP3);
             break;
         case ui::Dataspace::V0_SCRGB:
-            *colorGamut = SkColorSpace::Gamut::kSRGB_Gamut;
             *colorSpace = SkColorSpace::MakeSRGB();
             break;
         case ui::Dataspace::V0_SRGB:
             // when sRGB is returned, it means wide color gamut is not supported.
-            *colorGamut = SkColorSpace::Gamut::kSRGB_Gamut;
             *colorSpace = SkColorSpace::MakeSRGB();
             break;
         default:
@@ -112,7 +107,7 @@
         mMaxTextureSize = -1;
 #endif
     mDisplayInfo = QueryDisplayInfo();
-    queryWideColorGamutPreference(&mWideColorGamut, &mWideColorSpace, &mWideColorType);
+    queryWideColorGamutPreference(&mWideColorSpace, &mWideColorType);
 }
 
 int DeviceInfo::maxTextureSize() const {
diff --git a/libs/hwui/DeviceInfo.h b/libs/hwui/DeviceInfo.h
index 9bcc8e8..2bab5d3 100644
--- a/libs/hwui/DeviceInfo.h
+++ b/libs/hwui/DeviceInfo.h
@@ -38,7 +38,6 @@
     // context or if you are using the HWUI_NULL_GPU
     int maxTextureSize() const;
     const DisplayInfo& displayInfo() const { return mDisplayInfo; }
-    SkColorSpace::Gamut getWideColorGamut() const { return mWideColorGamut; }
     sk_sp<SkColorSpace> getWideColorSpace() const { return mWideColorSpace; }
     SkColorType getWideColorType() const { return mWideColorType; }
 
@@ -50,7 +49,6 @@
 
     int mMaxTextureSize;
     DisplayInfo mDisplayInfo;
-    SkColorSpace::Gamut mWideColorGamut;
     sk_sp<SkColorSpace> mWideColorSpace;
     SkColorType mWideColorType;
 };
diff --git a/libs/hwui/HardwareBitmapUploader.cpp b/libs/hwui/HardwareBitmapUploader.cpp
index 635d0ec..39bfcdd 100644
--- a/libs/hwui/HardwareBitmapUploader.cpp
+++ b/libs/hwui/HardwareBitmapUploader.cpp
@@ -164,15 +164,11 @@
         const SkImageInfo& info = source.info();
         bitmap.allocPixels(
                 SkImageInfo::MakeN32(info.width(), info.height(), info.alphaType(), nullptr));
-        bitmap.eraseColor(0);
-        if (info.colorType() == kRGBA_F16_SkColorType) {
-            // Drawing RGBA_F16 onto ARGB_8888 is not supported
-            source.readPixels(bitmap.info().makeColorSpace(SkColorSpace::MakeSRGB()),
-                              bitmap.getPixels(), bitmap.rowBytes(), 0, 0);
-        } else {
-            SkCanvas canvas(bitmap);
-            canvas.drawBitmap(source, 0.0f, 0.0f, nullptr);
-        }
+
+        SkCanvas canvas(bitmap);
+        canvas.drawColor(0);
+        canvas.drawBitmap(source, 0.0f, 0.0f, nullptr);
+
         return bitmap;
     }
 }
@@ -253,8 +249,8 @@
         eglDestroySyncKHR(display, fence);
     }
 
-    return Bitmap::createFrom(buffer.get(), bitmap.refColorSpace(), bitmap.alphaType(),
-                              Bitmap::computePalette(bitmap));
+    return Bitmap::createFrom(buffer.get(), bitmap.colorType(), bitmap.refColorSpace(),
+                              bitmap.alphaType(), Bitmap::computePalette(bitmap));
 }
 
 void HardwareBitmapUploader::terminate() {
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/Bitmap.cpp b/libs/hwui/hwui/Bitmap.cpp
index 6e0258c..3bbee18 100644
--- a/libs/hwui/hwui/Bitmap.cpp
+++ b/libs/hwui/hwui/Bitmap.cpp
@@ -133,12 +133,11 @@
 }
 
 
-sk_sp<Bitmap> Bitmap::createFrom(sp<GraphicBuffer> graphicBuffer, sk_sp<SkColorSpace> colorSpace,
-                                 SkAlphaType alphaType, BitmapPalette palette) {
-    // As we will be effectively texture-sampling the buffer (using either EGL or Vulkan), we can
-    // view the format as RGBA8888.
+sk_sp<Bitmap> Bitmap::createFrom(sp<GraphicBuffer> graphicBuffer, SkColorType colorType,
+                                 sk_sp<SkColorSpace> colorSpace, SkAlphaType alphaType,
+                                 BitmapPalette palette) {
     SkImageInfo info = SkImageInfo::Make(graphicBuffer->getWidth(), graphicBuffer->getHeight(),
-                                         kRGBA_8888_SkColorType, alphaType, colorSpace);
+                                         colorType, alphaType, colorSpace);
     return sk_sp<Bitmap>(new Bitmap(graphicBuffer.get(), info, palette));
 }
 
diff --git a/libs/hwui/hwui/Bitmap.h b/libs/hwui/hwui/Bitmap.h
index 2138040..01e4516 100644
--- a/libs/hwui/hwui/Bitmap.h
+++ b/libs/hwui/hwui/Bitmap.h
@@ -72,6 +72,7 @@
      * memory that is provided as an input param.
      */
     static sk_sp<Bitmap> createFrom(sp<GraphicBuffer> graphicBuffer,
+                                    SkColorType colorType,
                                     sk_sp<SkColorSpace> colorSpace,
                                     SkAlphaType alphaType = kPremul_SkAlphaType,
                                     BitmapPalette palette = BitmapPalette::Unknown);
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..ad46b239 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/ShaderCache.cpp b/libs/hwui/pipeline/skia/ShaderCache.cpp
index 1661905..8508274 100644
--- a/libs/hwui/pipeline/skia/ShaderCache.cpp
+++ b/libs/hwui/pipeline/skia/ShaderCache.cpp
@@ -31,8 +31,8 @@
 
 // Cache size limits.
 static const size_t maxKeySize = 1024;
-static const size_t maxValueSize = 64 * 1024;
-static const size_t maxTotalSize = 512 * 1024;
+static const size_t maxValueSize = 512 * 1024;
+static const size_t maxTotalSize = 1024 * 1024;
 
 ShaderCache::ShaderCache() {
     // There is an "incomplete FileBlobCache type" compilation error, if ctor is moved to header.
diff --git a/libs/hwui/pipeline/skia/SkiaOpenGLPipeline.cpp b/libs/hwui/pipeline/skia/SkiaOpenGLPipeline.cpp
index cfbb995..570e895 100644
--- a/libs/hwui/pipeline/skia/SkiaOpenGLPipeline.cpp
+++ b/libs/hwui/pipeline/skia/SkiaOpenGLPipeline.cpp
@@ -167,7 +167,7 @@
 
     if (surface) {
         mRenderThread.requireGlContext();
-        auto newSurface = mEglManager.createSurface(surface, colorMode, mSurfaceColorGamut);
+        auto newSurface = mEglManager.createSurface(surface, colorMode, mSurfaceColorSpace);
         if (!newSurface) {
             return false;
         }
diff --git a/libs/hwui/pipeline/skia/SkiaPipeline.cpp b/libs/hwui/pipeline/skia/SkiaPipeline.cpp
index 47c9094..a00a36f 100644
--- a/libs/hwui/pipeline/skia/SkiaPipeline.cpp
+++ b/libs/hwui/pipeline/skia/SkiaPipeline.cpp
@@ -476,11 +476,9 @@
 void SkiaPipeline::setSurfaceColorProperties(ColorMode colorMode) {
     if (colorMode == ColorMode::SRGB) {
         mSurfaceColorType = SkColorType::kN32_SkColorType;
-        mSurfaceColorGamut = SkColorSpace::Gamut::kSRGB_Gamut;
         mSurfaceColorSpace = SkColorSpace::MakeSRGB();
     } else if (colorMode == ColorMode::WideColorGamut) {
         mSurfaceColorType = DeviceInfo::get()->getWideColorType();
-        mSurfaceColorGamut = DeviceInfo::get()->getWideColorGamut();
         mSurfaceColorSpace = DeviceInfo::get()->getWideColorSpace();
     } else {
         LOG_ALWAYS_FATAL("Unreachable: unsupported color mode.");
diff --git a/libs/hwui/pipeline/skia/SkiaPipeline.h b/libs/hwui/pipeline/skia/SkiaPipeline.h
index e9957df..7381e04 100644
--- a/libs/hwui/pipeline/skia/SkiaPipeline.h
+++ b/libs/hwui/pipeline/skia/SkiaPipeline.h
@@ -116,7 +116,6 @@
 
     renderthread::RenderThread& mRenderThread;
     SkColorType mSurfaceColorType;
-    SkColorSpace::Gamut mSurfaceColorGamut;
     sk_sp<SkColorSpace> mSurfaceColorSpace;
 
 private:
diff --git a/libs/hwui/pipeline/skia/SkiaVulkanPipeline.cpp b/libs/hwui/pipeline/skia/SkiaVulkanPipeline.cpp
index 53495a7..d0fe022 100644
--- a/libs/hwui/pipeline/skia/SkiaVulkanPipeline.cpp
+++ b/libs/hwui/pipeline/skia/SkiaVulkanPipeline.cpp
@@ -129,7 +129,7 @@
     setSurfaceColorProperties(colorMode);
     if (surface) {
         mVkSurface = mVkManager.createSurface(surface, colorMode, mSurfaceColorSpace,
-                                              mSurfaceColorGamut, mSurfaceColorType);
+                                              mSurfaceColorType);
     }
 
     return mVkSurface != nullptr;
@@ -149,20 +149,8 @@
 
 sk_sp<Bitmap> SkiaVulkanPipeline::allocateHardwareBitmap(renderthread::RenderThread& renderThread,
                                                          SkBitmap& skBitmap) {
-    // TODO: implement this function for Vulkan pipeline
-    // code below is a hack to avoid crashing because of missing HW Bitmap support
-    sp<GraphicBuffer> buffer = new GraphicBuffer(
-            skBitmap.info().width(), skBitmap.info().height(), PIXEL_FORMAT_RGBA_8888,
-            GraphicBuffer::USAGE_HW_TEXTURE | GraphicBuffer::USAGE_SW_WRITE_NEVER |
-                    GraphicBuffer::USAGE_SW_READ_NEVER,
-            std::string("SkiaVulkanPipeline::allocateHardwareBitmap pid [") +
-                    std::to_string(getpid()) + "]");
-    status_t error = buffer->initCheck();
-    if (error < 0) {
-        ALOGW("SkiaVulkanPipeline::allocateHardwareBitmap() failed in GraphicBuffer.create()");
-        return nullptr;
-    }
-    return Bitmap::createFrom(buffer, skBitmap.refColorSpace());
+    LOG_ALWAYS_FATAL("Unimplemented");
+    return nullptr;
 }
 
 } /* namespace skiapipeline */
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/renderthread/EglManager.cpp b/libs/hwui/renderthread/EglManager.cpp
index 8cd97ed..2cc3f36 100644
--- a/libs/hwui/renderthread/EglManager.cpp
+++ b/libs/hwui/renderthread/EglManager.cpp
@@ -132,11 +132,13 @@
     createPBufferSurface();
     makeCurrent(mPBufferSurface, nullptr, /* force */ true);
 
-    SkColorSpace::Gamut wideColorGamut = DeviceInfo::get()->getWideColorGamut();
+    skcms_Matrix3x3 wideColorGamut;
+    LOG_ALWAYS_FATAL_IF(!DeviceInfo::get()->getWideColorSpace()->toXYZD50(&wideColorGamut),
+                        "Could not get gamut matrix from wideColorSpace");
     bool hasWideColorSpaceExtension = false;
-    if (wideColorGamut == SkColorSpace::Gamut::kDCIP3_D65_Gamut) {
+    if (memcmp(&wideColorGamut, &SkNamedGamut::kDCIP3, sizeof(wideColorGamut)) == 0) {
         hasWideColorSpaceExtension = EglExtensions.displayP3;
-    } else if (wideColorGamut == SkColorSpace::Gamut::kSRGB_Gamut) {
+    } else if (memcmp(&wideColorGamut, &SkNamedGamut::kSRGB, sizeof(wideColorGamut)) == 0) {
         hasWideColorSpaceExtension = EglExtensions.scRGB;
     } else {
         LOG_ALWAYS_FATAL("Unsupported wide color space.");
@@ -297,7 +299,7 @@
 
 Result<EGLSurface, EGLint> EglManager::createSurface(EGLNativeWindowType window,
                                                      ColorMode colorMode,
-                                                     SkColorSpace::Gamut colorGamut) {
+                                                     sk_sp<SkColorSpace> colorSpace) {
     LOG_ALWAYS_FATAL_IF(!hasEglContext(), "Not initialized");
 
     bool wideColorGamut = colorMode == ColorMode::WideColorGamut && mHasWideColorGamutSupport &&
@@ -330,15 +332,15 @@
     if (EglExtensions.glColorSpace) {
         attribs[0] = EGL_GL_COLORSPACE_KHR;
         if (wideColorGamut) {
-            switch (colorGamut) {
-                case SkColorSpace::Gamut::kDCIP3_D65_Gamut:
-                    attribs[1] = EGL_GL_COLORSPACE_DISPLAY_P3_PASSTHROUGH_EXT;
-                    break;
-                case SkColorSpace::Gamut::kSRGB_Gamut:
-                    attribs[1] = EGL_GL_COLORSPACE_SCRGB_EXT;
-                    break;
-                default:
-                    LOG_ALWAYS_FATAL("Unreachable: unsupported wide color space.");
+            skcms_Matrix3x3 colorGamut;
+            LOG_ALWAYS_FATAL_IF(!colorSpace->toXYZD50(&colorGamut),
+                                "Could not get gamut matrix from color space");
+            if (memcmp(&colorGamut, &SkNamedGamut::kDCIP3, sizeof(colorGamut)) == 0) {
+                attribs[1] = EGL_GL_COLORSPACE_DISPLAY_P3_PASSTHROUGH_EXT;
+            } else if (memcmp(&colorGamut, &SkNamedGamut::kSRGB, sizeof(colorGamut)) == 0) {
+                attribs[1] = EGL_GL_COLORSPACE_SCRGB_EXT;
+            } else {
+                LOG_ALWAYS_FATAL("Unreachable: unsupported wide color space.");
             }
         } else {
             attribs[1] = EGL_GL_COLORSPACE_LINEAR_KHR;
diff --git a/libs/hwui/renderthread/EglManager.h b/libs/hwui/renderthread/EglManager.h
index 4dd9096..27d41d2 100644
--- a/libs/hwui/renderthread/EglManager.h
+++ b/libs/hwui/renderthread/EglManager.h
@@ -49,7 +49,7 @@
     bool hasEglContext();
 
     Result<EGLSurface, EGLint> createSurface(EGLNativeWindowType window, ColorMode colorMode,
-                                             SkColorSpace::Gamut colorGamut);
+                                             sk_sp<SkColorSpace> colorSpace);
     void destroySurface(EGLSurface surface);
 
     void destroy();
diff --git a/libs/hwui/renderthread/RenderProxy.cpp b/libs/hwui/renderthread/RenderProxy.cpp
index ab59af7..720c603 100644
--- a/libs/hwui/renderthread/RenderProxy.cpp
+++ b/libs/hwui/renderthread/RenderProxy.cpp
@@ -241,8 +241,10 @@
 }
 
 void RenderProxy::dumpGraphicsMemory(int fd) {
-    auto& thread = RenderThread::getInstance();
-    thread.queue().runSync([&]() { thread.dumpGraphicsMemory(fd); });
+    if (RenderThread::hasInstance()) {
+        auto& thread = RenderThread::getInstance();
+        thread.queue().runSync([&]() { thread.dumpGraphicsMemory(fd); });
+    }
 }
 
 void RenderProxy::setProcessStatsBuffer(int fd) {
diff --git a/libs/hwui/renderthread/VulkanManager.cpp b/libs/hwui/renderthread/VulkanManager.cpp
index 5c6cb9a..1e75202 100644
--- a/libs/hwui/renderthread/VulkanManager.cpp
+++ b/libs/hwui/renderthread/VulkanManager.cpp
@@ -516,10 +516,9 @@
     if (windowWidth != surface->mWindowWidth || windowHeight != surface->mWindowHeight) {
         ColorMode colorMode = surface->mColorMode;
         sk_sp<SkColorSpace> colorSpace = surface->mColorSpace;
-        SkColorSpace::Gamut colorGamut = surface->mColorGamut;
         SkColorType colorType = surface->mColorType;
         destroySurface(surface);
-        *surfaceOut = createSurface(window, colorMode, colorSpace, colorGamut, colorType);
+        *surfaceOut = createSurface(window, colorMode, colorSpace, colorType);
         surface = *surfaceOut;
     }
 
@@ -841,9 +840,12 @@
     }
 
     if (surface->mColorMode == ColorMode::WideColorGamut) {
-        if (surface->mColorGamut == SkColorSpace::Gamut::kSRGB_Gamut) {
+        skcms_Matrix3x3 surfaceGamut;
+        LOG_ALWAYS_FATAL_IF(!surface->mColorSpace->toXYZD50(&surfaceGamut),
+                            "Could not get gamut matrix from color space");
+        if (memcmp(&surfaceGamut, &SkNamedGamut::kSRGB, sizeof(surfaceGamut)) == 0) {
             colorSpace = VK_COLOR_SPACE_EXTENDED_SRGB_NONLINEAR_EXT;
-        } else if (surface->mColorGamut == SkColorSpace::Gamut::kDCIP3_D65_Gamut) {
+        } else if (memcmp(&surfaceGamut, &SkNamedGamut::kDCIP3, sizeof(surfaceGamut)) == 0) {
             colorSpace = VK_COLOR_SPACE_DISPLAY_P3_NONLINEAR_EXT;
         } else {
             LOG_ALWAYS_FATAL("Unreachable: unsupported wide color space.");
@@ -922,7 +924,6 @@
 
 VulkanSurface* VulkanManager::createSurface(ANativeWindow* window, ColorMode colorMode,
                                             sk_sp<SkColorSpace> surfaceColorSpace,
-                                            SkColorSpace::Gamut surfaceColorGamut,
                                             SkColorType surfaceColorType) {
     initialize();
 
@@ -931,7 +932,7 @@
     }
 
     VulkanSurface* surface = new VulkanSurface(colorMode, window, surfaceColorSpace,
-                                               surfaceColorGamut, surfaceColorType);
+                                               surfaceColorType);
 
     VkAndroidSurfaceCreateInfoKHR surfaceCreateInfo;
     memset(&surfaceCreateInfo, 0, sizeof(VkAndroidSurfaceCreateInfoKHR));
diff --git a/libs/hwui/renderthread/VulkanManager.h b/libs/hwui/renderthread/VulkanManager.h
index b06eb82..abe78ef 100644
--- a/libs/hwui/renderthread/VulkanManager.h
+++ b/libs/hwui/renderthread/VulkanManager.h
@@ -39,9 +39,9 @@
 class VulkanSurface {
 public:
     VulkanSurface(ColorMode colorMode, ANativeWindow* window, sk_sp<SkColorSpace> colorSpace,
-                  SkColorSpace::Gamut colorGamut, SkColorType colorType)
+                  SkColorType colorType)
             : mColorMode(colorMode), mNativeWindow(window), mColorSpace(colorSpace),
-              mColorGamut(colorGamut), mColorType(colorType) {}
+              mColorType(colorType) {}
 
     sk_sp<SkSurface> getBackBufferSurface() { return mBackbuffer; }
 
@@ -90,7 +90,6 @@
     int mWindowWidth = 0;
     int mWindowHeight = 0;
     sk_sp<SkColorSpace> mColorSpace;
-    SkColorSpace::Gamut mColorGamut;
     SkColorType mColorType;
     VkSurfaceTransformFlagBitsKHR mTransform = VK_SURFACE_TRANSFORM_IDENTITY_BIT_KHR;
     SkMatrix mPreTransform;
@@ -113,7 +112,6 @@
     // VulkanSurface object which is returned.
     VulkanSurface* createSurface(ANativeWindow* window, ColorMode colorMode,
                                  sk_sp<SkColorSpace> surfaceColorSpace,
-                                 SkColorSpace::Gamut surfaceColorGamut,
                                  SkColorType surfaceColorType);
 
     // Destroy the VulkanSurface and all associated vulkan objects.
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/HwBitmapInCompositeShader.cpp b/libs/hwui/tests/common/scenes/HwBitmapInCompositeShader.cpp
index ec81f62..2af955f 100644
--- a/libs/hwui/tests/common/scenes/HwBitmapInCompositeShader.cpp
+++ b/libs/hwui/tests/common/scenes/HwBitmapInCompositeShader.cpp
@@ -50,7 +50,8 @@
             pixels[4000 + 4 * i + 3] = 255;
         }
         buffer->unlock();
-        sk_sp<Bitmap> hardwareBitmap(Bitmap::createFrom(buffer, SkColorSpace::MakeSRGB()));
+        sk_sp<Bitmap> hardwareBitmap(Bitmap::createFrom(buffer, kRGBA_8888_SkColorType,
+                                                        SkColorSpace::MakeSRGB()));
         sk_sp<SkShader> hardwareShader(createBitmapShader(*hardwareBitmap));
 
         SkPoint center;
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..d3090367 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/libs/hwui/utils/Color.cpp b/libs/hwui/utils/Color.cpp
index 4415a59..d14116f 100644
--- a/libs/hwui/utils/Color.cpp
+++ b/libs/hwui/utils/Color.cpp
@@ -45,6 +45,20 @@
     }
 }
 
+SkColorType PixelFormatToColorType(android::PixelFormat format) {
+    switch (format) {
+        case PIXEL_FORMAT_RGBX_8888:    return kRGB_888x_SkColorType;
+        case PIXEL_FORMAT_RGBA_8888:    return kRGBA_8888_SkColorType;
+        case PIXEL_FORMAT_RGBA_FP16:    return kRGBA_F16_SkColorType;
+        case PIXEL_FORMAT_RGB_565:      return kRGB_565_SkColorType;
+        case PIXEL_FORMAT_RGBA_1010102: return kRGBA_1010102_SkColorType;
+        case PIXEL_FORMAT_RGBA_4444:    return kARGB_4444_SkColorType;
+        default:
+            ALOGW("Unsupported PixelFormat: %d, return kUnknown_SkColorType by default", format);
+            return kUnknown_SkColorType;
+    }
+}
+
 sk_sp<SkColorSpace> DataSpaceToColorSpace(android_dataspace dataspace) {
 
     skcms_Matrix3x3 gamut;
diff --git a/libs/hwui/utils/Color.h b/libs/hwui/utils/Color.h
index 3880252..b67d10d 100644
--- a/libs/hwui/utils/Color.h
+++ b/libs/hwui/utils/Color.h
@@ -112,6 +112,7 @@
 }
 
 android::PixelFormat ColorTypeToPixelFormat(SkColorType colorType);
+ANDROID_API SkColorType PixelFormatToColorType(android::PixelFormat format);
 
 ANDROID_API sk_sp<SkColorSpace> DataSpaceToColorSpace(android_dataspace dataspace);
 
diff --git a/media/Android.bp b/media/Android.bp
index 0eb86ac..0675a36 100644
--- a/media/Android.bp
+++ b/media/Android.bp
@@ -1,25 +1,59 @@
 java_library {
-    name: "media1",
+    name: "updatable-media1",
 
     srcs: [
         ":media1-srcs",
+        ":framework-media-annotation-srcs",
     ],
 
+    aidl: {
+        export_include_dirs: [
+            "apex/java",
+        ],
+
+        // TODO: find out a way to include only the necessary aidl files instead of dirs.
+        include_dirs: [
+            "frameworks/base/core/java",
+            "frameworks/base/media/java",
+        ],
+    },
+
+    installable: true,
+
+    // Make sure that the implementaion only relies on SDK or system APIs.
     sdk_version: "system_current",
 }
 
-filegroup {
-    name: "media1-srcs",
+java_library {
+    name: "updatable-mediasession2",
+
     srcs: [
-        "java/android/media/session/MediaSessionProviderService.java",
+        ":mediasession2-srcs",
+        ":framework-media-annotation-srcs",
     ],
+
+    aidl: {
+        export_include_dirs: [
+            "apex/java",
+        ],
+
+        // TODO: find out a way to include only the necessary aidl files instead of dirs.
+        include_dirs: [
+            "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: [
-        ":media2-srcs",
+        ":mediaplayer2-srcs",
         ":framework-media-annotation-srcs",
     ],
 
@@ -34,7 +68,87 @@
 }
 
 filegroup {
-    name: "media2-srcs",
+    name: "media-srcs-without-aidls",
+    srcs : [
+        ":media1-srcs-without-aidls",
+        ":mediasession2-srcs-without-aidls",
+        ":mediaplayer2-srcs",
+    ],
+}
+
+filegroup {
+    name: "media1-srcs",
+    srcs: [
+        "apex/java/android/media/MediaMetadata.java",
+        "apex/java/android/media/MediaParceledListSlice.java",
+        "apex/java/android/media/VolumeProvider.java",
+        "apex/java/android/media/browse/MediaBrowser.java",
+        "apex/java/android/media/browse/MediaBrowserUtils.java",
+        "apex/java/android/media/session/ControllerCallbackLink.java",
+        "apex/java/android/media/session/ControllerLink.java",
+        "apex/java/android/media/session/ISession.aidl",
+        "apex/java/android/media/session/ISessionCallback.aidl",
+        "apex/java/android/media/session/ISessionController.aidl",
+        "apex/java/android/media/session/ISessionControllerCallback.aidl",
+        "apex/java/android/media/session/MediaController.java",
+        "apex/java/android/media/session/MediaSessionEngine.java",
+        "apex/java/android/media/session/MediaSessionProviderService.java",
+        "apex/java/android/media/session/PlaybackState.java",
+        "apex/java/android/media/session/SessionCallbackLink.java",
+        "apex/java/android/media/session/SessionLink.java",
+        "apex/java/android/service/media/IMediaBrowserService.aidl",
+        "apex/java/android/service/media/IMediaBrowserServiceCallbacks.aidl",
+        "apex/java/android/service/media/MediaBrowserService.java",
+    ],
+}
+
+filegroup {
+    name: "media1-srcs-without-aidls",
+    srcs: [
+        ":media1-srcs",
+    ],
+    exclude_srcs: [
+        "apex/java/android/media/session/ISession.aidl",
+        "apex/java/android/media/session/ISessionCallback.aidl",
+        "apex/java/android/media/session/ISessionController.aidl",
+        "apex/java/android/media/session/ISessionControllerCallback.aidl",
+        "apex/java/android/service/media/IMediaBrowserService.aidl",
+        "apex/java/android/service/media/IMediaBrowserServiceCallbacks.aidl",
+    ],
+}
+
+filegroup {
+    name: "mediasession2-srcs",
+    srcs: [
+        "apex/java/android/media/Controller2Link.java",
+        "apex/java/android/media/IMediaController2.aidl",
+        "apex/java/android/media/IMediaSession2.aidl",
+        "apex/java/android/media/IMediaSession2Service.aidl",
+        "apex/java/android/media/MediaConstants.java",
+        "apex/java/android/media/MediaController2.java",
+        "apex/java/android/media/MediaItem2.java",
+        "apex/java/android/media/MediaSession2.java",
+        "apex/java/android/media/MediaSession2Service.java",
+        "apex/java/android/media/Session2Command.java",
+        "apex/java/android/media/Session2CommandGroup.java",
+        "apex/java/android/media/Session2Link.java",
+    ],
+}
+
+filegroup {
+    name: "mediasession2-srcs-without-aidls",
+    srcs: [
+        ":mediasession2-srcs",
+    ],
+    exclude_srcs: [
+        "apex/java/android/media/IMediaController2.aidl",
+        "apex/java/android/media/IMediaSession2.aidl",
+        "apex/java/android/media/IMediaSession2Service.aidl",
+    ],
+}
+
+filegroup {
+    name: "mediaplayer2-srcs",
     srcs: [
         "apex/java/android/media/CloseGuard.java",
         "apex/java/android/media/DataSourceCallback.java",
diff --git a/media/java/android/media/Controller2Link.aidl b/media/apex/java/android/media/Controller2Link.aidl
similarity index 100%
rename from media/java/android/media/Controller2Link.aidl
rename to media/apex/java/android/media/Controller2Link.aidl
diff --git a/media/java/android/media/Controller2Link.java b/media/apex/java/android/media/Controller2Link.java
similarity index 100%
rename from media/java/android/media/Controller2Link.java
rename to media/apex/java/android/media/Controller2Link.java
diff --git a/media/java/android/media/IMediaController2.aidl b/media/apex/java/android/media/IMediaController2.aidl
similarity index 100%
rename from media/java/android/media/IMediaController2.aidl
rename to media/apex/java/android/media/IMediaController2.aidl
diff --git a/media/java/android/media/IMediaSession2.aidl b/media/apex/java/android/media/IMediaSession2.aidl
similarity index 100%
rename from media/java/android/media/IMediaSession2.aidl
rename to media/apex/java/android/media/IMediaSession2.aidl
diff --git a/media/java/android/media/IMediaSession2Service.aidl b/media/apex/java/android/media/IMediaSession2Service.aidl
similarity index 100%
rename from media/java/android/media/IMediaSession2Service.aidl
rename to media/apex/java/android/media/IMediaSession2Service.aidl
diff --git a/media/java/android/media/MediaConstants.java b/media/apex/java/android/media/MediaConstants.java
similarity index 87%
rename from media/java/android/media/MediaConstants.java
rename to media/apex/java/android/media/MediaConstants.java
index 65b6f55..45ea826 100644
--- a/media/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/java/android/media/MediaController2.java b/media/apex/java/android/media/MediaController2.java
similarity index 97%
rename from media/java/android/media/MediaController2.java
rename to media/apex/java/android/media/MediaController2.java
index 887b447..41721f3 100644
--- a/media/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/java/android/media/MediaItem2.java b/media/apex/java/android/media/MediaItem2.java
similarity index 100%
rename from media/java/android/media/MediaItem2.java
rename to media/apex/java/android/media/MediaItem2.java
diff --git a/media/java/android/media/MediaMetadata.aidl b/media/apex/java/android/media/MediaMetadata.aidl
similarity index 100%
rename from media/java/android/media/MediaMetadata.aidl
rename to media/apex/java/android/media/MediaMetadata.aidl
diff --git a/media/java/android/media/MediaMetadata.java b/media/apex/java/android/media/MediaMetadata.java
similarity index 98%
rename from media/java/android/media/MediaMetadata.java
rename to media/apex/java/android/media/MediaMetadata.java
index a3d75a3..dea98d5 100644
--- a/media/java/android/media/MediaMetadata.java
+++ b/media/apex/java/android/media/MediaMetadata.java
@@ -250,16 +250,16 @@
      * second line for media described by this metadata this should be preferred
      * to other fields if present.
      */
-    public static final String METADATA_KEY_DISPLAY_SUBTITLE
-            = "android.media.metadata.DISPLAY_SUBTITLE";
+    public static final String METADATA_KEY_DISPLAY_SUBTITLE =
+            "android.media.metadata.DISPLAY_SUBTITLE";
 
     /**
      * A description that is suitable for display to the user. When displaying
      * more information for media described by this metadata this should be
      * preferred to other fields if present.
      */
-    public static final String METADATA_KEY_DISPLAY_DESCRIPTION
-            = "android.media.metadata.DISPLAY_DESCRIPTION";
+    public static final String METADATA_KEY_DISPLAY_DESCRIPTION =
+            "android.media.metadata.DISPLAY_DESCRIPTION";
 
     /**
      * An icon or thumbnail that is suitable for display to the user. When
@@ -270,8 +270,8 @@
      * if it is too large. For higher resolution artwork
      * {@link #METADATA_KEY_DISPLAY_ICON_URI} should be used instead.
      */
-    public static final String METADATA_KEY_DISPLAY_ICON
-            = "android.media.metadata.DISPLAY_ICON";
+    public static final String METADATA_KEY_DISPLAY_ICON =
+            "android.media.metadata.DISPLAY_ICON";
 
     /**
      * A Uri formatted String for an icon or thumbnail that is suitable for
@@ -285,8 +285,8 @@
      * {@link ContentResolver#EXTRA_SIZE} for retrieving scaled artwork through
      * {@link ContentResolver#openTypedAssetFileDescriptor(Uri, String, Bundle)}.
      */
-    public static final String METADATA_KEY_DISPLAY_ICON_URI
-            = "android.media.metadata.DISPLAY_ICON_URI";
+    public static final String METADATA_KEY_DISPLAY_ICON_URI =
+            "android.media.metadata.DISPLAY_ICON_URI";
 
     /**
      * A String key for identifying the content. This value is specific to the
@@ -320,8 +320,8 @@
      * <li>{@link MediaDescription#BT_FOLDER_TYPE_YEARS}</li>
      * </ul>
      */
-    public static final String METADATA_KEY_BT_FOLDER_TYPE
-            = "android.media.metadata.BT_FOLDER_TYPE";
+    public static final String METADATA_KEY_BT_FOLDER_TYPE =
+            "android.media.metadata.BT_FOLDER_TYPE";
 
     private static final @TextKey String[] PREFERRED_DESCRIPTION_ORDER = {
             METADATA_KEY_TITLE,
diff --git a/media/java/android/media/MediaParceledListSlice.aidl b/media/apex/java/android/media/MediaParceledListSlice.aidl
similarity index 100%
rename from media/java/android/media/MediaParceledListSlice.aidl
rename to media/apex/java/android/media/MediaParceledListSlice.aidl
diff --git a/media/java/android/media/MediaParceledListSlice.java b/media/apex/java/android/media/MediaParceledListSlice.java
similarity index 100%
rename from media/java/android/media/MediaParceledListSlice.java
rename to media/apex/java/android/media/MediaParceledListSlice.java
diff --git a/media/apex/java/android/media/MediaPlayer2.java b/media/apex/java/android/media/MediaPlayer2.java
index aa79c41..a2feec2 100644
--- a/media/apex/java/android/media/MediaPlayer2.java
+++ b/media/apex/java/android/media/MediaPlayer2.java
@@ -26,6 +26,7 @@
 import android.content.res.AssetFileDescriptor;
 import android.graphics.Rect;
 import android.graphics.SurfaceTexture;
+import android.media.MediaDrm.KeyRequest;
 import android.media.MediaPlayer2.DrmInfo;
 import android.media.MediaPlayer2Proto.PlayerMessage;
 import android.media.MediaPlayer2Proto.Value;
@@ -76,6 +77,8 @@
 import java.util.concurrent.Executors;
 import java.util.concurrent.Future;
 import java.util.concurrent.RejectedExecutionException;
+import java.util.concurrent.TimeUnit;
+import java.util.concurrent.TimeoutException;
 import java.util.concurrent.atomic.AtomicInteger;
 import java.util.concurrent.atomic.AtomicLong;
 
@@ -298,7 +301,7 @@
     private volatile float mVolume = 1.0f;
     private VideoSize mVideoSize = new VideoSize(0, 0);
 
-    private ExecutorService mDrmThreadPool = Executors.newCachedThreadPool();
+    private static ExecutorService sDrmThreadPool = Executors.newCachedThreadPool();
 
     // Creating a dummy audio track, used for keeping session id alive
     private final Object mSessionIdLock = new Object();
@@ -402,7 +405,7 @@
 
         // Modular DRM clean up
         synchronized (mDrmEventCallbackLock) {
-            mDrmEventCallbackRecords.clear();
+            mDrmEventCallback = null;
         }
 
         native_release();
@@ -2293,6 +2296,7 @@
     private static final int MEDIA_PAUSED = 7;
     private static final int MEDIA_STOPPED = 8;
     private static final int MEDIA_SKIPPED = 9;
+    private static final int MEDIA_DRM_PREPARED = 10;
     private static final int MEDIA_NOTIFY_TIME = 98;
     private static final int MEDIA_TIMED_TEXT = 99;
     private static final int MEDIA_ERROR = 100;
@@ -2330,7 +2334,16 @@
 
             switch(msg.what) {
                 case MEDIA_PREPARED:
+                case MEDIA_DRM_PREPARED:
                 {
+                    sourceInfo.mPrepareBarrier--;
+                    if (sourceInfo.mPrepareBarrier > 0) {
+                        break;
+                    } else if (sourceInfo.mPrepareBarrier < 0) {
+                        Log.w(TAG, "duplicated (drm) prepared events");
+                        break;
+                    }
+
                     if (dsd != null) {
                         sendEvent(new EventNotifier() {
                             @Override
@@ -2387,14 +2400,42 @@
                         }
 
                         // notifying the client outside the lock
+                        DrmPreparationInfo drmPrepareInfo = null;
                         if (drmInfo != null) {
-                            sendDrmEvent(new DrmEventNotifier() {
+                            try {
+                                drmPrepareInfo = sendDrmEventWait(
+                                        new DrmEventNotifier<DrmPreparationInfo>() {
+                                            @Override
+                                            public DrmPreparationInfo notifyWait(
+                                                    DrmEventCallback callback) {
+                                                return callback.onDrmInfo(mMediaPlayer, dsd,
+                                                        drmInfo);
+                                            }
+                                        });
+                            } catch (InterruptedException | ExecutionException
+                                    | TimeoutException e) {
+                                Log.w(TAG, "Exception while waiting for DrmPreparationInfo", e);
+                            }
+                        }
+                        if (sourceInfo.mDrmHandle.setPreparationInfo(drmPrepareInfo)) {
+                            sourceInfo.mPrepareBarrier++;
+                            final Task prepareDrmTask;
+                            prepareDrmTask = newPrepareDrmTask(dsd, drmPrepareInfo.mUUID);
+                            mTaskHandler.post(new Runnable() {
                                 @Override
-                                public void notify(DrmEventCallback callback) {
-                                    callback.onDrmInfo(
-                                            mMediaPlayer, dsd, drmInfo);
+                                public void run() {
+                                    // Run as simple Runnable, not Task
+                                    try {
+                                        prepareDrmTask.process();
+                                    } catch (NoDrmSchemeException | IOException e) {
+                                        final String errMsg;
+                                        errMsg = "Unexpected Exception during prepareDrm";
+                                        throw new RuntimeException(errMsg, e);
+                                    }
                                 }
                             });
+                        } else {
+                            Log.w(TAG, "No valid DrmPreparationInfo set");
                         }
                     } else {
                         Log.w(TAG, "MEDIA_DRM_INFO msg.obj of unexpected type " + msg.obj);
@@ -2892,7 +2933,8 @@
     private void sendDrmEvent(final DrmEventNotifier notifier) {
         synchronized (mDrmEventCallbackLock) {
             try {
-                for (Pair<Executor, DrmEventCallback> cb : mDrmEventCallbackRecords) {
+                Pair<Executor, DrmEventCallback> cb = mDrmEventCallback;
+                if (cb != null) {
                     cb.first.execute(() -> notifier.notify(cb.second));
                 }
             } catch (RejectedExecutionException e) {
@@ -2903,13 +2945,18 @@
     }
 
     private <T> T sendDrmEventWait(final DrmEventNotifier<T> notifier)
-            throws InterruptedException, ExecutionException {
+            throws InterruptedException, ExecutionException, TimeoutException {
+        return sendDrmEventWait(notifier, 0);
+    }
+
+    private <T> T sendDrmEventWait(final DrmEventNotifier<T> notifier, final long timeoutMs)
+            throws InterruptedException, ExecutionException, TimeoutException {
         synchronized (mDrmEventCallbackLock) {
-            mDrmEventCallbackRecords.get(0);
-            for (Pair<Executor, DrmEventCallback> cb : mDrmEventCallbackRecords) {
+            Pair<Executor, DrmEventCallback> cb = mDrmEventCallback;
+            if (cb != null) {
                 CompletableFuture<T> ret = new CompletableFuture<>();
                 cb.first.execute(() -> ret.complete(notifier.notifyWait(cb.second)));
-                return ret.get();
+                return timeoutMs <= 0 ? ret.get() : ret.get(timeoutMs, TimeUnit.MILLISECONDS);
             }
         }
         return null;
@@ -3388,8 +3435,8 @@
             private Map<String, String> mOptionalParameters;
 
             /**
-             * Set UUID of the crypto scheme selected to decrypt content. An UUID can be retrieved from
-             * the source listening to {@link MediaPlayer2.DrmEventCallback#onDrmInfo}.
+             * Set UUID of the crypto scheme selected to decrypt content. An UUID can be retrieved
+             * from the source listening to {@link MediaPlayer2.DrmEventCallback#onDrmInfo}.
              *
              * @param uuid of selected crypto scheme
              * @return this
@@ -3401,11 +3448,12 @@
 
             /**
              * Set identifier of a persisted offline key obtained from
-             * {@link MediaPlayer2.DrmEventCallback#onDrmPrepared(MediaPlayer2, DataSourceDesc, int, byte[])}.
+             * {@link MediaPlayer2.DrmEventCallback#onDrmPrepared}.
              *
              * A {@code keySetId} can be used to restore persisted offline keys into a new playback
-             * session of a DRM protected data source. When {@code keySetId} is set, {@code initData},
-             * {@code mimeType}, {@code keyType}, {@code optionalParameters} are ignored.
+             * session of a DRM protected data source. When {@code keySetId} is set,
+             * {@code initData}, {@code mimeType}, {@code keyType}, {@code optionalParameters} are
+             * ignored.
              *
              * @param keySetId identifier of a persisted offline key
              * @return this
@@ -3455,24 +3503,24 @@
             }
 
             /**
-             * Set optional parameters to be included in a {@link MediaDrm.KeyRequest} message sent to
-             * the license server.
+             * Set optional parameters to be included in a {@link MediaDrm.KeyRequest} message sent
+             * to the license server.
              *
              * @param optionalParameters optional parameters to be included in a key request
              * @return this
              */
-            public Builder setOptionalParameters(
-                    @Nullable Map<String, String> optionalParameters) {
+            public Builder setOptionalParameters(@Nullable Map<String, String> optionalParameters) {
                 this.mOptionalParameters = optionalParameters;
                 return this;
             }
 
             /**
-             * @return an immutable {@link MediaPlayer2.DrmPreparationInfo} representing the settings of this builder
+             * @return an immutable {@link MediaPlayer2.DrmPreparationInfo} representing the
+             *         settings of this builder
              */
             public MediaPlayer2.DrmPreparationInfo build() {
-                return new MediaPlayer2.DrmPreparationInfo(mUUID, mKeySetId, mInitData, mMimeType, mKeyType,
-                        mOptionalParameters);
+                return new MediaPlayer2.DrmPreparationInfo(mUUID, mKeySetId, mInitData, mMimeType,
+                        mKeyType, mOptionalParameters);
             }
 
         }
@@ -3494,6 +3542,20 @@
             this.mOptionalParameters = optionalParameters;
         }
 
+        boolean isValid() {
+            if (mUUID == null) {
+                return false;
+            }
+            if (mKeySetId != null) {
+                // offline restore case
+                return true;
+            }
+            if (mInitData != null && mMimeType != null) {
+                // new streaming license case
+                return true;
+            }
+            return false;
+        }
     }
 
     /**
@@ -3501,6 +3563,7 @@
      * DRM events.
      */
     public static class DrmEventCallback {
+
         /**
          * Called to indicate DRM info is available. Return a {@link DrmPreparationInfo} object that
          * bundles DRM initialization parameters.
@@ -3517,21 +3580,6 @@
         }
 
         /**
-         * Called to notify the client that {@code mp} is ready to decrypt DRM protected data source
-         * {@code dsd}
-         *
-         * @param mp the {@code MediaPlayer2} associated with this callback
-         * @param dsd the {@link DataSourceDesc} of this data source
-         * @param status the result of DRM preparation.
-         * @param keySetId optional identifier that can be used to restore DRM playback initiated
-         *        with a {@link MediaDrm#KEY_TYPE_OFFLINE} key request.
-         *
-         * @see DrmPreparationInfo.Builder#setKeySetId(byte[])
-         */
-        public void onDrmPrepared(@NonNull MediaPlayer2 mp, @NonNull DataSourceDesc dsd,
-                @PrepareDrmStatusCode int status, @Nullable byte[] keySetId) { }
-
-        /**
          * Called to give the app the opportunity to configure DRM before the session is created.
          *
          * This facilitates configuration of the properties, like 'securityLevel', which
@@ -3567,11 +3615,25 @@
             return null;
         }
 
+        /**
+         * Called to notify the client that {@code mp} is ready to decrypt DRM protected data source
+         * {@code dsd} or if there is an error during DRM preparation
+         *
+         * @param mp the {@code MediaPlayer2} associated with this callback
+         * @param dsd the {@link DataSourceDesc} of this data source
+         * @param status the result of DRM preparation.
+         * @param keySetId optional identifier that can be used to restore DRM playback initiated
+         *        with a {@link MediaDrm#KEY_TYPE_OFFLINE} key request.
+         *
+         * @see DrmPreparationInfo.Builder#setKeySetId(byte[])
+         */
+        public void onDrmPrepared(@NonNull MediaPlayer2 mp, @NonNull DataSourceDesc dsd,
+                @PrepareDrmStatusCode int status, @Nullable byte[] keySetId) { }
+
     }
 
     private final Object mDrmEventCallbackLock = new Object();
-    private List<Pair<Executor, DrmEventCallback>> mDrmEventCallbackRecords =
-            new ArrayList<Pair<Executor, DrmEventCallback>>();
+    private Pair<Executor, DrmEventCallback> mDrmEventCallback;
 
     /**
      * Registers the callback to be invoked for various DRM events.
@@ -3590,25 +3652,17 @@
                     "Illegal null Executor for the EventCallback");
         }
         synchronized (mDrmEventCallbackLock) {
-            mDrmEventCallbackRecords = Collections.singletonList(
-                    new Pair<Executor, DrmEventCallback>(executor, eventCallback));
+            mDrmEventCallback = new Pair<Executor, DrmEventCallback>(executor, eventCallback);
         }
     }
 
     /**
-     * Unregisters the {@link DrmEventCallback}.
-     *
-     * @param eventCallback the callback to be unregistered
-     * @hide
+     * Clear the {@link DrmEventCallback}.
      */
     // This is a synchronous call.
-    public void unregisterDrmEventCallback(DrmEventCallback eventCallback) {
+    public void clearDrmEventCallback() {
         synchronized (mDrmEventCallbackLock) {
-            for (Pair<Executor, DrmEventCallback> cb : mDrmEventCallbackRecords) {
-                if (cb.second == eventCallback) {
-                    mDrmEventCallbackRecords.remove(cb);
-                }
-            }
+            mDrmEventCallback = null;
         }
     }
 
@@ -3651,6 +3705,18 @@
      */
     public static final int PREPARE_DRM_STATUS_RESOURCE_BUSY = 5;
 
+    /**
+     * Restoring persisted offline keys failed.
+     * @hide
+     */
+    public static final int PREPARE_DRM_STATUS_RESTORE_ERROR = 6;
+
+    /**
+     * Error during key request/response exchange with license server.
+     * @hide
+     */
+    public static final int PREPARE_DRM_STATUS_KEY_EXCHANGE_ERROR = 7;
+
     /** @hide */
     @IntDef(flag = false, prefix = "PREPARE_DRM_STATUS", value = {
         PREPARE_DRM_STATUS_SUCCESS,
@@ -3659,6 +3725,8 @@
         PREPARE_DRM_STATUS_PREPARATION_ERROR,
         PREPARE_DRM_STATUS_UNSUPPORTED_SCHEME,
         PREPARE_DRM_STATUS_RESOURCE_BUSY,
+        PREPARE_DRM_STATUS_RESTORE_ERROR,
+        PREPARE_DRM_STATUS_KEY_EXCHANGE_ERROR,
     })
     @Retention(RetentionPolicy.SOURCE)
     public @interface PrepareDrmStatusCode {}
@@ -3747,12 +3815,16 @@
      */
     // This is an asynchronous call.
     public Object prepareDrm(@NonNull DataSourceDesc dsd, @NonNull UUID uuid) {
-        return addTask(new Task(CALL_COMPLETED_PREPARE_DRM, true) {
+        return addTask(newPrepareDrmTask(dsd, uuid));
+    }
+
+    private Task newPrepareDrmTask(DataSourceDesc dsd, UUID uuid) {
+        return new Task(CALL_COMPLETED_PREPARE_DRM, true) {
             @Override
             void process() {
                 final SourceInfo sourceInfo = getSourceInfo(dsd);
                 int status = PREPARE_DRM_STATUS_PREPARATION_ERROR;
-                boolean sendEvent = true;
+                boolean finishPrepare = true;
 
                 if (sourceInfo == null) {
                     Log.e(TAG, "prepareDrm(): DataSource not found.");
@@ -3780,8 +3852,8 @@
                     status = sourceInfo.mDrmHandle.handleProvisioninig(uuid, mTaskId);
 
                     if (status == PREPARE_DRM_STATUS_SUCCESS) {
-                        // DrmEventCallback will be fired in provisioning
-                        sendEvent = false;
+                        // License will be setup in provisioning
+                        finishPrepare = false;
                     } else {
                         synchronized (sourceInfo.mDrmHandle) {
                             sourceInfo.mDrmHandle.cleanDrmObj();
@@ -3808,23 +3880,16 @@
                     status = PREPARE_DRM_STATUS_PREPARATION_ERROR;
                 }
 
-                if (sendEvent) {
-                    final int prepareDrmStatus = status;
-                    sendDrmEvent(new DrmEventNotifier() {
-                        @Override
-                        public void notify(DrmEventCallback callback) {
-                            callback.onDrmPrepared(MediaPlayer2.this, dsd, prepareDrmStatus,
-                                    /* TODO: keySetId */ null);
-                        }
-                    });
-
+                if (finishPrepare) {
+                    sourceInfo.mDrmHandle.finishPrepare(status);
                     synchronized (mTaskLock) {
                         mCurrentTask = null;
                         processPendingTask_l();
                     }
                 }
+
             }
-        });
+        };
     }
 
     /**
@@ -4417,7 +4482,7 @@
     };
 
     // Modular DRM
-    final class DrmHandle {
+    private class DrmHandle {
 
         static final int PROVISION_TIMEOUT_MS = 60000;
 
@@ -4432,6 +4497,7 @@
         boolean mDrmProvisioningInProgress;
         boolean mPrepareDrmInProgress;
         Future<?> mProvisionResult;
+        DrmPreparationInfo mPrepareInfo;
         //--- guarded by |this| end
 
         DrmHandle(DataSourceDesc dsd, long srcId) {
@@ -4441,7 +4507,7 @@
 
         void prepare(UUID uuid) throws UnsupportedSchemeException,
                 ResourceBusyException, NotProvisionedException, InterruptedException,
-                ExecutionException {
+                ExecutionException, TimeoutException {
             Log.v(TAG, "prepareDrm: uuid: " + uuid);
 
             synchronized (this) {
@@ -4580,7 +4646,7 @@
                 // networking in a background thread
                 mDrmProvisioningInProgress = true;
 
-                mProvisionResult = mDrmThreadPool.submit(newProvisioningTask(uuid, taskId));
+                mProvisionResult = sDrmThreadPool.submit(newProvisioningTask(uuid, taskId));
 
                 return PREPARE_DRM_STATUS_SUCCESS;
             }
@@ -4654,14 +4720,7 @@
             }  // synchronized
 
             // calling the callback outside the lock
-            final int finalStatus = status;
-            sendDrmEvent(new DrmEventNotifier() {
-                @Override
-                public void notify(DrmEventCallback callback) {
-                    callback.onDrmPrepared(
-                            MediaPlayer2.this, mDSD, finalStatus, /* TODO: keySetId */ null);
-                }
-            });
+            finishPrepare(status);
 
             synchronized (mTaskLock) {
                 if (mCurrentTask != null
@@ -4703,6 +4762,93 @@
             return success;
         }
 
+        synchronized boolean setPreparationInfo(DrmPreparationInfo prepareInfo) {
+            if (prepareInfo == null || !prepareInfo.isValid() || mPrepareInfo != null) {
+                return false;
+            }
+            mPrepareInfo = prepareInfo;
+            return true;
+        }
+
+        void finishPrepare(int status) {
+            if (status != PREPARE_DRM_STATUS_SUCCESS) {
+                notifyPrepared(status, null);
+                return;
+            }
+
+            if (mPrepareInfo == null) {
+                // Deprecated: this can only happen when using MediaPlayer Version 1 APIs
+                notifyPrepared(status, null);
+                return;
+            }
+
+            final byte[] keySetId = mPrepareInfo.mKeySetId;
+            if (keySetId != null) {
+                try {
+                    mDrmObj.restoreKeys(mDrmSessionId, keySetId);
+                    notifyPrepared(PREPARE_DRM_STATUS_SUCCESS, keySetId);
+                } catch (Exception e) {
+                    notifyPrepared(PREPARE_DRM_STATUS_RESTORE_ERROR, keySetId);
+                }
+                return;
+            }
+
+            sDrmThreadPool.submit(newKeyExchangeTask());
+        }
+
+        Runnable newKeyExchangeTask() {
+            return new Runnable() {
+                @Override
+                public void run() {
+                    final byte[] initData = mPrepareInfo.mInitData;
+                    final String mimeType = mPrepareInfo.mMimeType;
+                    final int keyType = mPrepareInfo.mKeyType;
+                    final Map<String, String> optionalParams = mPrepareInfo.mOptionalParameters;
+                    byte[] keySetId = null;
+                    try {
+                        KeyRequest req;
+                        req = getDrmKeyRequest(null, initData, mimeType, keyType, optionalParams);
+                        byte[] response = sendDrmEventWait(new DrmEventNotifier<byte[]>() {
+                            @Override
+                            public byte[] notifyWait(DrmEventCallback callback) {
+                                final MediaPlayer2 mp = MediaPlayer2.this;
+                                return callback.onDrmKeyRequest(mp, mDSD, req);
+                            }
+                        });
+                        keySetId = provideDrmKeyResponse(null, response);
+                    } catch (Exception e) {
+                    }
+                    if (keySetId == null) {
+                        notifyPrepared(PREPARE_DRM_STATUS_KEY_EXCHANGE_ERROR, null);
+                    } else {
+                        notifyPrepared(PREPARE_DRM_STATUS_SUCCESS, keySetId);
+                    }
+                }
+            };
+        }
+
+        void notifyPrepared(final int status, byte[] keySetId) {
+
+            Message msg;
+            if (status == PREPARE_DRM_STATUS_SUCCESS) {
+                msg = mTaskHandler.obtainMessage(
+                        MEDIA_DRM_PREPARED, 0, 0, null);
+            } else {
+                msg = mTaskHandler.obtainMessage(
+                        MEDIA_ERROR, status, MEDIA_ERROR_UNKNOWN, null);
+            }
+            mTaskHandler.sendMessage(msg);
+
+            sendDrmEvent(new DrmEventNotifier() {
+                @Override
+                public void notify(DrmEventCallback callback) {
+                    callback.onDrmPrepared(MediaPlayer2.this, mDSD, status,
+                            keySetId);
+                }
+            });
+
+        }
+
         void cleanDrmObj() {
             // the caller holds mDrmLock
             Log.v(TAG, "cleanDrmObj: mDrmObj=" + mDrmObj + " mDrmSessionId=" + mDrmSessionId);
@@ -4768,6 +4914,7 @@
                 // set to false to avoid duplicate release calls
                 this.mActiveDrmUUID = null;
 
+                native_releaseDrm(mSrcId);
                 cleanDrmObj();
             }   // synchronized
         }
@@ -4924,6 +5071,7 @@
         final long mId = mSrcIdGenerator.getAndIncrement();
         AtomicInteger mBufferedPercentage = new AtomicInteger(0);
         boolean mClosed = false;
+        int mPrepareBarrier = 1;
 
         // m*AsNextSource (below) only applies to pending data sources in the playlist;
         // the meanings of mCurrentSourceInfo.{mStateAsNextSource,mPlayPendingAsNextSource}
@@ -5022,7 +5170,7 @@
         if (sourceInfo != null) {
             sourceInfo.close();
             Runnable task = sourceInfo.mDrmHandle.newCleanupTask();
-            mDrmThreadPool.submit(task);
+            sDrmThreadPool.submit(task);
         }
     }
 
diff --git a/media/java/android/media/MediaSession2.java b/media/apex/java/android/media/MediaSession2.java
similarity index 98%
rename from media/java/android/media/MediaSession2.java
rename to media/apex/java/android/media/MediaSession2.java
index fdd07fd..80c91cc 100644
--- a/media/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/java/android/media/MediaSession2Service.java b/media/apex/java/android/media/MediaSession2Service.java
similarity index 98%
rename from media/java/android/media/MediaSession2Service.java
rename to media/apex/java/android/media/MediaSession2Service.java
index 5bb746a..f18cd31 100644
--- a/media/java/android/media/MediaSession2Service.java
+++ b/media/apex/java/android/media/MediaSession2Service.java
@@ -16,6 +16,8 @@
 
 package android.media;
 
+import static android.media.Session2Token.SESSION_SERVICE_INTERFACE;
+
 import android.annotation.CallSuper;
 import android.annotation.NonNull;
 import android.annotation.Nullable;
@@ -45,10 +47,6 @@
  * for consistent behavior across all devices.
  */
 public abstract class MediaSession2Service extends Service {
-    /**
-     * The {@link Intent} that must be declared as handled by the service.
-     */
-    public static final String SERVICE_INTERFACE = "android.media.MediaSession2Service";
 
     private static final String TAG = "MediaSession2Service";
     private static final boolean DEBUG = Log.isLoggable(TAG, Log.DEBUG);
@@ -100,7 +98,7 @@
     @Override
     @Nullable
     public IBinder onBind(@NonNull Intent intent) {
-        if (SERVICE_INTERFACE.equals(intent.getAction())) {
+        if (SESSION_SERVICE_INTERFACE.equals(intent.getAction())) {
             synchronized (mLock) {
                 return mStub;
             }
diff --git a/media/java/android/media/Session2Command.aidl b/media/apex/java/android/media/Session2Command.aidl
similarity index 100%
rename from media/java/android/media/Session2Command.aidl
rename to media/apex/java/android/media/Session2Command.aidl
diff --git a/media/java/android/media/Session2Command.java b/media/apex/java/android/media/Session2Command.java
similarity index 100%
rename from media/java/android/media/Session2Command.java
rename to media/apex/java/android/media/Session2Command.java
diff --git a/media/java/android/media/Session2CommandGroup.java b/media/apex/java/android/media/Session2CommandGroup.java
similarity index 96%
rename from media/java/android/media/Session2CommandGroup.java
rename to media/apex/java/android/media/Session2CommandGroup.java
index a189c26..2dab697 100644
--- a/media/java/android/media/Session2CommandGroup.java
+++ b/media/apex/java/android/media/Session2CommandGroup.java
@@ -71,11 +71,10 @@
      */
     @SuppressWarnings("WeakerAccess") /* synthetic access */
     Session2CommandGroup(Parcel in) {
-        Session2Command[] commands = in.readParcelableArray(
-                Session2Command.class.getClassLoader(), Session2Command.class);
+        Parcelable[] commands = in.readParcelableArray(Session2Command.class.getClassLoader());
         if (commands != null) {
-            for (Session2Command command : commands) {
-                mCommands.add(command);
+            for (Parcelable command : commands) {
+                mCommands.add((Session2Command) command);
             }
         }
     }
diff --git a/media/java/android/media/Session2Link.java b/media/apex/java/android/media/Session2Link.java
similarity index 100%
rename from media/java/android/media/Session2Link.java
rename to media/apex/java/android/media/Session2Link.java
diff --git a/media/java/android/media/Session2Token.aidl b/media/apex/java/android/media/Session2Token.aidl
similarity index 100%
rename from media/java/android/media/Session2Token.aidl
rename to media/apex/java/android/media/Session2Token.aidl
diff --git a/media/java/android/media/VolumeProvider.java b/media/apex/java/android/media/VolumeProvider.java
similarity index 97%
rename from media/java/android/media/VolumeProvider.java
rename to media/apex/java/android/media/VolumeProvider.java
index 0297406..49202ee 100644
--- a/media/java/android/media/VolumeProvider.java
+++ b/media/apex/java/android/media/VolumeProvider.java
@@ -158,7 +158,10 @@
      * @hide
      */
     @SystemApi
-    public static abstract class Callback {
+    public abstract static class Callback {
+        /**
+         * Called when volume changed.
+         */
         public abstract void onVolumeChanged(VolumeProvider volumeProvider);
     }
 }
diff --git a/media/java/android/media/browse/MediaBrowser.aidl b/media/apex/java/android/media/browse/MediaBrowser.aidl
similarity index 100%
rename from media/java/android/media/browse/MediaBrowser.aidl
rename to media/apex/java/android/media/browse/MediaBrowser.aidl
diff --git a/media/java/android/media/browse/MediaBrowser.java b/media/apex/java/android/media/browse/MediaBrowser.java
similarity index 98%
rename from media/java/android/media/browse/MediaBrowser.java
rename to media/apex/java/android/media/browse/MediaBrowser.java
index b1b14c6..2dffef9 100644
--- a/media/java/android/media/browse/MediaBrowser.java
+++ b/media/apex/java/android/media/browse/MediaBrowser.java
@@ -284,8 +284,8 @@
      */
     public @NonNull ComponentName getServiceComponent() {
         if (!isConnected()) {
-            throw new IllegalStateException("getServiceComponent() called while not connected" +
-                    " (state=" + mState + ")");
+            throw new IllegalStateException("getServiceComponent() called while not connected"
+                    + " (state=" + mState + ")");
         }
         return mServiceComponent;
     }
@@ -331,7 +331,7 @@
      *
      * @throws IllegalStateException if not connected.
      */
-     public @NonNull MediaSession.Token getSessionToken() {
+    public @NonNull MediaSession.Token getSessionToken() {
         if (!isConnected()) {
             throw new IllegalStateException("getSessionToken() called while not connected (state="
                     + mState + ")");
@@ -464,7 +464,7 @@
                     cb.onError(mediaId);
                     return;
                 }
-                cb.onItemLoaded((MediaItem)item);
+                cb.onItemLoaded((MediaItem) item);
             }
         };
         try {
@@ -575,7 +575,7 @@
         }
     }
 
-    private final void onServiceConnected(final IMediaBrowserServiceCallbacks callback,
+    private void onServiceConnected(final IMediaBrowserServiceCallbacks callback,
             final String root, final MediaSession.Token session, final Bundle extra) {
         mHandler.post(new Runnable() {
             @Override
@@ -625,7 +625,7 @@
         });
     }
 
-    private final void onConnectionFailed(final IMediaBrowserServiceCallbacks callback) {
+    private void onConnectionFailed(final IMediaBrowserServiceCallbacks callback) {
         mHandler.post(new Runnable() {
             @Override
             public void run() {
@@ -652,7 +652,7 @@
         });
     }
 
-    private final void onLoadChildren(final IMediaBrowserServiceCallbacks callback,
+    private void onLoadChildren(final IMediaBrowserServiceCallbacks callback,
             final String parentId, final MediaParceledListSlice list, final Bundle options) {
         mHandler.post(new Runnable() {
             @Override
@@ -745,7 +745,7 @@
 
         /** @hide */
         @Retention(RetentionPolicy.SOURCE)
-        @IntDef(flag=true, value = { FLAG_BROWSABLE, FLAG_PLAYABLE })
+        @IntDef(flag = true, value = { FLAG_BROWSABLE, FLAG_PLAYABLE })
         public @interface Flags { }
 
         /**
@@ -886,7 +886,7 @@
     /**
      * Callbacks for subscription related events.
      */
-    public static abstract class SubscriptionCallback {
+    public abstract static class SubscriptionCallback {
         Binder mToken;
 
         public SubscriptionCallback() {
@@ -947,7 +947,7 @@
     /**
      * Callback for receiving the result of {@link #getItem}.
      */
-    public static abstract class ItemCallback {
+    public abstract static class ItemCallback {
         /**
          * Called when the item has been returned by the connected service.
          *
@@ -1078,7 +1078,7 @@
     private static class ServiceCallbacks extends IMediaBrowserServiceCallbacks.Stub {
         private WeakReference<MediaBrowser> mMediaBrowser;
 
-        public ServiceCallbacks(MediaBrowser mediaBrowser) {
+        ServiceCallbacks(MediaBrowser mediaBrowser) {
             mMediaBrowser = new WeakReference<MediaBrowser>(mediaBrowser);
         }
 
@@ -1125,7 +1125,7 @@
         private final List<SubscriptionCallback> mCallbacks;
         private final List<Bundle> mOptionsList;
 
-        public Subscription() {
+        Subscription() {
             mCallbacks = new ArrayList<>();
             mOptionsList = new ArrayList<>();
         }
diff --git a/media/java/android/media/browse/MediaBrowserUtils.java b/media/apex/java/android/media/browse/MediaBrowserUtils.java
similarity index 94%
rename from media/java/android/media/browse/MediaBrowserUtils.java
rename to media/apex/java/android/media/browse/MediaBrowserUtils.java
index 2943e60..19d9f00 100644
--- a/media/java/android/media/browse/MediaBrowserUtils.java
+++ b/media/apex/java/android/media/browse/MediaBrowserUtils.java
@@ -22,6 +22,9 @@
  * @hide
  */
 public class MediaBrowserUtils {
+    /**
+     * Compares whether two bundles are the same.
+     */
     public static boolean areSameOptions(Bundle options1, Bundle options2) {
         if (options1 == options2) {
             return true;
@@ -39,6 +42,9 @@
         }
     }
 
+    /**
+     * Returnes true if the page options has duplicated items.
+     */
     public static boolean hasDuplicatedItems(Bundle options1, Bundle options2) {
         int page1 = options1 == null ? -1 : options1.getInt(MediaBrowser.EXTRA_PAGE, -1);
         int page2 = options2 == null ? -1 : options2.getInt(MediaBrowser.EXTRA_PAGE, -1);
diff --git a/media/java/android/media/session/ControllerCallbackLink.aidl b/media/apex/java/android/media/session/ControllerCallbackLink.aidl
similarity index 100%
rename from media/java/android/media/session/ControllerCallbackLink.aidl
rename to media/apex/java/android/media/session/ControllerCallbackLink.aidl
diff --git a/media/java/android/media/session/ControllerCallbackLink.java b/media/apex/java/android/media/session/ControllerCallbackLink.java
similarity index 100%
rename from media/java/android/media/session/ControllerCallbackLink.java
rename to media/apex/java/android/media/session/ControllerCallbackLink.java
diff --git a/media/java/android/media/session/ControllerLink.aidl b/media/apex/java/android/media/session/ControllerLink.aidl
similarity index 100%
rename from media/java/android/media/session/ControllerLink.aidl
rename to media/apex/java/android/media/session/ControllerLink.aidl
diff --git a/media/java/android/media/session/ControllerLink.java b/media/apex/java/android/media/session/ControllerLink.java
similarity index 100%
rename from media/java/android/media/session/ControllerLink.java
rename to media/apex/java/android/media/session/ControllerLink.java
diff --git a/media/java/android/media/session/ISession.aidl b/media/apex/java/android/media/session/ISession.aidl
similarity index 100%
rename from media/java/android/media/session/ISession.aidl
rename to media/apex/java/android/media/session/ISession.aidl
diff --git a/media/java/android/media/session/ISessionCallback.aidl b/media/apex/java/android/media/session/ISessionCallback.aidl
similarity index 100%
rename from media/java/android/media/session/ISessionCallback.aidl
rename to media/apex/java/android/media/session/ISessionCallback.aidl
diff --git a/media/java/android/media/session/ISessionController.aidl b/media/apex/java/android/media/session/ISessionController.aidl
similarity index 100%
rename from media/java/android/media/session/ISessionController.aidl
rename to media/apex/java/android/media/session/ISessionController.aidl
diff --git a/media/java/android/media/session/ISessionControllerCallback.aidl b/media/apex/java/android/media/session/ISessionControllerCallback.aidl
similarity index 100%
rename from media/java/android/media/session/ISessionControllerCallback.aidl
rename to media/apex/java/android/media/session/ISessionControllerCallback.aidl
diff --git a/media/java/android/media/session/MediaController.aidl b/media/apex/java/android/media/session/MediaController.aidl
similarity index 100%
rename from media/java/android/media/session/MediaController.aidl
rename to media/apex/java/android/media/session/MediaController.aidl
diff --git a/media/java/android/media/session/MediaController.java b/media/apex/java/android/media/session/MediaController.java
similarity index 99%
rename from media/java/android/media/session/MediaController.java
rename to media/apex/java/android/media/session/MediaController.java
index 057c9cb..d43acf4 100644
--- a/media/java/android/media/session/MediaController.java
+++ b/media/apex/java/android/media/session/MediaController.java
@@ -206,6 +206,7 @@
                 } catch (RuntimeException e) {
                     Log.wtf(TAG, "Error calling adjustVolumeBy", e);
                 }
+                break;
             }
 
             case KeyEvent.ACTION_UP: {
@@ -319,7 +320,7 @@
      *
      * @return The current set of flags for the session.
      */
-    public @MediaSession.SessionFlags long getFlags() {
+    public long getFlags() {
         try {
             return mSessionBinder.getFlags();
         } catch (RuntimeException e) {
@@ -582,7 +583,7 @@
         return null;
     }
 
-    private final void postMessage(int what, Object obj, Bundle data) {
+    private void postMessage(int what, Object obj, Bundle data) {
         synchronized (mLock) {
             for (int i = mCallbacks.size() - 1; i >= 0; i--) {
                 mCallbacks.get(i).post(what, obj, data);
@@ -594,7 +595,7 @@
      * Callback for receiving updates from the session. A Callback can be
      * registered using {@link #registerCallback}.
      */
-    public static abstract class Callback {
+    public abstract static class Callback {
         /**
          * Override to handle the session being destroyed. The session is no
          * longer valid after this call and calls to it will be ignored.
@@ -1191,11 +1192,11 @@
         }
     }
 
-    private final static class MessageHandler extends Handler {
+    private static final class MessageHandler extends Handler {
         private final MediaController.Callback mCallback;
         private boolean mRegistered = false;
 
-        public MessageHandler(Looper looper, MediaController.Callback cb) {
+        MessageHandler(Looper looper, MediaController.Callback cb) {
             super(looper);
             mCallback = cb;
         }
diff --git a/media/java/android/media/session/MediaSessionEngine.java b/media/apex/java/android/media/session/MediaSessionEngine.java
similarity index 99%
rename from media/java/android/media/session/MediaSessionEngine.java
rename to media/apex/java/android/media/session/MediaSessionEngine.java
index f159a95..1f5fa5f 100644
--- a/media/java/android/media/session/MediaSessionEngine.java
+++ b/media/apex/java/android/media/session/MediaSessionEngine.java
@@ -53,7 +53,7 @@
  */
 @SystemApi
 public final class MediaSessionEngine implements AutoCloseable {
-    private static final String TAG = MediaSession.TAG;
+    private static final String TAG = "MediaSession";
 
     private final Object mLock = new Object();
     private final int mMaxBitmapSize;
@@ -172,7 +172,7 @@
      *
      * @param flags The flags to set for this session.
      */
-    public void setFlags(@MediaSession.SessionFlags int flags) {
+    public void setFlags(int flags) {
         try {
             mSessionLink.setFlags(flags);
         } catch (RuntimeException e) {
@@ -409,7 +409,7 @@
      * <li>{@link Rating#RATING_THUMB_UP_DOWN}</li>
      * </ul>
      */
-    public void setRatingType(@Rating.Style int type) {
+    public void setRatingType(int type) {
         try {
             mSessionLink.setRatingType(type);
         } catch (RuntimeException e) {
diff --git a/media/java/android/media/session/MediaSessionProviderService.java b/media/apex/java/android/media/session/MediaSessionProviderService.java
similarity index 100%
rename from media/java/android/media/session/MediaSessionProviderService.java
rename to media/apex/java/android/media/session/MediaSessionProviderService.java
diff --git a/media/java/android/media/session/PlaybackState.aidl b/media/apex/java/android/media/session/PlaybackState.aidl
similarity index 100%
rename from media/java/android/media/session/PlaybackState.aidl
rename to media/apex/java/android/media/session/PlaybackState.aidl
diff --git a/media/java/android/media/session/PlaybackState.java b/media/apex/java/android/media/session/PlaybackState.java
similarity index 95%
rename from media/java/android/media/session/PlaybackState.java
rename to media/apex/java/android/media/session/PlaybackState.java
index 0d0ec4c..6b28c97 100644
--- a/media/java/android/media/session/PlaybackState.java
+++ b/media/apex/java/android/media/session/PlaybackState.java
@@ -41,7 +41,7 @@
     /**
      * @hide
      */
-    @LongDef(flag=true, value={ACTION_STOP, ACTION_PAUSE, ACTION_PLAY, ACTION_REWIND,
+    @LongDef(flag = true, value = {ACTION_STOP, ACTION_PAUSE, ACTION_PLAY, ACTION_REWIND,
             ACTION_SKIP_TO_PREVIOUS, ACTION_SKIP_TO_NEXT, ACTION_FAST_FORWARD, ACTION_SET_RATING,
             ACTION_SEEK_TO, ACTION_PLAY_PAUSE, ACTION_PLAY_FROM_MEDIA_ID, ACTION_PLAY_FROM_SEARCH,
             ACTION_SKIP_TO_QUEUE_ITEM, ACTION_PLAY_FROM_URI, ACTION_PREPARE,
@@ -191,42 +191,42 @@
      * @see Builder#setState(int, long, float)
      * @see Builder#setState(int, long, float, long)
      */
-    public final static int STATE_NONE = 0;
+    public static final int STATE_NONE = 0;
 
     /**
      * State indicating this item is currently stopped.
      *
      * @see Builder#setState
      */
-    public final static int STATE_STOPPED = 1;
+    public static final int STATE_STOPPED = 1;
 
     /**
      * State indicating this item is currently paused.
      *
      * @see Builder#setState
      */
-    public final static int STATE_PAUSED = 2;
+    public static final int STATE_PAUSED = 2;
 
     /**
      * State indicating this item is currently playing.
      *
      * @see Builder#setState
      */
-    public final static int STATE_PLAYING = 3;
+    public static final int STATE_PLAYING = 3;
 
     /**
      * State indicating this item is currently fast forwarding.
      *
      * @see Builder#setState
      */
-    public final static int STATE_FAST_FORWARDING = 4;
+    public static final int STATE_FAST_FORWARDING = 4;
 
     /**
      * State indicating this item is currently rewinding.
      *
      * @see Builder#setState
      */
-    public final static int STATE_REWINDING = 5;
+    public static final int STATE_REWINDING = 5;
 
     /**
      * State indicating this item is currently buffering and will begin playing
@@ -234,7 +234,7 @@
      *
      * @see Builder#setState
      */
-    public final static int STATE_BUFFERING = 6;
+    public static final int STATE_BUFFERING = 6;
 
     /**
      * State indicating this item is currently in an error state. The error
@@ -242,7 +242,7 @@
      *
      * @see Builder#setState
      */
-    public final static int STATE_ERROR = 7;
+    public static final int STATE_ERROR = 7;
 
     /**
      * State indicating the class doing playback is currently connecting to a
@@ -252,21 +252,21 @@
      *
      * @see Builder#setState
      */
-    public final static int STATE_CONNECTING = 8;
+    public static final int STATE_CONNECTING = 8;
 
     /**
      * State indicating the player is currently skipping to the previous item.
      *
      * @see Builder#setState
      */
-    public final static int STATE_SKIPPING_TO_PREVIOUS = 9;
+    public static final int STATE_SKIPPING_TO_PREVIOUS = 9;
 
     /**
      * State indicating the player is currently skipping to the next item.
      *
      * @see Builder#setState
      */
-    public final static int STATE_SKIPPING_TO_NEXT = 10;
+    public static final int STATE_SKIPPING_TO_NEXT = 10;
 
     /**
      * State indicating the player is currently skipping to a specific item in
@@ -274,12 +274,12 @@
      *
      * @see Builder#setState
      */
-    public final static int STATE_SKIPPING_TO_QUEUE_ITEM = 11;
+    public static final int STATE_SKIPPING_TO_QUEUE_ITEM = 11;
 
     /**
      * Use this value for the position to indicate the position is not known.
      */
-    public final static long PLAYBACK_POSITION_UNKNOWN = -1;
+    public static final long PLAYBACK_POSITION_UNKNOWN = -1;
 
     private final int mState;
     private final long mPosition;
@@ -534,19 +534,19 @@
             return 0;
         }
 
-        public static final Parcelable.Creator<PlaybackState.CustomAction> CREATOR
-                = new Parcelable.Creator<PlaybackState.CustomAction>() {
+        public static final Parcelable.Creator<PlaybackState.CustomAction> CREATOR =
+                new Parcelable.Creator<PlaybackState.CustomAction>() {
 
-            @Override
-            public PlaybackState.CustomAction createFromParcel(Parcel p) {
-                return new PlaybackState.CustomAction(p);
-            }
+                    @Override
+                    public PlaybackState.CustomAction createFromParcel(Parcel p) {
+                        return new PlaybackState.CustomAction(p);
+                    }
 
-            @Override
-            public PlaybackState.CustomAction[] newArray(int size) {
-                return new PlaybackState.CustomAction[size];
-            }
-        };
+                    @Override
+                    public PlaybackState.CustomAction[] newArray(int size) {
+                        return new PlaybackState.CustomAction[size];
+                    }
+                };
 
         /**
          * Returns the action of the {@link CustomAction}.
@@ -588,10 +588,7 @@
 
         @Override
         public String toString() {
-            return "Action:" +
-                    "mName='" + mName +
-                    ", mIcon=" + mIcon +
-                    ", mExtras=" + mExtras;
+            return "Action:" + "mName='" + mName + ", mIcon=" + mIcon + ", mExtras=" + mExtras;
         }
 
         /**
diff --git a/media/java/android/media/session/SessionCallbackLink.aidl b/media/apex/java/android/media/session/SessionCallbackLink.aidl
similarity index 100%
rename from media/java/android/media/session/SessionCallbackLink.aidl
rename to media/apex/java/android/media/session/SessionCallbackLink.aidl
diff --git a/media/java/android/media/session/SessionCallbackLink.java b/media/apex/java/android/media/session/SessionCallbackLink.java
similarity index 100%
rename from media/java/android/media/session/SessionCallbackLink.java
rename to media/apex/java/android/media/session/SessionCallbackLink.java
diff --git a/media/java/android/media/session/SessionLink.aidl b/media/apex/java/android/media/session/SessionLink.aidl
similarity index 100%
rename from media/java/android/media/session/SessionLink.aidl
rename to media/apex/java/android/media/session/SessionLink.aidl
diff --git a/media/java/android/media/session/SessionLink.java b/media/apex/java/android/media/session/SessionLink.java
similarity index 98%
rename from media/java/android/media/session/SessionLink.java
rename to media/apex/java/android/media/session/SessionLink.java
index 0da0a5a..4ea7623 100644
--- a/media/java/android/media/session/SessionLink.java
+++ b/media/apex/java/android/media/session/SessionLink.java
@@ -23,8 +23,6 @@
 import android.media.AudioAttributes;
 import android.media.MediaMetadata;
 import android.media.MediaParceledListSlice;
-import android.media.Rating;
-import android.media.VolumeProvider;
 import android.media.session.MediaSession.QueueItem;
 import android.os.Bundle;
 import android.os.IBinder;
@@ -234,7 +232,7 @@
      *
      * @param type the rating type.
      */
-    void setRatingType(@Rating.Style int type) {
+    void setRatingType(int type) {
         try {
             mISession.setRatingType(type);
         } catch (RemoteException e) {
@@ -261,7 +259,7 @@
      * @param control the volume control type
      * @param max the max volume
      */
-    void setPlaybackToRemote(@VolumeProvider.ControlType int control, int max) {
+    void setPlaybackToRemote(int control, int max) {
         try {
             mISession.setPlaybackToRemote(control, max);
         } catch (RemoteException e) {
diff --git a/media/java/android/service/media/IMediaBrowserService.aidl b/media/apex/java/android/service/media/IMediaBrowserService.aidl
similarity index 94%
rename from media/java/android/service/media/IMediaBrowserService.aidl
rename to media/apex/java/android/service/media/IMediaBrowserService.aidl
index 84f41f6..1c50ec7 100644
--- a/media/java/android/service/media/IMediaBrowserService.aidl
+++ b/media/apex/java/android/service/media/IMediaBrowserService.aidl
@@ -2,9 +2,7 @@
 
 package android.service.media;
 
-import android.content.res.Configuration;
 import android.service.media.IMediaBrowserServiceCallbacks;
-import android.net.Uri;
 import android.os.Bundle;
 import android.os.ResultReceiver;
 
diff --git a/media/java/android/service/media/IMediaBrowserServiceCallbacks.aidl b/media/apex/java/android/service/media/IMediaBrowserServiceCallbacks.aidl
similarity index 96%
rename from media/java/android/service/media/IMediaBrowserServiceCallbacks.aidl
rename to media/apex/java/android/service/media/IMediaBrowserServiceCallbacks.aidl
index 8dc480d..507a8f7 100644
--- a/media/java/android/service/media/IMediaBrowserServiceCallbacks.aidl
+++ b/media/apex/java/android/service/media/IMediaBrowserServiceCallbacks.aidl
@@ -2,7 +2,6 @@
 
 package android.service.media;
 
-import android.graphics.Bitmap;
 import android.media.MediaParceledListSlice;
 import android.media.session.MediaSession;
 import android.os.Bundle;
diff --git a/media/java/android/service/media/MediaBrowserService.java b/media/apex/java/android/service/media/MediaBrowserService.java
similarity index 98%
rename from media/java/android/service/media/MediaBrowserService.java
rename to media/apex/java/android/service/media/MediaBrowserService.java
index 2fbc699..d9ef6ae 100644
--- a/media/java/android/service/media/MediaBrowserService.java
+++ b/media/apex/java/android/service/media/MediaBrowserService.java
@@ -98,7 +98,7 @@
 
     /** @hide */
     @Retention(RetentionPolicy.SOURCE)
-    @IntDef(flag=true, value = { RESULT_FLAG_OPTION_NOT_HANDLED,
+    @IntDef(flag = true, value = { RESULT_FLAG_OPTION_NOT_HANDLED,
             RESULT_FLAG_ON_LOAD_ITEM_NOT_IMPLEMENTED })
     private @interface ResultFlags { }
 
@@ -291,7 +291,7 @@
                         final ConnectionRecord connection = mConnections.get(b);
                         if (connection == null) {
                             Log.w(TAG, "addSubscription for callback that isn't registered id="
-                                + id);
+                                    + id);
                             return;
                         }
 
@@ -301,7 +301,8 @@
         }
 
         @Override
-        public void removeSubscriptionDeprecated(String id, IMediaBrowserServiceCallbacks callbacks) {
+        public void removeSubscriptionDeprecated(
+                String id, IMediaBrowserServiceCallbacks callbacks) {
             // do-nothing
         }
 
@@ -487,7 +488,7 @@
             @Override
             public void run() {
                 Iterator<ConnectionRecord> iter = mConnections.values().iterator();
-                while (iter.hasNext()){
+                while (iter.hasNext()) {
                     ConnectionRecord connection = iter.next();
                     try {
                         connection.callbacks.onConnect(connection.root.getRootId(), token,
@@ -607,7 +608,7 @@
         final PackageManager pm = getPackageManager();
         final String[] packages = pm.getPackagesForUid(uid);
         final int N = packages.length;
-        for (int i=0; i<N; i++) {
+        for (int i = 0; i < N; i++) {
             if (packages[i].equals(pkg)) {
                 return true;
             }
@@ -648,7 +649,7 @@
         List<Pair<IBinder, Bundle>> callbackList = connection.subscriptions.get(id);
         if (callbackList != null) {
             Iterator<Pair<IBinder, Bundle>> iter = callbackList.iterator();
-            while (iter.hasNext()){
+            while (iter.hasNext()) {
                 if (token == iter.next().first) {
                     removed = true;
                     iter.remove();
@@ -819,8 +820,8 @@
          */
         public static final String EXTRA_SUGGESTED = "android.service.media.extra.SUGGESTED";
 
-        final private String mRootId;
-        final private Bundle mExtras;
+        private final String mRootId;
+        private final Bundle mExtras;
 
         /**
          * Constructs a browser root.
@@ -829,8 +830,8 @@
          */
         public BrowserRoot(@NonNull String rootId, @Nullable Bundle extras) {
             if (rootId == null) {
-                throw new IllegalArgumentException("The root id in BrowserRoot cannot be null. " +
-                        "Use null for BrowserRoot instead.");
+                throw new IllegalArgumentException("The root id in BrowserRoot cannot be null. "
+                        + "Use null for BrowserRoot instead.");
             }
             mRootId = rootId;
             mExtras = extras;
diff --git a/media/java/android/media/AudioManager.java b/media/java/android/media/AudioManager.java
index b7f042b..f996d38 100644
--- a/media/java/android/media/AudioManager.java
+++ b/media/java/android/media/AudioManager.java
@@ -30,6 +30,7 @@
 import android.app.PendingIntent;
 import android.bluetooth.BluetoothCodecConfig;
 import android.bluetooth.BluetoothDevice;
+import android.bluetooth.BluetoothProfile;
 import android.content.ComponentName;
 import android.content.Context;
 import android.content.Intent;
@@ -3989,33 +3990,11 @@
     }
 
      /**
-     * Indicate A2DP source or sink connection state change.
-     * @param device Bluetooth device connected/disconnected
-     * @param state  new connection state (BluetoothProfile.STATE_xxx)
-     * @param profile profile for the A2DP device
-     * (either {@link android.bluetooth.BluetoothProfile.A2DP} or
-     * {@link android.bluetooth.BluetoothProfile.A2DP_SINK})
-     * @return a delay in ms that the caller should wait before broadcasting
-     * BluetoothA2dp.ACTION_CONNECTION_STATE_CHANGED intent.
-     * {@hide}
-     */
-    public int setBluetoothA2dpDeviceConnectionState(BluetoothDevice device, int state,
-            int profile) {
-        final IAudioService service = getService();
-        int delay = 0;
-        try {
-            delay = service.setBluetoothA2dpDeviceConnectionState(device, state, profile);
-        } catch (RemoteException e) {
-            throw e.rethrowFromSystemServer();
-        }
-        return delay;
-    }
-
-     /**
      * Indicate A2DP source or sink connection state change and eventually suppress
      * the {@link AudioManager.ACTION_AUDIO_BECOMING_NOISY} intent.
      * @param device Bluetooth device connected/disconnected
-     * @param state  new connection state (BluetoothProfile.STATE_xxx)
+     * @param state  new connection state, {@link BluetoothProfile#STATE_CONNECTED}
+     *     or {@link BluetoothProfile#STATE_DISCONNECTED}
      * @param profile profile for the A2DP device
      * @param a2dpVolume New volume for the connecting device. Does nothing if disconnecting.
      * (either {@link android.bluetooth.BluetoothProfile.A2DP} or
@@ -4027,8 +4006,8 @@
      * {@hide}
      */
     public int setBluetoothA2dpDeviceConnectionStateSuppressNoisyIntent(
-                BluetoothDevice device, int state, int profile,
-                boolean suppressNoisyIntent, int a2dpVolume) {
+            BluetoothDevice device, int state,
+            int profile, boolean suppressNoisyIntent, int a2dpVolume) {
         final IAudioService service = getService();
         int delay = 0;
         try {
diff --git a/media/java/android/media/AudioSystem.java b/media/java/android/media/AudioSystem.java
index af016d5..ffa3b24 100644
--- a/media/java/android/media/AudioSystem.java
+++ b/media/java/android/media/AudioSystem.java
@@ -39,6 +39,8 @@
  */
 public class AudioSystem
 {
+    private static final boolean DEBUG_VOLUME = true;
+
     private static final String TAG = "AudioSystem";
     /* These values must be kept in sync with system/audio.h */
     /*
@@ -879,6 +881,15 @@
         }
     }
 
+    /** Wrapper for native methods called from AudioService */
+    public static int setStreamVolumeIndexAS(int stream, int index, int device) {
+        if (DEBUG_VOLUME) {
+            Log.i(TAG, "setStreamVolumeIndex: " + STREAM_NAMES[stream]
+                    + " dev=" + Integer.toHexString(device) + " idx=" + index);
+        }
+        return setStreamVolumeIndex(stream, index, device);
+    }
+
     // usage for AudioRecord.startRecordingSync(), must match AudioSystem::sync_event_t
     public static final int SYNC_EVENT_NONE = 0;
     public static final int SYNC_EVENT_PRESENTATION_COMPLETE = 1;
@@ -906,7 +917,7 @@
     @UnsupportedAppUsage
     public static native int initStreamVolume(int stream, int indexMin, int indexMax);
     @UnsupportedAppUsage
-    public static native int setStreamVolumeIndex(int stream, int index, int device);
+    private static native int setStreamVolumeIndex(int stream, int index, int device);
     public static native int getStreamVolumeIndex(int stream, int device);
     public static native int setMasterVolume(float value);
     public static native float getMasterVolume();
diff --git a/media/java/android/media/AudioTrack.java b/media/java/android/media/AudioTrack.java
index 5e77fdf..3440cde 100644
--- a/media/java/android/media/AudioTrack.java
+++ b/media/java/android/media/AudioTrack.java
@@ -996,6 +996,50 @@
     }
 
     /**
+     * Configures the delay and padding values for the current compressed stream playing
+     * in offload mode.
+     * This can only be used on a track successfully initialized with
+     * {@link AudioTrack.Builder#setOffloadedPlayback(boolean)}. The unit is frames, where a
+     * frame indicates the number of samples per channel, e.g. 100 frames for a stereo compressed
+     * stream corresponds to 200 decoded interleaved PCM samples.
+     * @param delayInFrames number of frames to be ignored at the beginning of the stream. A value
+     *     of 0 indicates no padding is to be applied.
+     * @param paddingInFrames number of frames to be ignored at the end of the stream. A value of 0
+     *     of 0 indicates no delay is to be applied.
+     */
+    public void setOffloadDelayPadding(int delayInFrames, int paddingInFrames) {
+        if (paddingInFrames < 0) {
+            throw new IllegalArgumentException("Illegal negative padding");
+        }
+        if (delayInFrames < 0) {
+            throw new IllegalArgumentException("Illegal negative delay");
+        }
+        if (!mOffloaded) {
+            throw new IllegalStateException("Illegal use of delay/padding on non-offloaded track");
+        }
+        if (mState == STATE_UNINITIALIZED) {
+            throw new IllegalStateException("Uninitialized track");
+        }
+        native_set_delay_padding(delayInFrames, paddingInFrames);
+    }
+
+    /**
+     * Declares that the last write() operation on this track provided the last buffer of this
+     * stream.
+     * After the end of stream, previously set padding and delay values are ignored.
+     * Use this method in the same thread as any write() operation.
+     */
+    public void setOffloadEndOfStream() {
+        if (!mOffloaded) {
+            throw new IllegalStateException("EOS not supported on non-offloaded track");
+        }
+        if (mState == STATE_UNINITIALIZED) {
+            throw new IllegalStateException("Uninitialized track");
+        }
+        native_set_eos();
+    }
+
+    /**
      * Returns whether direct playback of an audio format with the provided attributes is
      * currently supported on the system.
      * <p>Direct playback means that the audio stream is not resampled or downmixed
@@ -3457,6 +3501,9 @@
 
     private native int native_getPortId();
 
+    private native void native_set_delay_padding(int delayInFrames, int paddingInFrames);
+    private native void native_set_eos();
+
     //---------------------------------------------------------
     // Utility methods
     //------------------
diff --git a/media/java/android/media/IAudioService.aidl b/media/java/android/media/IAudioService.aidl
index 14bdab9..f5aeca7 100644
--- a/media/java/android/media/IAudioService.aidl
+++ b/media/java/android/media/IAudioService.aidl
@@ -151,8 +151,6 @@
     void setWiredDeviceConnectionState(int type, int state, String address, String name,
             String caller);
 
-    int setBluetoothA2dpDeviceConnectionState(in BluetoothDevice device, int state, int profile);
-
     void handleBluetoothA2dpDeviceConfigChange(in BluetoothDevice device);
 
     int handleBluetoothA2dpActiveDeviceChange(in BluetoothDevice device,
diff --git a/media/java/android/media/MediaScanner.java b/media/java/android/media/MediaScanner.java
index 4eed12f..33e7d8e 100644
--- a/media/java/android/media/MediaScanner.java
+++ b/media/java/android/media/MediaScanner.java
@@ -335,7 +335,7 @@
     private final Uri mPlaylistsUri;
     @UnsupportedAppUsage
     private final Uri mFilesUri;
-    private final Uri mFilesUriNoNotify;
+    private final Uri mFilesFullUri;
     private final boolean mProcessPlaylists;
     private final boolean mProcessGenres;
     private int mMtpObjectHandle;
@@ -445,7 +445,11 @@
         mVideoUri = Video.Media.getContentUri(volumeName);
         mImagesUri = Images.Media.getContentUri(volumeName);
         mFilesUri = Files.getContentUri(volumeName);
-        mFilesUriNoNotify = mFilesUri.buildUpon().appendQueryParameter("nonotify", "1").build();
+
+        Uri filesFullUri = mFilesUri.buildUpon().appendQueryParameter("nonotify", "1").build();
+        filesFullUri = MediaStore.setIncludePending(filesFullUri);
+        filesFullUri = MediaStore.setIncludeTrashed(filesFullUri);
+        mFilesFullUri = filesFullUri;
 
         if (!volumeName.equals("internal")) {
             // we only support playlists on external media
@@ -1625,7 +1629,7 @@
         try {
             where = Files.FileColumns.DATA + "=?";
             selectionArgs = new String[] { path };
-            c = mMediaProvider.query(mFilesUriNoNotify, FILES_PRESCAN_PROJECTION,
+            c = mMediaProvider.query(mFilesFullUri, FILES_PRESCAN_PROJECTION,
                     where, selectionArgs, null, null);
             if (c != null && c.moveToFirst()) {
                 long rowId = c.getLong(FILES_PRESCAN_ID_COLUMN_INDEX);
diff --git a/media/java/android/media/Session2Token.java b/media/java/android/media/Session2Token.java
index 238cc2b..80494ad 100644
--- a/media/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..fa6e034 100644
--- a/media/java/android/media/session/ISessionManager.aidl
+++ b/media/java/android/media/session/ISessionManager.aidl
@@ -37,6 +37,7 @@
     SessionLink createSession(String packageName, in SessionCallbackLink sessionCb, String tag,
             int userId);
     void notifySession2Created(in Session2Token sessionToken);
+    void notifySession2Destroyed(in Session2Token sessionToken);
     List<ControllerLink> getSessions(in ComponentName compName, int userId);
     List<Session2Token> getSession2Tokens(int userId);
     void dispatchMediaKeyEvent(String packageName, boolean asSystemService, in KeyEvent keyEvent,
diff --git a/media/java/android/media/session/MediaSessionManager.java b/media/java/android/media/session/MediaSessionManager.java
index c64c452..cae4d17 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.
@@ -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/jni/Android.bp b/media/jni/Android.bp
index 48dbf55..852d296 100644
--- a/media/jni/Android.bp
+++ b/media/jni/Android.bp
@@ -101,7 +101,7 @@
         "libhidlbase",
         "libhidlmemory",
 
-        "libmediametrics",  // Used by MediaMetrics. Will be replaced with stable C API.
+        "libmediametrics",
         "libbinder",  // Used by JWakeLock and MediaMetrics.
 
         "libutils",  // Have to use shared lib to make libandroid_runtime behave correctly.
diff --git a/media/jni/android_media_ImageReader.cpp b/media/jni/android_media_ImageReader.cpp
index a45aa90..417a427 100644
--- a/media/jni/android_media_ImageReader.cpp
+++ b/media/jni/android_media_ImageReader.cpp
@@ -405,6 +405,11 @@
                 nativeFormat, consumerUsage);
         return;
     }
+
+    if (consumerUsage & GRALLOC_USAGE_PROTECTED) {
+        gbConsumer->setConsumerIsProtected(true);
+    }
+
     ctx->setBufferConsumer(bufferConsumer);
     bufferConsumer->setName(consumerName);
 
diff --git a/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/packages/MediaCore/Android.bp b/media/packages/MediaCore/Android.bp.bak
similarity index 100%
rename from media/packages/MediaCore/Android.bp
rename to media/packages/MediaCore/Android.bp.bak
diff --git a/native/android/libandroid.map.txt b/native/android/libandroid.map.txt
index 8b45af0..51afbc7 100644
--- a/native/android/libandroid.map.txt
+++ b/native/android/libandroid.map.txt
@@ -231,6 +231,7 @@
     ASurfaceTransaction_setBuffer; # introduced=29
     ASurfaceTransaction_setBufferAlpha; # introduced=29
     ASurfaceTransaction_setBufferTransparency; # introduced=29
+    ASurfaceTransaction_setColor; # introduced=29
     ASurfaceTransaction_setDamageRegion; # introduced=29
     ASurfaceTransaction_setDesiredPresentTime; # introduced=29
     ASurfaceTransaction_setGeometry; # introduced=29
diff --git a/native/android/surface_control.cpp b/native/android/surface_control.cpp
index f0100a9..3156732 100644
--- a/native/android/surface_control.cpp
+++ b/native/android/surface_control.cpp
@@ -44,6 +44,57 @@
     LOG_ALWAYS_FATAL_IF(!static_cast<const Rect&>(name).isValid(), \
                         "invalid arg passed as " #name " argument");
 
+static bool getWideColorSupport(const sp<SurfaceControl>& surfaceControl) {
+    sp<SurfaceComposerClient> client = surfaceControl->getClient();
+    sp<IBinder> display(client->getBuiltInDisplay(ISurfaceComposer::eDisplayIdMain));
+    bool isWideColorDisplay = false;
+    status_t err = client->isWideColorDisplay(display, &isWideColorDisplay);
+    if (err) {
+        ALOGE("unable to get wide color support");
+        return false;
+    }
+    return isWideColorDisplay;
+}
+
+static bool getHdrSupport(const sp<SurfaceControl>& surfaceControl) {
+    sp<SurfaceComposerClient> client = surfaceControl->getClient();
+    sp<IBinder> display(client->getBuiltInDisplay(ISurfaceComposer::eDisplayIdMain));
+
+    HdrCapabilities hdrCapabilities;
+    status_t err = client->getHdrCapabilities(display, &hdrCapabilities);
+    if (err) {
+        ALOGE("unable to get hdr capabilities");
+        return false;
+    }
+
+    return !hdrCapabilities.getSupportedHdrTypes().empty();
+}
+
+static bool isDataSpaceValid(const sp<SurfaceControl>& surfaceControl, ADataSpace dataSpace) {
+    static_assert(static_cast<int>(ADATASPACE_UNKNOWN) == static_cast<int>(HAL_DATASPACE_UNKNOWN));
+    static_assert(static_cast<int>(ADATASPACE_SCRGB_LINEAR) == static_cast<int>(HAL_DATASPACE_V0_SCRGB_LINEAR));
+    static_assert(static_cast<int>(ADATASPACE_SRGB) == static_cast<int>(HAL_DATASPACE_V0_SRGB));
+    static_assert(static_cast<int>(ADATASPACE_SCRGB) == static_cast<int>(HAL_DATASPACE_V0_SCRGB));
+    static_assert(static_cast<int>(ADATASPACE_DISPLAY_P3) == static_cast<int>(HAL_DATASPACE_DISPLAY_P3));
+    static_assert(static_cast<int>(ADATASPACE_BT2020_PQ) == static_cast<int>(HAL_DATASPACE_BT2020_PQ));
+
+    switch (static_cast<android_dataspace_t>(dataSpace)) {
+        case HAL_DATASPACE_UNKNOWN:
+        case HAL_DATASPACE_V0_SRGB:
+            return true;
+        // These data space need wide gamut support.
+        case HAL_DATASPACE_V0_SCRGB_LINEAR:
+        case HAL_DATASPACE_V0_SCRGB:
+        case HAL_DATASPACE_DISPLAY_P3:
+            return getWideColorSupport(surfaceControl);
+        // These data space need HDR support.
+        case HAL_DATASPACE_BT2020_PQ:
+            return getHdrSupport(surfaceControl);
+        default:
+            return false;
+    }
+}
+
 Transaction* ASurfaceTransaction_to_Transaction(ASurfaceTransaction* aSurfaceTransaction) {
     return reinterpret_cast<Transaction*>(aSurfaceTransaction);
 }
@@ -431,3 +482,24 @@
 
     transaction->setHdrMetadata(surfaceControl, hdrMetadata);
 }
+
+void ASurfaceTransaction_setColor(ASurfaceTransaction* aSurfaceTransaction,
+                                  ASurfaceControl* aSurfaceControl,
+                                  float r, float g, float b, float alpha,
+                                  ADataSpace dataspace) {
+    CHECK_NOT_NULL(aSurfaceTransaction);
+    CHECK_NOT_NULL(aSurfaceControl);
+
+    sp<SurfaceControl> surfaceControl = ASurfaceControl_to_SurfaceControl(aSurfaceControl);
+    LOG_ALWAYS_FATAL_IF(!isDataSpaceValid(surfaceControl, dataspace), "invalid dataspace");
+    Transaction* transaction = ASurfaceTransaction_to_Transaction(aSurfaceTransaction);
+
+    half3 color;
+    color.r = r;
+    color.g = g;
+    color.b = b;
+
+    transaction->setColor(surfaceControl, color)
+                .setColorAlpha(surfaceControl, alpha)
+                .setColorDataspace(surfaceControl, static_cast<ui::Dataspace>(dataspace));
+}
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/Android.bp b/packages/CarSystemUI/Android.bp
index 9b6ad38..9064ebe 100644
--- a/packages/CarSystemUI/Android.bp
+++ b/packages/CarSystemUI/Android.bp
@@ -81,5 +81,5 @@
         "com.android.keyguard",
     ],
 
-    annotation_processors: ["dagger2-compiler-2.19"],
+    plugins: ["dagger2-compiler-2.19"],
 }
diff --git a/packages/CarSystemUI/res/layout/car_navigation_bar.xml b/packages/CarSystemUI/res/layout/car_navigation_bar.xml
index 052566d..e591ea9 100644
--- a/packages/CarSystemUI/res/layout/car_navigation_bar.xml
+++ b/packages/CarSystemUI/res/layout/car_navigation_bar.xml
@@ -65,8 +65,9 @@
         <com.android.systemui.statusbar.car.CarFacetButton
             android:id="@+id/music_nav"
             style="@style/NavigationBarButton"
+            systemui:categories="android.intent.category.APP_MUSIC"
             systemui:icon="@drawable/car_ic_music"
-            systemui:intent="intent:#Intent;component=com.android.car.media/.MediaActivity;launchFlags=0x14000000;end"
+            systemui:intent="intent:#Intent;action=android.car.intent.action.MEDIA_TEMPLATE;launchFlags=0x10000000;end"
             systemui:packages="com.android.car.media"
             systemui:selectedIcon="@drawable/car_ic_music_selected"
             systemui:useMoreIcon="false"
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/AssistantSettings.java b/packages/ExtServices/src/android/ext/services/notification/AssistantSettings.java
index 39a1676..406b44d 100644
--- a/packages/ExtServices/src/android/ext/services/notification/AssistantSettings.java
+++ b/packages/ExtServices/src/android/ext/services/notification/AssistantSettings.java
@@ -20,8 +20,11 @@
 import android.database.ContentObserver;
 import android.net.Uri;
 import android.os.Handler;
+import android.provider.DeviceConfig;
 import android.provider.Settings;
+import android.text.TextUtils;
 import android.util.KeyValueListParser;
+import android.util.Log;
 
 import com.android.internal.annotations.VisibleForTesting;
 
@@ -29,6 +32,7 @@
  * Observes the settings for {@link Assistant}.
  */
 final class AssistantSettings extends ContentObserver {
+    private static final String LOG_TAG = "AssistantSettings";
     public static Factory FACTORY = AssistantSettings::createAndRegister;
     private static final boolean DEFAULT_GENERATE_REPLIES = true;
     private static final boolean DEFAULT_GENERATE_ACTIONS = true;
@@ -39,19 +43,33 @@
     private static final Uri DISMISS_TO_VIEW_RATIO_LIMIT_URI =
             Settings.Global.getUriFor(
                     Settings.Global.BLOCKING_HELPER_DISMISS_TO_VIEW_RATIO_LIMIT);
-    private static final Uri SMART_SUGGESTIONS_IN_NOTIFICATIONS_FLAGS_URI =
-            Settings.Global.getUriFor(
-                    Settings.Global.SMART_SUGGESTIONS_IN_NOTIFICATIONS_FLAGS);
     private static final Uri NOTIFICATION_NEW_INTERRUPTION_MODEL_URI =
             Settings.Secure.getUriFor(Settings.Secure.NOTIFICATION_NEW_INTERRUPTION_MODEL);
 
-    private static final String KEY_GENERATE_REPLIES = "generate_replies";
-    private static final String KEY_GENERATE_ACTIONS = "generate_actions";
+    /**
+     * Flag determining whether the Notification Assistant should generate replies for
+     * notifications.
+     * <p>
+     * This flag belongs to the namespace: {@link DeviceConfig#NAMESPACE_NOTIFICATION_ASSISTANT}.
+     */
+    @VisibleForTesting
+    static final String KEY_GENERATE_REPLIES = "notification_assistant_generate_replies";
+
+    /**
+     * Flag determining whether the Notification Assistant should generate contextual actions in
+     * notifications.
+     * <p>
+     * This flag belongs to the namespace: {@link DeviceConfig#NAMESPACE_NOTIFICATION_ASSISTANT}.
+     */
+    @VisibleForTesting
+    static final String KEY_GENERATE_ACTIONS = "notification_assistant_generate_actions";
 
     private final KeyValueListParser mParser = new KeyValueListParser(',');
     private final ContentResolver mResolver;
     private final int mUserId;
 
+    private final Handler mHandler;
+
     @VisibleForTesting
     protected final Runnable mOnUpdateRunnable;
 
@@ -65,6 +83,7 @@
     private AssistantSettings(Handler handler, ContentResolver resolver, int userId,
             Runnable onUpdateRunnable) {
         super(handler);
+        mHandler = handler;
         mResolver = resolver;
         mUserId = userId;
         mOnUpdateRunnable = onUpdateRunnable;
@@ -75,6 +94,7 @@
         AssistantSettings assistantSettings =
                 new AssistantSettings(handler, resolver, userId, onUpdateRunnable);
         assistantSettings.register();
+        assistantSettings.registerDeviceConfigs();
         return assistantSettings;
     }
 
@@ -91,13 +111,62 @@
         mResolver.registerContentObserver(
                 DISMISS_TO_VIEW_RATIO_LIMIT_URI, false, this, mUserId);
         mResolver.registerContentObserver(STREAK_LIMIT_URI, false, this, mUserId);
-        mResolver.registerContentObserver(
-                SMART_SUGGESTIONS_IN_NOTIFICATIONS_FLAGS_URI, false, this, mUserId);
 
         // Update all uris on creation.
         update(null);
     }
 
+    private void registerDeviceConfigs() {
+        DeviceConfig.addOnPropertyChangedListener(
+                DeviceConfig.NAMESPACE_NOTIFICATION_ASSISTANT,
+                this::postToHandler,
+                this::onDeviceConfigPropertyChanged);
+
+        // Update the fields in this class from the current state of the device config.
+        updateFromDeviceConfigFlags();
+    }
+
+    private void postToHandler(Runnable r) {
+        this.mHandler.post(r);
+    }
+
+    @VisibleForTesting
+    void onDeviceConfigPropertyChanged(String namespace, String name, String value) {
+        if (!DeviceConfig.NAMESPACE_NOTIFICATION_ASSISTANT.equals(namespace)) {
+            Log.e(LOG_TAG, "Received update from DeviceConfig for unrelated namespace: "
+                    + namespace + " " + name + "=" + value);
+            return;
+        }
+
+        updateFromDeviceConfigFlags();
+    }
+
+    private void updateFromDeviceConfigFlags() {
+        String generateRepliesFlag = DeviceConfig.getProperty(
+                DeviceConfig.NAMESPACE_NOTIFICATION_ASSISTANT,
+                KEY_GENERATE_REPLIES);
+        if (TextUtils.isEmpty(generateRepliesFlag)) {
+            mGenerateReplies = DEFAULT_GENERATE_REPLIES;
+        } else {
+            // parseBoolean returns false for everything that isn't 'true' so there's no need to
+            // sanitise the flag string here.
+            mGenerateReplies = Boolean.parseBoolean(generateRepliesFlag);
+        }
+
+        String generateActionsFlag = DeviceConfig.getProperty(
+                DeviceConfig.NAMESPACE_NOTIFICATION_ASSISTANT,
+                KEY_GENERATE_ACTIONS);
+        if (TextUtils.isEmpty(generateActionsFlag)) {
+            mGenerateActions = DEFAULT_GENERATE_ACTIONS;
+        } else {
+            // parseBoolean returns false for everything that isn't 'true' so there's no need to
+            // sanitise the flag string here.
+            mGenerateActions = Boolean.parseBoolean(generateActionsFlag);
+        }
+
+        mOnUpdateRunnable.run();
+    }
+
     @Override
     public void onChange(boolean selfChange, Uri uri) {
         update(uri);
@@ -114,15 +183,6 @@
                     mResolver, Settings.Global.BLOCKING_HELPER_STREAK_LIMIT,
                     ChannelImpressions.DEFAULT_STREAK_LIMIT);
         }
-        if (uri == null || SMART_SUGGESTIONS_IN_NOTIFICATIONS_FLAGS_URI.equals(uri)) {
-            mParser.setString(
-                    Settings.Global.getString(mResolver,
-                            Settings.Global.SMART_SUGGESTIONS_IN_NOTIFICATIONS_FLAGS));
-            mGenerateReplies =
-                    mParser.getBoolean(KEY_GENERATE_REPLIES, DEFAULT_GENERATE_REPLIES);
-            mGenerateActions =
-                    mParser.getBoolean(KEY_GENERATE_ACTIONS, DEFAULT_GENERATE_ACTIONS);
-        }
         if (uri == null || NOTIFICATION_NEW_INTERRUPTION_MODEL_URI.equals(uri)) {
             int mNewInterruptionModelInt = Settings.Secure.getInt(
                     mResolver, Settings.Secure.NOTIFICATION_NEW_INTERRUPTION_MODEL,
diff --git a/packages/ExtServices/src/android/ext/services/notification/SmartActionsHelper.java b/packages/ExtServices/src/android/ext/services/notification/SmartActionsHelper.java
index 0d528e7..5acf4fb 100644
--- a/packages/ExtServices/src/android/ext/services/notification/SmartActionsHelper.java
+++ b/packages/ExtServices/src/android/ext/services/notification/SmartActionsHelper.java
@@ -202,7 +202,7 @@
         }
         TextClassifierEvent textClassifierEvent =
                 createTextClassifierEventBuilder(TextClassifierEvent.TYPE_SMART_ACTION, resultId)
-                        .setEntityType(ConversationAction.TYPE_TEXT_REPLY)
+                        .setEntityTypes(ConversationAction.TYPE_TEXT_REPLY)
                         .build();
         mTextClassifier.onTextClassifierEvent(textClassifierEvent);
     }
@@ -225,7 +225,7 @@
         }
         TextClassifierEvent textClassifierEvent =
                 createTextClassifierEventBuilder(TextClassifierEvent.TYPE_SMART_ACTION, resultId)
-                        .setEntityType(actionType)
+                        .setEntityTypes(actionType)
                         .build();
         mTextClassifier.onTextClassifierEvent(textClassifierEvent);
     }
@@ -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/AssistantSettingsTest.java b/packages/ExtServices/tests/src/android/ext/services/notification/AssistantSettingsTest.java
index fd23f2b..51b723d 100644
--- a/packages/ExtServices/tests/src/android/ext/services/notification/AssistantSettingsTest.java
+++ b/packages/ExtServices/tests/src/android/ext/services/notification/AssistantSettingsTest.java
@@ -26,6 +26,7 @@
 import android.content.ContentResolver;
 import android.os.Handler;
 import android.os.Looper;
+import android.provider.DeviceConfig;
 import android.provider.Settings;
 import android.support.test.InstrumentationRegistry;
 import android.support.test.runner.AndroidJUnit4;
@@ -62,9 +63,6 @@
         Settings.Global.putFloat(mResolver,
                 Settings.Global.BLOCKING_HELPER_DISMISS_TO_VIEW_RATIO_LIMIT, 0.8f);
         Settings.Global.putInt(mResolver, Settings.Global.BLOCKING_HELPER_STREAK_LIMIT, 2);
-        Settings.Global.putString(mResolver,
-                Settings.Global.SMART_SUGGESTIONS_IN_NOTIFICATIONS_FLAGS,
-                "generate_replies=true,generate_actions=true");
         Settings.Secure.putInt(mResolver, Settings.Secure.NOTIFICATION_NEW_INTERRUPTION_MODEL, 1);
 
         mAssistantSettings = AssistantSettings.createForTesting(
@@ -73,56 +71,78 @@
 
     @Test
     public void testGenerateRepliesDisabled() {
-        Settings.Global.putString(mResolver,
-                Settings.Global.SMART_SUGGESTIONS_IN_NOTIFICATIONS_FLAGS,
-                "generate_replies=false");
-
-        // Notify for the settings values we updated.
-        mAssistantSettings.onChange(false,
-                Settings.Global.getUriFor(
-                        Settings.Global.SMART_SUGGESTIONS_IN_NOTIFICATIONS_FLAGS));
-
+        mAssistantSettings.onDeviceConfigPropertyChanged(
+                DeviceConfig.NAMESPACE_NOTIFICATION_ASSISTANT,
+                AssistantSettings.KEY_GENERATE_REPLIES,
+                "false");
 
         assertFalse(mAssistantSettings.mGenerateReplies);
     }
 
     @Test
     public void testGenerateRepliesEnabled() {
-        Settings.Global.putString(mResolver,
-                Settings.Global.SMART_SUGGESTIONS_IN_NOTIFICATIONS_FLAGS, "generate_replies=true");
+        mAssistantSettings.onDeviceConfigPropertyChanged(
+                DeviceConfig.NAMESPACE_NOTIFICATION_ASSISTANT,
+                AssistantSettings.KEY_GENERATE_REPLIES,
+                "true");
 
-        // Notify for the settings values we updated.
-        mAssistantSettings.onChange(false,
-                Settings.Global.getUriFor(
-                        Settings.Global.SMART_SUGGESTIONS_IN_NOTIFICATIONS_FLAGS));
+        assertTrue(mAssistantSettings.mGenerateReplies);
+    }
 
+    @Test
+    public void testGenerateRepliesEmptyFlag() {
+        mAssistantSettings.onDeviceConfigPropertyChanged(
+                DeviceConfig.NAMESPACE_NOTIFICATION_ASSISTANT,
+                AssistantSettings.KEY_GENERATE_REPLIES,
+                "false");
+
+        assertFalse(mAssistantSettings.mGenerateReplies);
+
+        mAssistantSettings.onDeviceConfigPropertyChanged(
+                DeviceConfig.NAMESPACE_NOTIFICATION_ASSISTANT,
+                AssistantSettings.KEY_GENERATE_REPLIES,
+                "");
+
+        // Go back to the default value.
         assertTrue(mAssistantSettings.mGenerateReplies);
     }
 
     @Test
     public void testGenerateActionsDisabled() {
-        Settings.Global.putString(mResolver,
-                Settings.Global.SMART_SUGGESTIONS_IN_NOTIFICATIONS_FLAGS, "generate_actions=false");
+        mAssistantSettings.onDeviceConfigPropertyChanged(
+                DeviceConfig.NAMESPACE_NOTIFICATION_ASSISTANT,
+                AssistantSettings.KEY_GENERATE_ACTIONS,
+                "false");
 
-        // Notify for the settings values we updated.
-        mAssistantSettings.onChange(false,
-                Settings.Global.getUriFor(
-                        Settings.Global.SMART_SUGGESTIONS_IN_NOTIFICATIONS_FLAGS));
-
-        assertTrue(mAssistantSettings.mGenerateReplies);
+        assertFalse(mAssistantSettings.mGenerateActions);
     }
 
     @Test
     public void testGenerateActionsEnabled() {
-        Settings.Global.putString(mResolver,
-                Settings.Global.SMART_SUGGESTIONS_IN_NOTIFICATIONS_FLAGS, "generate_actions=true");
+        mAssistantSettings.onDeviceConfigPropertyChanged(
+                DeviceConfig.NAMESPACE_NOTIFICATION_ASSISTANT,
+                AssistantSettings.KEY_GENERATE_ACTIONS,
+                "true");
 
-        // Notify for the settings values we updated.
-        mAssistantSettings.onChange(false,
-                Settings.Global.getUriFor(
-                        Settings.Global.SMART_SUGGESTIONS_IN_NOTIFICATIONS_FLAGS));
+        assertTrue(mAssistantSettings.mGenerateActions);
+    }
 
-        assertTrue(mAssistantSettings.mGenerateReplies);
+    @Test
+    public void testGenerateActionsEmptyFlag() {
+        mAssistantSettings.onDeviceConfigPropertyChanged(
+                DeviceConfig.NAMESPACE_NOTIFICATION_ASSISTANT,
+                AssistantSettings.KEY_GENERATE_ACTIONS,
+                "false");
+
+        assertFalse(mAssistantSettings.mGenerateActions);
+
+        mAssistantSettings.onDeviceConfigPropertyChanged(
+                DeviceConfig.NAMESPACE_NOTIFICATION_ASSISTANT,
+                AssistantSettings.KEY_GENERATE_ACTIONS,
+                "");
+
+        // Go back to the default value.
+        assertTrue(mAssistantSettings.mGenerateActions);
     }
 
     @Test
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/DhcpServer.java b/packages/NetworkStack/src/android/net/dhcp/DhcpServer.java
index 7b112df..beabd3e 100644
--- a/packages/NetworkStack/src/android/net/dhcp/DhcpServer.java
+++ b/packages/NetworkStack/src/android/net/dhcp/DhcpServer.java
@@ -16,8 +16,6 @@
 
 package android.net.dhcp;
 
-import static android.net.NetworkUtils.getBroadcastAddress;
-import static android.net.NetworkUtils.getPrefixMaskAsInet4Address;
 import static android.net.TrafficStats.TAG_SYSTEM_DHCP_SERVER;
 import static android.net.dhcp.DhcpPacket.DHCP_CLIENT;
 import static android.net.dhcp.DhcpPacket.DHCP_HOST_NAME;
@@ -25,6 +23,8 @@
 import static android.net.dhcp.DhcpPacket.ENCAP_BOOTP;
 import static android.net.dhcp.IDhcpServer.STATUS_INVALID_ARGUMENT;
 import static android.net.dhcp.IDhcpServer.STATUS_SUCCESS;
+import static android.net.shared.Inet4AddressUtils.getBroadcastAddress;
+import static android.net.shared.Inet4AddressUtils.getPrefixMaskAsInet4Address;
 import static android.system.OsConstants.AF_INET;
 import static android.system.OsConstants.IPPROTO_UDP;
 import static android.system.OsConstants.SOCK_DGRAM;
@@ -33,6 +33,8 @@
 import static android.system.OsConstants.SO_REUSEADDR;
 
 import static com.android.server.util.NetworkStackConstants.INFINITE_LEASE;
+import static com.android.server.util.NetworkStackConstants.IPV4_ADDR_ALL;
+import static com.android.server.util.NetworkStackConstants.IPV4_ADDR_ANY;
 import static com.android.server.util.PermissionUtil.checkNetworkStackCallingPermission;
 
 import static java.lang.Integer.toUnsignedLong;
@@ -434,7 +436,7 @@
         if (!isEmpty(request.mRelayIp)) {
             return request.mRelayIp;
         } else if (broadcastFlag) {
-            return (Inet4Address) Inet4Address.ALL;
+            return IPV4_ADDR_ALL;
         } else if (!isEmpty(request.mClientIp)) {
             return request.mClientIp;
         } else {
@@ -517,7 +519,7 @@
                 request.mRelayIp, request.mClientMac, true /* broadcast */, message);
 
         final Inet4Address dst = isEmpty(request.mRelayIp)
-                ? (Inet4Address) Inet4Address.ALL
+                ? IPV4_ADDR_ALL
                 : request.mRelayIp;
         return transmitPacket(nakPacket, DhcpNakPacket.class.getSimpleName(), dst);
     }
@@ -598,7 +600,7 @@
     }
 
     private static boolean isEmpty(@Nullable Inet4Address address) {
-        return address == null || Inet4Address.ANY.equals(address);
+        return address == null || IPV4_ADDR_ANY.equals(address);
     }
 
     private class PacketListener extends DhcpPacketListener {
@@ -632,7 +634,7 @@
                 SocketUtils.bindSocketToInterface(mSocket, mIfName);
                 Os.setsockoptInt(mSocket, SOL_SOCKET, SO_REUSEADDR, 1);
                 Os.setsockoptInt(mSocket, SOL_SOCKET, SO_BROADCAST, 1);
-                Os.bind(mSocket, Inet4Address.ANY, DHCP_SERVER);
+                Os.bind(mSocket, IPV4_ADDR_ANY, DHCP_SERVER);
 
                 return mSocket;
             } catch (IOException | ErrnoException e) {
diff --git a/packages/NetworkStack/src/android/net/dhcp/DhcpServingParams.java b/packages/NetworkStack/src/android/net/dhcp/DhcpServingParams.java
index 868f3be..31ce95b 100644
--- a/packages/NetworkStack/src/android/net/dhcp/DhcpServingParams.java
+++ b/packages/NetworkStack/src/android/net/dhcp/DhcpServingParams.java
@@ -16,8 +16,8 @@
 
 package android.net.dhcp;
 
-import static android.net.NetworkUtils.getPrefixMaskAsInet4Address;
-import static android.net.NetworkUtils.intToInet4AddressHTH;
+import static android.net.shared.Inet4AddressUtils.getPrefixMaskAsInet4Address;
+import static android.net.shared.Inet4AddressUtils.intToInet4AddressHTH;
 
 import static com.android.server.util.NetworkStackConstants.INFINITE_LEASE;
 import static com.android.server.util.NetworkStackConstants.IPV4_MAX_MTU;
@@ -29,7 +29,7 @@
 import android.annotation.Nullable;
 import android.net.IpPrefix;
 import android.net.LinkAddress;
-import android.net.NetworkUtils;
+import android.net.shared.Inet4AddressUtils;
 import android.util.ArraySet;
 
 import java.net.Inet4Address;
@@ -164,7 +164,8 @@
      */
     @NonNull
     public Inet4Address getBroadcastAddress() {
-        return NetworkUtils.getBroadcastAddress(getServerInet4Addr(), serverAddr.getPrefixLength());
+        return Inet4AddressUtils.getBroadcastAddress(
+                getServerInet4Addr(), serverAddr.getPrefixLength());
     }
 
     /**
diff --git a/packages/NetworkStack/src/android/net/ip/IpClient.java b/packages/NetworkStack/src/android/net/ip/IpClient.java
index ad7f85d..f20e0163 100644
--- a/packages/NetworkStack/src/android/net/ip/IpClient.java
+++ b/packages/NetworkStack/src/android/net/ip/IpClient.java
@@ -29,7 +29,6 @@
 import android.net.IpPrefix;
 import android.net.LinkAddress;
 import android.net.LinkProperties;
-import android.net.Network;
 import android.net.ProvisioningConfigurationParcelable;
 import android.net.ProxyInfo;
 import android.net.ProxyInfoParcelable;
@@ -1000,7 +999,9 @@
         // mDhcpResults is never shared with any other owner so we don't have
         // to worry about concurrent modification.
         if (mDhcpResults != null) {
-            for (RouteInfo route : mDhcpResults.getRoutes(mInterfaceName)) {
+            final List<RouteInfo> routes =
+                    mDhcpResults.toStaticIpConfiguration().getRoutes(mInterfaceName);
+            for (RouteInfo route : routes) {
                 newLp.addRoute(route);
             }
             addAllReachableDnsServers(newLp, mDhcpResults.dnsServers);
diff --git a/packages/NetworkStack/src/android/net/ip/IpNeighborMonitor.java b/packages/NetworkStack/src/android/net/ip/IpNeighborMonitor.java
index eb993a4..2e6ff24 100644
--- a/packages/NetworkStack/src/android/net/ip/IpNeighborMonitor.java
+++ b/packages/NetworkStack/src/android/net/ip/IpNeighborMonitor.java
@@ -36,8 +36,6 @@
 import android.system.OsConstants;
 import android.util.Log;
 
-import com.android.internal.util.BitUtils;
-
 import libcore.io.IoUtils;
 
 import java.io.FileDescriptor;
@@ -186,7 +184,7 @@
 
             final int srcPortId = nlMsg.getHeader().nlmsg_pid;
             if (srcPortId !=  0) {
-                mLog.e("non-kernel source portId: " + BitUtils.uint32(srcPortId));
+                mLog.e("non-kernel source portId: " + Integer.toUnsignedLong(srcPortId));
                 break;
             }
 
diff --git a/packages/NetworkStack/src/android/net/ip/IpReachabilityMonitor.java b/packages/NetworkStack/src/android/net/ip/IpReachabilityMonitor.java
index 761db68..76a0338 100644
--- a/packages/NetworkStack/src/android/net/ip/IpReachabilityMonitor.java
+++ b/packages/NetworkStack/src/android/net/ip/IpReachabilityMonitor.java
@@ -31,15 +31,15 @@
 import android.net.netlink.StructNdMsg;
 import android.net.util.InterfaceParams;
 import android.net.util.SharedLog;
+import android.os.ConditionVariable;
 import android.os.Handler;
+import android.os.Looper;
 import android.os.PowerManager;
 import android.os.PowerManager.WakeLock;
 import android.os.SystemClock;
 import android.util.Log;
 
 import com.android.internal.annotations.VisibleForTesting;
-import com.android.internal.util.DumpUtils;
-import com.android.internal.util.DumpUtils.Dump;
 
 import java.io.PrintWriter;
 import java.net.Inet6Address;
@@ -215,15 +215,20 @@
     }
 
     public void dump(PrintWriter pw) {
-        DumpUtils.dumpAsync(
-                mIpNeighborMonitor.getHandler(),
-                new Dump() {
-                    @Override
-                    public void dump(PrintWriter pw, String prefix) {
-                        pw.println(describeWatchList("\n"));
-                    }
-                },
-                pw, "", 1000);
+        if (Looper.myLooper() == mIpNeighborMonitor.getHandler().getLooper()) {
+            pw.println(describeWatchList("\n"));
+            return;
+        }
+
+        final ConditionVariable cv = new ConditionVariable(false);
+        mIpNeighborMonitor.getHandler().post(() -> {
+            pw.println(describeWatchList("\n"));
+            cv.open();
+        });
+
+        if (!cv.block(1000)) {
+            pw.println("Timed out waiting for IpReachabilityMonitor dump");
+        }
     }
 
     private String describeWatchList() { return describeWatchList(" "); }
diff --git a/services/net/java/android/net/dhcp/DhcpClient.java b/packages/NetworkStack/src/android/net/util/NetworkStackUtils.java
similarity index 62%
rename from services/net/java/android/net/dhcp/DhcpClient.java
rename 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/com/android/server/NetworkObserver.java b/packages/NetworkStack/src/com/android/server/NetworkObserver.java
new file mode 100644
index 0000000..d3b40a6
--- /dev/null
+++ b/packages/NetworkStack/src/com/android/server/NetworkObserver.java
@@ -0,0 +1,87 @@
+/*
+ * Copyright (C) 2019 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server;
+
+import android.net.LinkAddress;
+
+/**
+ * Observer for network events, to use with {@link NetworkObserverRegistry}.
+ */
+public interface NetworkObserver {
+
+    /**
+     * @see android.net.INetdUnsolicitedEventListener#onInterfaceChanged(java.lang.String, boolean)
+     */
+    default void onInterfaceChanged(String ifName, boolean up) {}
+
+    /**
+     * @see android.net.INetdUnsolicitedEventListener#onInterfaceRemoved(String)
+     */
+    default void onInterfaceRemoved(String ifName) {}
+
+    /**
+     * @see android.net.INetdUnsolicitedEventListener
+     *          #onInterfaceAddressUpdated(String, String, int, int)
+     */
+    default void onInterfaceAddressUpdated(LinkAddress address, String ifName) {}
+
+    /**
+     * @see android.net.INetdUnsolicitedEventListener
+     *          #onInterfaceAddressRemoved(String, String, int, int)
+     */
+    default void onInterfaceAddressRemoved(LinkAddress address, String ifName) {}
+
+    /**
+     * @see android.net.INetdUnsolicitedEventListener#onInterfaceLinkStateChanged(String, boolean)
+     */
+    default void onInterfaceLinkStateChanged(String ifName, boolean up) {}
+
+    /**
+     * @see android.net.INetdUnsolicitedEventListener#onInterfaceAdded(String)
+     */
+    default void onInterfaceAdded(String ifName) {}
+
+    /**
+     * @see android.net.INetdUnsolicitedEventListener
+     *          #onInterfaceClassActivityChanged(boolean, int, long, int)
+     */
+    default void onInterfaceClassActivityChanged(
+            boolean isActive, int label, long timestamp, int uid) {}
+
+    /**
+     * @see android.net.INetdUnsolicitedEventListener#onQuotaLimitReached(String, String)
+     */
+    default void onQuotaLimitReached(String alertName, String ifName) {}
+
+    /**
+     * @see android.net.INetdUnsolicitedEventListener
+     *          #onInterfaceDnsServerInfo(String, long, String[])
+     */
+    default void onInterfaceDnsServerInfo(String ifName, long lifetime, String[] servers) {}
+
+    /**
+     * @see android.net.INetdUnsolicitedEventListener
+     *          #onRouteChanged(boolean, String, String, String)
+     */
+    default void onRouteUpdated(String route, String gateway, String ifName) {}
+
+    /**
+     * @see android.net.INetdUnsolicitedEventListener
+     *          #onRouteChanged(boolean, String, String, String)
+     */
+    default void onRouteRemoved(String route, String gateway, String ifName) {}
+}
diff --git a/packages/NetworkStack/src/com/android/server/NetworkObserverRegistry.java b/packages/NetworkStack/src/com/android/server/NetworkObserverRegistry.java
new file mode 100644
index 0000000..14e6c5f
--- /dev/null
+++ b/packages/NetworkStack/src/com/android/server/NetworkObserverRegistry.java
@@ -0,0 +1,150 @@
+/*
+ * Copyright (C) 2019 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.android.server;
+
+import android.annotation.NonNull;
+import android.net.INetd;
+import android.net.INetdUnsolicitedEventListener;
+import android.net.LinkAddress;
+import android.os.Handler;
+import android.os.RemoteException;
+
+import java.util.Map;
+import java.util.concurrent.ConcurrentHashMap;
+
+/**
+ * A class for reporting network events to clients.
+ *
+ * Implements INetdUnsolicitedEventListener and registers with netd, and relays those events to
+ * all INetworkManagementEventObserver objects that have registered with it.
+ */
+public class NetworkObserverRegistry extends INetdUnsolicitedEventListener.Stub {
+
+    /**
+     * Constructs a new NetworkObserverRegistry.
+     *
+     * <p>Only one registry should be used per process since netd will silently ignore multiple
+     * registrations from the same process.
+     */
+    NetworkObserverRegistry() {}
+
+    /**
+     * Start listening for Netd events.
+     *
+     * <p>This should be called before allowing any observer to be registered.
+     */
+    void register(@NonNull INetd netd) throws RemoteException {
+        netd.registerUnsolicitedEventListener(this);
+    }
+
+    private final ConcurrentHashMap<NetworkObserver, Handler> mObservers =
+            new ConcurrentHashMap<>();
+
+    /**
+     * Registers the specified observer and start sending callbacks to it.
+     * This method may be called on any thread.
+     */
+    public void registerObserver(@NonNull NetworkObserver observer, @NonNull Handler handler) {
+        mObservers.put(observer, handler);
+    }
+
+    /**
+     * Unregisters the specified observer and stop sending callbacks to it.
+     * This method may be called on any thread.
+     */
+    public void unregisterObserver(@NonNull NetworkObserver observer) {
+        mObservers.remove(observer);
+    }
+
+    @FunctionalInterface
+    private interface NetworkObserverEventCallback {
+        void sendCallback(NetworkObserver o);
+    }
+
+    private void invokeForAllObservers(@NonNull final NetworkObserverEventCallback callback) {
+        // ConcurrentHashMap#entrySet is weakly consistent: observers that were in the map before
+        // creation will be processed, those added during traversal may or may not.
+        for (Map.Entry<NetworkObserver, Handler> entry : mObservers.entrySet()) {
+            final NetworkObserver observer = entry.getKey();
+            entry.getValue().post(() -> callback.sendCallback(observer));
+        }
+    }
+
+    @Override
+    public void onInterfaceClassActivityChanged(boolean isActive,
+            int label, long timestamp, int uid) {
+        invokeForAllObservers(o -> o.onInterfaceClassActivityChanged(
+                isActive, label, timestamp, uid));
+    }
+
+    /**
+     * Notify our observers of a limit reached.
+     */
+    @Override
+    public void onQuotaLimitReached(String alertName, String ifName) {
+        invokeForAllObservers(o -> o.onQuotaLimitReached(alertName, ifName));
+    }
+
+    @Override
+    public void onInterfaceDnsServerInfo(String ifName, long lifetime, String[] servers) {
+        invokeForAllObservers(o -> o.onInterfaceDnsServerInfo(ifName, lifetime, servers));
+    }
+
+    @Override
+    public void onInterfaceAddressUpdated(String addr, String ifName, int flags, int scope) {
+        final LinkAddress address = new LinkAddress(addr, flags, scope);
+        invokeForAllObservers(o -> o.onInterfaceAddressUpdated(address, ifName));
+    }
+
+    @Override
+    public void onInterfaceAddressRemoved(String addr,
+            String ifName, int flags, int scope) {
+        final LinkAddress address = new LinkAddress(addr, flags, scope);
+        invokeForAllObservers(o -> o.onInterfaceAddressRemoved(address, ifName));
+    }
+
+    @Override
+    public void onInterfaceAdded(String ifName) {
+        invokeForAllObservers(o -> o.onInterfaceAdded(ifName));
+    }
+
+    @Override
+    public void onInterfaceRemoved(String ifName) {
+        invokeForAllObservers(o -> o.onInterfaceRemoved(ifName));
+    }
+
+    @Override
+    public void onInterfaceChanged(String ifName, boolean up) {
+        invokeForAllObservers(o -> o.onInterfaceChanged(ifName, up));
+    }
+
+    @Override
+    public void onInterfaceLinkStateChanged(String ifName, boolean up) {
+        invokeForAllObservers(o -> o.onInterfaceLinkStateChanged(ifName, up));
+    }
+
+    @Override
+    public void onRouteChanged(boolean updated, String route, String gateway, String ifName) {
+        if (updated) {
+            invokeForAllObservers(o -> o.onRouteUpdated(route, gateway, ifName));
+        } else {
+            invokeForAllObservers(o -> o.onRouteRemoved(route, gateway, ifName));
+        }
+    }
+
+    @Override
+    public void onStrictCleartextDetected(int uid, String hex) {}
+}
diff --git a/packages/NetworkStack/src/com/android/server/NetworkStackService.java b/packages/NetworkStack/src/com/android/server/NetworkStackService.java
index 4080ddf..631ee45 100644
--- a/packages/NetworkStack/src/com/android/server/NetworkStackService.java
+++ b/packages/NetworkStack/src/com/android/server/NetworkStackService.java
@@ -29,6 +29,7 @@
 import android.content.Context;
 import android.content.Intent;
 import android.net.ConnectivityManager;
+import android.net.INetd;
 import android.net.INetworkMonitor;
 import android.net.INetworkMonitorCallbacks;
 import android.net.INetworkStackConnector;
@@ -65,6 +66,7 @@
  */
 public class NetworkStackService extends Service {
     private static final String TAG = NetworkStackService.class.getSimpleName();
+    private static NetworkStackConnector sConnector;
 
     /**
      * Create a binder connector for the system server to communicate with the network stack.
@@ -72,8 +74,11 @@
      * <p>On platforms where the network stack runs in the system server process, this method may
      * be called directly instead of obtaining the connector by binding to the service.
      */
-    public static IBinder makeConnector(Context context) {
-        return new NetworkStackConnector(context);
+    public static synchronized IBinder makeConnector(Context context) {
+        if (sConnector == null) {
+            sConnector = new NetworkStackConnector(context);
+        }
+        return sConnector;
     }
 
     @NonNull
@@ -85,6 +90,8 @@
     private static class NetworkStackConnector extends INetworkStackConnector.Stub {
         private static final int NUM_VALIDATION_LOG_LINES = 20;
         private final Context mContext;
+        private final INetd mNetd;
+        private final NetworkObserverRegistry mObserverRegistry;
         private final ConnectivityManager mCm;
         @GuardedBy("mIpClients")
         private final ArrayList<WeakReference<IpClient>> mIpClients = new ArrayList<>();
@@ -106,7 +113,11 @@
 
         NetworkStackConnector(Context context) {
             mContext = context;
+            mNetd = (INetd) context.getSystemService(Context.NETD_SERVICE);
+            mObserverRegistry = new NetworkObserverRegistry();
             mCm = context.getSystemService(ConnectivityManager.class);
+
+            // TODO: call mObserverRegistry here after adding sepolicy changes
         }
 
         @NonNull
diff --git a/packages/NetworkStack/src/com/android/server/connectivity/NetworkMonitor.java b/packages/NetworkStack/src/com/android/server/connectivity/NetworkMonitor.java
index 6b31b82..96eaa50 100644
--- a/packages/NetworkStack/src/com/android/server/connectivity/NetworkMonitor.java
+++ b/packages/NetworkStack/src/com/android/server/connectivity/NetworkMonitor.java
@@ -31,6 +31,7 @@
 import static android.net.metrics.ValidationProbeEvent.DNS_SUCCESS;
 import static android.net.metrics.ValidationProbeEvent.PROBE_FALLBACK;
 import static android.net.metrics.ValidationProbeEvent.PROBE_PRIVDNS;
+import static android.net.util.NetworkStackUtils.isEmpty;
 
 import android.annotation.Nullable;
 import android.app.PendingIntent;
@@ -80,7 +81,6 @@
 import android.util.Log;
 
 import com.android.internal.annotations.VisibleForTesting;
-import com.android.internal.util.ArrayUtils;
 import com.android.internal.util.RingBufferIndices;
 import com.android.internal.util.State;
 import com.android.internal.util.StateMachine;
@@ -93,6 +93,7 @@
 import java.net.UnknownHostException;
 import java.util.ArrayList;
 import java.util.Arrays;
+import java.util.Collection;
 import java.util.Collections;
 import java.util.LinkedHashMap;
 import java.util.List;
@@ -1146,7 +1147,10 @@
                 return null;
             }
 
-            return CaptivePortalProbeSpec.parseCaptivePortalProbeSpecs(settingsValue);
+            final Collection<CaptivePortalProbeSpec> specs =
+                    CaptivePortalProbeSpec.parseCaptivePortalProbeSpecs(settingsValue);
+            final CaptivePortalProbeSpec[] specsArray = new CaptivePortalProbeSpec[specs.size()];
+            return specs.toArray(specsArray);
         } catch (Exception e) {
             // Don't let a misconfiguration bootloop the system.
             Log.e(TAG, "Error parsing configured fallback probe specs", e);
@@ -1169,7 +1173,7 @@
     }
 
     private CaptivePortalProbeSpec nextFallbackSpec() {
-        if (ArrayUtils.isEmpty(mCaptivePortalFallbackSpecs)) {
+        if (isEmpty(mCaptivePortalFallbackSpecs)) {
             return null;
         }
         // Randomly change spec without memory. Also randomize the first attempt.
diff --git a/packages/NetworkStack/src/com/android/server/util/NetworkStackConstants.java b/packages/NetworkStack/src/com/android/server/util/NetworkStackConstants.java
index eedaf30..804765e 100644
--- a/packages/NetworkStack/src/com/android/server/util/NetworkStackConstants.java
+++ b/packages/NetworkStack/src/com/android/server/util/NetworkStackConstants.java
@@ -16,6 +16,10 @@
 
 package com.android.server.util;
 
+import static android.net.shared.Inet4AddressUtils.intToInet4AddressHTH;
+
+import java.net.Inet4Address;
+
 /**
  * Network constants used by the network stack.
  */
@@ -79,6 +83,8 @@
     public static final int IPV4_SRC_ADDR_OFFSET = 12;
     public static final int IPV4_DST_ADDR_OFFSET = 16;
     public static final int IPV4_ADDR_LEN = 4;
+    public static final Inet4Address IPV4_ADDR_ALL = intToInet4AddressHTH(0xffffffff);
+    public static final Inet4Address IPV4_ADDR_ANY = intToInet4AddressHTH(0x0);
 
     /**
      * IPv6 constants.
diff --git a/packages/NetworkStack/tests/src/android/net/dhcp/DhcpLeaseRepositoryTest.java b/packages/NetworkStack/tests/src/android/net/dhcp/DhcpLeaseRepositoryTest.java
index 51d50d9..4abd77e 100644
--- a/packages/NetworkStack/tests/src/android/net/dhcp/DhcpLeaseRepositoryTest.java
+++ b/packages/NetworkStack/tests/src/android/net/dhcp/DhcpLeaseRepositoryTest.java
@@ -21,6 +21,8 @@
 import static android.net.dhcp.DhcpLeaseRepository.CLIENTID_UNSPEC;
 import static android.net.dhcp.DhcpLeaseRepository.INETADDR_UNSPEC;
 
+import static com.android.server.util.NetworkStackConstants.IPV4_ADDR_ANY;
+
 import static org.junit.Assert.assertEquals;
 import static org.junit.Assert.assertFalse;
 import static org.junit.Assert.assertNotEquals;
@@ -55,7 +57,6 @@
 @RunWith(AndroidJUnit4.class)
 @SmallTest
 public class DhcpLeaseRepositoryTest {
-    private static final Inet4Address INET4_ANY = (Inet4Address) Inet4Address.ANY;
     private static final Inet4Address TEST_DEF_ROUTER = parseAddr4("192.168.42.247");
     private static final Inet4Address TEST_SERVER_ADDR = parseAddr4("192.168.42.241");
     private static final Inet4Address TEST_RESERVED_ADDR = parseAddr4("192.168.42.243");
@@ -108,7 +109,7 @@
             MacAddress newMac = MacAddress.fromBytes(hwAddrBytes);
             final String hostname = "host_" + i;
             final DhcpLease lease = mRepo.getOffer(CLIENTID_UNSPEC, newMac,
-                    INET4_ANY /* relayAddr */, INETADDR_UNSPEC /* reqAddr */, hostname);
+                    IPV4_ADDR_ANY /* relayAddr */, INETADDR_UNSPEC /* reqAddr */, hostname);
 
             assertNotNull(lease);
             assertEquals(newMac, lease.getHwAddr());
@@ -130,7 +131,7 @@
 
         try {
             mRepo.getOffer(null, TEST_MAC_2,
-                    INET4_ANY /* relayAddr */, INETADDR_UNSPEC /* reqAddr */, HOSTNAME_NONE);
+                    IPV4_ADDR_ANY /* relayAddr */, INETADDR_UNSPEC /* reqAddr */, HOSTNAME_NONE);
             fail("Should be out of addresses");
         } catch (DhcpLeaseRepository.OutOfAddressesException e) {
             // Expected
@@ -181,11 +182,11 @@
     public void testGetOffer_StableAddress() throws Exception {
         for (final MacAddress macAddr : new MacAddress[] { TEST_MAC_1, TEST_MAC_2, TEST_MAC_3 }) {
             final DhcpLease lease = mRepo.getOffer(CLIENTID_UNSPEC, macAddr,
-                    INET4_ANY /* relayAddr */, INETADDR_UNSPEC /* reqAddr */, HOSTNAME_NONE);
+                    IPV4_ADDR_ANY /* relayAddr */, INETADDR_UNSPEC /* reqAddr */, HOSTNAME_NONE);
 
             // Same lease is offered twice
             final DhcpLease newLease = mRepo.getOffer(CLIENTID_UNSPEC, macAddr,
-                    INET4_ANY /* relayAddr */, INETADDR_UNSPEC /* reqAddr */, HOSTNAME_NONE);
+                    IPV4_ADDR_ANY /* relayAddr */, INETADDR_UNSPEC /* reqAddr */, HOSTNAME_NONE);
             assertEquals(lease, newLease);
         }
     }
@@ -196,7 +197,7 @@
         mRepo.updateParams(newPrefix, TEST_EXCL_SET, TEST_LEASE_TIME_MS);
 
         DhcpLease lease = mRepo.getOffer(CLIENTID_UNSPEC, TEST_MAC_1,
-                INET4_ANY /* relayAddr */, INETADDR_UNSPEC /* reqAddr */, HOSTNAME_NONE);
+                IPV4_ADDR_ANY /* relayAddr */, INETADDR_UNSPEC /* reqAddr */, HOSTNAME_NONE);
         assertTrue(newPrefix.contains(lease.getNetAddr()));
     }
 
@@ -205,7 +206,7 @@
         requestLeaseSelecting(TEST_MAC_1, TEST_INETADDR_1, TEST_HOSTNAME_1);
 
         DhcpLease offer = mRepo.getOffer(CLIENTID_UNSPEC, TEST_MAC_1,
-                INET4_ANY /* relayAddr */, INETADDR_UNSPEC /* reqAddr */, HOSTNAME_NONE);
+                IPV4_ADDR_ANY /* relayAddr */, INETADDR_UNSPEC /* reqAddr */, HOSTNAME_NONE);
         assertEquals(TEST_INETADDR_1, offer.getNetAddr());
         assertEquals(TEST_HOSTNAME_1, offer.getHostname());
     }
@@ -213,12 +214,13 @@
     @Test
     public void testGetOffer_ClientIdHasExistingLease() throws Exception {
         final byte[] clientId = new byte[] { 1, 2 };
-        mRepo.requestLease(clientId, TEST_MAC_1, INET4_ANY /* clientAddr */,
-                INET4_ANY /* relayAddr */, TEST_INETADDR_1 /* reqAddr */, false, TEST_HOSTNAME_1);
+        mRepo.requestLease(clientId, TEST_MAC_1, IPV4_ADDR_ANY /* clientAddr */,
+                IPV4_ADDR_ANY /* relayAddr */, TEST_INETADDR_1 /* reqAddr */, false,
+                TEST_HOSTNAME_1);
 
         // Different MAC, but same clientId
         DhcpLease offer = mRepo.getOffer(clientId, TEST_MAC_2,
-                INET4_ANY /* relayAddr */, INETADDR_UNSPEC /* reqAddr */, HOSTNAME_NONE);
+                IPV4_ADDR_ANY /* relayAddr */, INETADDR_UNSPEC /* reqAddr */, HOSTNAME_NONE);
         assertEquals(TEST_INETADDR_1, offer.getNetAddr());
         assertEquals(TEST_HOSTNAME_1, offer.getHostname());
     }
@@ -227,12 +229,13 @@
     public void testGetOffer_DifferentClientId() throws Exception {
         final byte[] clientId1 = new byte[] { 1, 2 };
         final byte[] clientId2 = new byte[] { 3, 4 };
-        mRepo.requestLease(clientId1, TEST_MAC_1, INET4_ANY /* clientAddr */,
-                INET4_ANY /* relayAddr */, TEST_INETADDR_1 /* reqAddr */, false, TEST_HOSTNAME_1);
+        mRepo.requestLease(clientId1, TEST_MAC_1, IPV4_ADDR_ANY /* clientAddr */,
+                IPV4_ADDR_ANY /* relayAddr */, TEST_INETADDR_1 /* reqAddr */, false,
+                TEST_HOSTNAME_1);
 
         // Same MAC, different client ID
         DhcpLease offer = mRepo.getOffer(clientId2, TEST_MAC_1,
-                INET4_ANY /* relayAddr */, INETADDR_UNSPEC /* reqAddr */, HOSTNAME_NONE);
+                IPV4_ADDR_ANY /* relayAddr */, INETADDR_UNSPEC /* reqAddr */, HOSTNAME_NONE);
         // Obtains a different address
         assertNotEquals(TEST_INETADDR_1, offer.getNetAddr());
         assertEquals(HOSTNAME_NONE, offer.getHostname());
@@ -241,7 +244,7 @@
 
     @Test
     public void testGetOffer_RequestedAddress() throws Exception {
-        DhcpLease offer = mRepo.getOffer(CLIENTID_UNSPEC, TEST_MAC_1, INET4_ANY /* relayAddr */,
+        DhcpLease offer = mRepo.getOffer(CLIENTID_UNSPEC, TEST_MAC_1, IPV4_ADDR_ANY /* relayAddr */,
                 TEST_INETADDR_1 /* reqAddr */, TEST_HOSTNAME_1);
         assertEquals(TEST_INETADDR_1, offer.getNetAddr());
         assertEquals(TEST_HOSTNAME_1, offer.getHostname());
@@ -250,14 +253,14 @@
     @Test
     public void testGetOffer_RequestedAddressInUse() throws Exception {
         requestLeaseSelecting(TEST_MAC_1, TEST_INETADDR_1);
-        DhcpLease offer = mRepo.getOffer(CLIENTID_UNSPEC, TEST_MAC_2, INET4_ANY /* relayAddr */,
+        DhcpLease offer = mRepo.getOffer(CLIENTID_UNSPEC, TEST_MAC_2, IPV4_ADDR_ANY /* relayAddr */,
                 TEST_INETADDR_1 /* reqAddr */, HOSTNAME_NONE);
         assertNotEquals(TEST_INETADDR_1, offer.getNetAddr());
     }
 
     @Test
     public void testGetOffer_RequestedAddressReserved() throws Exception {
-        DhcpLease offer = mRepo.getOffer(CLIENTID_UNSPEC, TEST_MAC_1, INET4_ANY /* relayAddr */,
+        DhcpLease offer = mRepo.getOffer(CLIENTID_UNSPEC, TEST_MAC_1, IPV4_ADDR_ANY /* relayAddr */,
                 TEST_RESERVED_ADDR /* reqAddr */, HOSTNAME_NONE);
         assertNotEquals(TEST_RESERVED_ADDR, offer.getNetAddr());
     }
@@ -265,7 +268,7 @@
     @Test
     public void testGetOffer_RequestedAddressInvalid() throws Exception {
         final Inet4Address invalidAddr = parseAddr4("192.168.42.0");
-        DhcpLease offer = mRepo.getOffer(CLIENTID_UNSPEC, TEST_MAC_1, INET4_ANY /* relayAddr */,
+        DhcpLease offer = mRepo.getOffer(CLIENTID_UNSPEC, TEST_MAC_1, IPV4_ADDR_ANY /* relayAddr */,
                 invalidAddr /* reqAddr */, HOSTNAME_NONE);
         assertNotEquals(invalidAddr, offer.getNetAddr());
     }
@@ -273,7 +276,7 @@
     @Test
     public void testGetOffer_RequestedAddressOutsideSubnet() throws Exception {
         final Inet4Address invalidAddr = parseAddr4("192.168.254.2");
-        DhcpLease offer = mRepo.getOffer(CLIENTID_UNSPEC, TEST_MAC_1, INET4_ANY /* relayAddr */,
+        DhcpLease offer = mRepo.getOffer(CLIENTID_UNSPEC, TEST_MAC_1, IPV4_ADDR_ANY /* relayAddr */,
                 invalidAddr /* reqAddr */, HOSTNAME_NONE);
         assertNotEquals(invalidAddr, offer.getNetAddr());
     }
@@ -322,7 +325,7 @@
 
     @Test(expected = DhcpLeaseRepository.InvalidSubnetException.class)
     public void testRequestLease_SelectingRelayInInvalidSubnet() throws  Exception {
-        mRepo.requestLease(CLIENTID_UNSPEC, TEST_MAC_1, INET4_ANY /* clientAddr */,
+        mRepo.requestLease(CLIENTID_UNSPEC, TEST_MAC_1, IPV4_ADDR_ANY /* clientAddr */,
                 parseAddr4("192.168.128.1") /* relayAddr */, TEST_INETADDR_1 /* reqAddr */,
                 true /* sidSet */, HOSTNAME_NONE);
     }
@@ -419,14 +422,14 @@
     public void testReleaseLease_StableOffer() throws Exception {
         for (MacAddress mac : new MacAddress[] { TEST_MAC_1, TEST_MAC_2, TEST_MAC_3 }) {
             final DhcpLease lease = mRepo.getOffer(CLIENTID_UNSPEC, mac,
-                    INET4_ANY /* relayAddr */, INETADDR_UNSPEC /* reqAddr */, HOSTNAME_NONE);
+                    IPV4_ADDR_ANY /* relayAddr */, INETADDR_UNSPEC /* reqAddr */, HOSTNAME_NONE);
 
             requestLeaseSelecting(mac, lease.getNetAddr());
             mRepo.releaseLease(CLIENTID_UNSPEC, mac, lease.getNetAddr());
 
             // Same lease is offered after it was released
             final DhcpLease newLease = mRepo.getOffer(CLIENTID_UNSPEC, mac,
-                    INET4_ANY /* relayAddr */, INETADDR_UNSPEC /* reqAddr */, HOSTNAME_NONE);
+                    IPV4_ADDR_ANY /* relayAddr */, INETADDR_UNSPEC /* reqAddr */, HOSTNAME_NONE);
             assertEquals(lease.getNetAddr(), newLease.getNetAddr());
         }
     }
@@ -434,13 +437,13 @@
     @Test
     public void testMarkLeaseDeclined() throws Exception {
         final DhcpLease lease = mRepo.getOffer(CLIENTID_UNSPEC, TEST_MAC_1,
-                INET4_ANY /* relayAddr */, INETADDR_UNSPEC /* reqAddr */, HOSTNAME_NONE);
+                IPV4_ADDR_ANY /* relayAddr */, INETADDR_UNSPEC /* reqAddr */, HOSTNAME_NONE);
 
         mRepo.markLeaseDeclined(lease.getNetAddr());
 
         // Same lease is not offered again
         final DhcpLease newLease = mRepo.getOffer(CLIENTID_UNSPEC, TEST_MAC_1,
-                INET4_ANY /* relayAddr */, INETADDR_UNSPEC /* reqAddr */, HOSTNAME_NONE);
+                IPV4_ADDR_ANY /* relayAddr */, INETADDR_UNSPEC /* reqAddr */, HOSTNAME_NONE);
         assertNotEquals(lease.getNetAddr(), newLease.getNetAddr());
     }
 
@@ -457,16 +460,16 @@
 
         // Last 2 addresses: addresses marked declined should be used
         final DhcpLease firstLease = mRepo.getOffer(CLIENTID_UNSPEC, TEST_MAC_1,
-                INET4_ANY /* relayAddr */, INETADDR_UNSPEC /* reqAddr */, TEST_HOSTNAME_1);
+                IPV4_ADDR_ANY /* relayAddr */, INETADDR_UNSPEC /* reqAddr */, TEST_HOSTNAME_1);
         requestLeaseSelecting(TEST_MAC_1, firstLease.getNetAddr());
 
         final DhcpLease secondLease = mRepo.getOffer(CLIENTID_UNSPEC, TEST_MAC_2,
-                INET4_ANY /* relayAddr */, INETADDR_UNSPEC /* reqAddr */, TEST_HOSTNAME_2);
+                IPV4_ADDR_ANY /* relayAddr */, INETADDR_UNSPEC /* reqAddr */, TEST_HOSTNAME_2);
         requestLeaseSelecting(TEST_MAC_2, secondLease.getNetAddr());
 
         // Now out of addresses
         try {
-            mRepo.getOffer(CLIENTID_UNSPEC, TEST_MAC_3, INET4_ANY /* relayAddr */,
+            mRepo.getOffer(CLIENTID_UNSPEC, TEST_MAC_3, IPV4_ADDR_ANY /* relayAddr */,
                     INETADDR_UNSPEC /* reqAddr */, HOSTNAME_NONE);
             fail("Repository should be out of addresses and throw");
         } catch (DhcpLeaseRepository.OutOfAddressesException e) { /* expected */ }
@@ -480,7 +483,8 @@
     private DhcpLease requestLease(@NonNull MacAddress macAddr, @NonNull Inet4Address clientAddr,
             @Nullable Inet4Address reqAddr, @Nullable String hostname, boolean sidSet)
             throws DhcpLeaseRepository.DhcpLeaseException {
-        return mRepo.requestLease(CLIENTID_UNSPEC, macAddr, clientAddr, INET4_ANY /* relayAddr */,
+        return mRepo.requestLease(CLIENTID_UNSPEC, macAddr, clientAddr,
+                IPV4_ADDR_ANY /* relayAddr */,
                 reqAddr, sidSet, hostname);
     }
 
@@ -490,7 +494,7 @@
     private DhcpLease requestLeaseSelecting(@NonNull MacAddress macAddr,
             @NonNull Inet4Address reqAddr, @Nullable String hostname)
             throws DhcpLeaseRepository.DhcpLeaseException {
-        return requestLease(macAddr, INET4_ANY /* clientAddr */, reqAddr, hostname,
+        return requestLease(macAddr, IPV4_ADDR_ANY /* clientAddr */, reqAddr, hostname,
                 true /* sidSet */);
     }
 
@@ -507,7 +511,7 @@
      */
     private DhcpLease requestLeaseInitReboot(@NonNull MacAddress macAddr,
             @NonNull Inet4Address reqAddr) throws DhcpLeaseRepository.DhcpLeaseException {
-        return requestLease(macAddr, INET4_ANY /* clientAddr */, reqAddr, HOSTNAME_NONE,
+        return requestLease(macAddr, IPV4_ADDR_ANY /* clientAddr */, reqAddr, HOSTNAME_NONE,
                 false /* sidSet */);
     }
 
diff --git a/packages/NetworkStack/tests/src/android/net/dhcp/DhcpPacketTest.java b/packages/NetworkStack/tests/src/android/net/dhcp/DhcpPacketTest.java
index a592809..7544e72 100644
--- a/packages/NetworkStack/tests/src/android/net/dhcp/DhcpPacketTest.java
+++ b/packages/NetworkStack/tests/src/android/net/dhcp/DhcpPacketTest.java
@@ -16,9 +16,27 @@
 
 package android.net.dhcp;
 
-import static android.net.NetworkUtils.getBroadcastAddress;
-import static android.net.NetworkUtils.getPrefixMaskAsInet4Address;
-import static android.net.dhcp.DhcpPacket.*;
+import static android.net.dhcp.DhcpPacket.DHCP_BROADCAST_ADDRESS;
+import static android.net.dhcp.DhcpPacket.DHCP_DNS_SERVER;
+import static android.net.dhcp.DhcpPacket.DHCP_DOMAIN_NAME;
+import static android.net.dhcp.DhcpPacket.DHCP_LEASE_TIME;
+import static android.net.dhcp.DhcpPacket.DHCP_MESSAGE_TYPE_ACK;
+import static android.net.dhcp.DhcpPacket.DHCP_MESSAGE_TYPE_OFFER;
+import static android.net.dhcp.DhcpPacket.DHCP_MTU;
+import static android.net.dhcp.DhcpPacket.DHCP_REBINDING_TIME;
+import static android.net.dhcp.DhcpPacket.DHCP_RENEWAL_TIME;
+import static android.net.dhcp.DhcpPacket.DHCP_ROUTER;
+import static android.net.dhcp.DhcpPacket.DHCP_SUBNET_MASK;
+import static android.net.dhcp.DhcpPacket.DHCP_VENDOR_INFO;
+import static android.net.dhcp.DhcpPacket.ENCAP_BOOTP;
+import static android.net.dhcp.DhcpPacket.ENCAP_L2;
+import static android.net.dhcp.DhcpPacket.ENCAP_L3;
+import static android.net.dhcp.DhcpPacket.INADDR_ANY;
+import static android.net.dhcp.DhcpPacket.INFINITE_LEASE;
+import static android.net.dhcp.DhcpPacket.ParseException;
+import static android.net.shared.Inet4AddressUtils.getBroadcastAddress;
+import static android.net.shared.Inet4AddressUtils.getPrefixMaskAsInet4Address;
+
 import static org.junit.Assert.assertEquals;
 import static org.junit.Assert.assertNotNull;
 import static org.junit.Assert.assertNull;
@@ -30,11 +48,15 @@
 import android.net.LinkAddress;
 import android.net.NetworkUtils;
 import android.net.metrics.DhcpErrorEvent;
-import android.support.test.runner.AndroidJUnit4;
 import android.support.test.filters.SmallTest;
+import android.support.test.runner.AndroidJUnit4;
 
 import com.android.internal.util.HexDump;
 
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
 import java.io.ByteArrayOutputStream;
 import java.net.Inet4Address;
 import java.nio.ByteBuffer;
@@ -44,10 +66,6 @@
 import java.util.Collections;
 import java.util.Random;
 
-import org.junit.Before;
-import org.junit.Test;
-import org.junit.runner.RunWith;
-
 @RunWith(AndroidJUnit4.class)
 @SmallTest
 public class DhcpPacketTest {
@@ -60,9 +78,9 @@
             SERVER_ADDR, PREFIX_LENGTH);
     private static final String HOSTNAME = "testhostname";
     private static final short MTU = 1500;
-    // Use our own empty address instead of Inet4Address.ANY or INADDR_ANY to ensure that the code
+    // Use our own empty address instead of IPV4_ADDR_ANY or INADDR_ANY to ensure that the code
     // doesn't use == instead of equals when comparing addresses.
-    private static final Inet4Address ANY = (Inet4Address) v4Address("0.0.0.0");
+    private static final Inet4Address ANY = v4Address("0.0.0.0");
 
     private static final byte[] CLIENT_MAC = new byte[] { 0x00, 0x01, 0x02, 0x03, 0x04, 0x05 };
 
diff --git a/packages/NetworkStack/tests/src/android/net/dhcp/DhcpServingParamsTest.java b/packages/NetworkStack/tests/src/android/net/dhcp/DhcpServingParamsTest.java
index 3ca0564..1004382 100644
--- a/packages/NetworkStack/tests/src/android/net/dhcp/DhcpServingParamsTest.java
+++ b/packages/NetworkStack/tests/src/android/net/dhcp/DhcpServingParamsTest.java
@@ -17,8 +17,8 @@
 package android.net.dhcp;
 
 import static android.net.InetAddresses.parseNumericAddress;
-import static android.net.NetworkUtils.inet4AddressToIntHTH;
 import static android.net.dhcp.DhcpServingParams.MTU_UNSET;
+import static android.net.shared.Inet4AddressUtils.inet4AddressToIntHTH;
 
 import static junit.framework.Assert.assertEquals;
 import static junit.framework.Assert.assertFalse;
@@ -27,8 +27,8 @@
 import android.annotation.NonNull;
 import android.annotation.Nullable;
 import android.net.LinkAddress;
-import android.net.NetworkUtils;
 import android.net.dhcp.DhcpServingParams.InvalidParameterException;
+import android.net.shared.Inet4AddressUtils;
 import android.support.test.filters.SmallTest;
 import android.support.test.runner.AndroidJUnit4;
 
@@ -200,7 +200,7 @@
     }
 
     private static int[] toIntArray(Collection<Inet4Address> addrs) {
-        return addrs.stream().mapToInt(NetworkUtils::inet4AddressToIntHTH).toArray();
+        return addrs.stream().mapToInt(Inet4AddressUtils::inet4AddressToIntHTH).toArray();
     }
 
     private static <T> void assertContains(@NonNull Set<T> set, @NonNull Set<T> subset) {
diff --git a/packages/NetworkStack/tests/src/android/net/ip/IpClientTest.java b/packages/NetworkStack/tests/src/android/net/ip/IpClientTest.java
index f21809f..4ae044de 100644
--- a/packages/NetworkStack/tests/src/android/net/ip/IpClientTest.java
+++ b/packages/NetworkStack/tests/src/android/net/ip/IpClientTest.java
@@ -103,9 +103,7 @@
         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.getResources()).thenReturn(mResources);
         when(mResources.getInteger(R.integer.config_networkAvoidBadWifi))
                 .thenReturn(DEFAULT_AVOIDBADWIFI_CONFIG_VALUE);
diff --git a/packages/SettingsLib/src/com/android/settingslib/RestrictedLockUtilsInternal.java b/packages/SettingsLib/src/com/android/settingslib/RestrictedLockUtilsInternal.java
index 74aaf3c..93f6a94 100644
--- a/packages/SettingsLib/src/com/android/settingslib/RestrictedLockUtilsInternal.java
+++ b/packages/SettingsLib/src/com/android/settingslib/RestrictedLockUtilsInternal.java
@@ -47,6 +47,7 @@
 import com.android.internal.widget.LockPatternUtils;
 
 import java.util.List;
+import java.util.Set;
 
 /**
  * Utility class to host methods usable in adding a restricted padlock icon and showing admin
@@ -325,7 +326,8 @@
         if (admin == null) {
             return null;
         }
-        if (dpm.getCrossProfileCalendarPackages().isEmpty()) {
+        final Set<String> packages = dpm.getCrossProfileCalendarPackages();
+        if (packages != null && packages.isEmpty()) {
             return admin;
         }
         return null;
diff --git a/packages/SettingsLib/src/com/android/settingslib/applications/AppUtils.java b/packages/SettingsLib/src/com/android/settingslib/applications/AppUtils.java
index 7357fe6..42afb69 100644
--- a/packages/SettingsLib/src/com/android/settingslib/applications/AppUtils.java
+++ b/packages/SettingsLib/src/com/android/settingslib/applications/AppUtils.java
@@ -16,6 +16,7 @@
 
 package com.android.settingslib.applications;
 
+import android.app.Application;
 import android.content.ComponentName;
 import android.content.Context;
 import android.content.IntentFilter;
@@ -123,4 +124,12 @@
         return null;
     }
 
+    /**
+     * Returns a boolean indicating whether the given package is a hidden system module
+     */
+    public static boolean isHiddenSystemModule(Context context, String packageName) {
+        return ApplicationsState.getInstance((Application) context.getApplicationContext())
+            .isHiddenModule(packageName);
+    }
+
 }
diff --git a/packages/SettingsLib/src/com/android/settingslib/applications/ApplicationsState.java b/packages/SettingsLib/src/com/android/settingslib/applications/ApplicationsState.java
index a936df2..c9fbc7b 100644
--- a/packages/SettingsLib/src/com/android/settingslib/applications/ApplicationsState.java
+++ b/packages/SettingsLib/src/com/android/settingslib/applications/ApplicationsState.java
@@ -29,6 +29,7 @@
 import android.content.pm.ApplicationInfo;
 import android.content.pm.IPackageManager;
 import android.content.pm.IPackageStatsObserver;
+import android.content.pm.ModuleInfo;
 import android.content.pm.PackageManager;
 import android.content.pm.PackageManager.NameNotFoundException;
 import android.content.pm.PackageStats;
@@ -71,6 +72,7 @@
 import java.util.Collections;
 import java.util.Comparator;
 import java.util.HashMap;
+import java.util.HashSet;
 import java.util.List;
 import java.util.Objects;
 import java.util.UUID;
@@ -95,9 +97,14 @@
     static ApplicationsState sInstance;
 
     public static ApplicationsState getInstance(Application app) {
+        return getInstance(app, AppGlobals.getPackageManager());
+    }
+
+    @VisibleForTesting
+    static ApplicationsState getInstance(Application app, IPackageManager iPackageManager) {
         synchronized (sLock) {
             if (sInstance == null) {
-                sInstance = new ApplicationsState(app);
+                sInstance = new ApplicationsState(app, iPackageManager);
             }
             return sInstance;
         }
@@ -132,6 +139,7 @@
     String mCurComputingSizePkg;
     int mCurComputingSizeUserId;
     boolean mSessionsChanged;
+    final HashSet<String> mHiddenModules = new HashSet<>();
 
     // Temporary for dispatching session callbacks.  Only touched by main thread.
     final ArrayList<WeakReference<Session>> mActiveSessions = new ArrayList<>();
@@ -172,11 +180,11 @@
             FLAG_SESSION_REQUEST_HOME_APP | FLAG_SESSION_REQUEST_ICONS |
             FLAG_SESSION_REQUEST_SIZES | FLAG_SESSION_REQUEST_LAUNCHER;
 
-    private ApplicationsState(Application app) {
+    private ApplicationsState(Application app, IPackageManager iPackageManager) {
         mContext = app;
         mPm = mContext.getPackageManager();
         mDrawableFactory = IconDrawableFactory.newInstance(mContext);
-        mIpm = AppGlobals.getPackageManager();
+        mIpm = iPackageManager;
         mUm = mContext.getSystemService(UserManager.class);
         mStats = mContext.getSystemService(StorageStatsManager.class);
         for (int userId : mUm.getProfileIdsWithDisabled(UserHandle.myUserId())) {
@@ -194,6 +202,13 @@
         mRetrieveFlags = PackageManager.MATCH_DISABLED_COMPONENTS |
                 PackageManager.MATCH_DISABLED_UNTIL_USED_COMPONENTS;
 
+        final List<ModuleInfo> moduleInfos = mPm.getInstalledModules(0 /* flags */);
+        for (ModuleInfo info : moduleInfos) {
+            if (info.isHidden()) {
+                mHiddenModules.add(info.getPackageName());
+            }
+        }
+
         /**
          * This is a trick to prevent the foreground thread from being delayed.
          * The problem is that Dalvik monitors are initially spin locks, to keep
@@ -283,6 +298,10 @@
                 }
                 mHaveDisabledApps = true;
             }
+            if (isHiddenModule(info.packageName)) {
+                mApplications.remove(i--);
+                continue;
+            }
             if (!mHaveInstantApps && AppUtils.isInstant(info)) {
                 mHaveInstantApps = true;
             }
@@ -314,10 +333,15 @@
     public boolean haveDisabledApps() {
         return mHaveDisabledApps;
     }
+
     public boolean haveInstantApps() {
         return mHaveInstantApps;
     }
 
+    boolean isHiddenModule(String packageName) {
+        return mHiddenModules.contains(packageName);
+    }
+
     void doPauseIfNeededLocked() {
         if (!mResumed) {
             return;
diff --git a/packages/SettingsLib/src/com/android/settingslib/net/NetworkCycleDataLoader.java b/packages/SettingsLib/src/com/android/settingslib/net/NetworkCycleDataLoader.java
index d957801..305a1ff 100644
--- a/packages/SettingsLib/src/com/android/settingslib/net/NetworkCycleDataLoader.java
+++ b/packages/SettingsLib/src/com/android/settingslib/net/NetworkCycleDataLoader.java
@@ -22,7 +22,6 @@
 import android.app.usage.NetworkStats;
 import android.app.usage.NetworkStatsManager;
 import android.content.Context;
-import android.net.ConnectivityManager;
 import android.net.INetworkStatsService;
 import android.net.INetworkStatsSession;
 import android.net.NetworkPolicy;
@@ -35,14 +34,14 @@
 import android.text.format.DateUtils;
 import android.util.Pair;
 
+import androidx.annotation.VisibleForTesting;
+import androidx.loader.content.AsyncTaskLoader;
+
 import com.android.settingslib.NetworkPolicyEditor;
 
 import java.time.ZonedDateTime;
 import java.util.Iterator;
 
-import androidx.annotation.VisibleForTesting;
-import androidx.loader.content.AsyncTaskLoader;
-
 /**
  * Loader for network data usage history. It returns a list of usage data per billing cycle.
  */
@@ -121,8 +120,7 @@
 
             long cycleEnd = historyEnd;
             while (cycleEnd > historyStart) {
-                final long cycleStart = Math.max(
-                    historyStart, cycleEnd - (DateUtils.WEEK_IN_MILLIS * 4));
+                final long cycleStart = cycleEnd - (DateUtils.WEEK_IN_MILLIS * 4);
                 recordUsage(cycleStart, cycleEnd);
                 cycleEnd = cycleStart;
             }
diff --git a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/applications/ApplicationsStateRoboTest.java b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/applications/ApplicationsStateRoboTest.java
index ccec175a..a098ecc 100644
--- a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/applications/ApplicationsStateRoboTest.java
+++ b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/applications/ApplicationsStateRoboTest.java
@@ -18,7 +18,9 @@
 
 import static com.google.common.truth.Truth.assertThat;
 
-import static org.mockito.ArgumentMatchers.anyString;
+import static org.mockito.Matchers.any;
+import static org.mockito.Matchers.anyInt;
+import static org.mockito.Matchers.anyString;
 import static org.mockito.Mockito.verify;
 import static org.mockito.Mockito.when;
 import static org.robolectric.shadow.api.Shadow.extract;
@@ -33,12 +35,16 @@
 import android.content.IntentFilter;
 import android.content.pm.ActivityInfo;
 import android.content.pm.ApplicationInfo;
+import android.content.pm.IPackageManager;
+import android.content.pm.ModuleInfo;
 import android.content.pm.PackageManager;
+import android.content.pm.ParceledListSlice;
 import android.content.pm.ResolveInfo;
 import android.graphics.drawable.ColorDrawable;
 import android.graphics.drawable.Drawable;
 import android.os.Handler;
 import android.os.UserHandle;
+import android.text.TextUtils;
 import android.util.IconDrawableFactory;
 
 import com.android.settingslib.applications.ApplicationsState.AppEntry;
@@ -46,11 +52,11 @@
 import com.android.settingslib.applications.ApplicationsState.Session;
 import com.android.settingslib.testutils.shadow.ShadowUserManager;
 
+import org.junit.After;
 import org.junit.Before;
 import org.junit.Test;
 import org.junit.runner.RunWith;
 import org.mockito.ArgumentCaptor;
-import org.mockito.ArgumentMatchers;
 import org.mockito.Captor;
 import org.mockito.Mock;
 import org.mockito.MockitoAnnotations;
@@ -78,6 +84,8 @@
 
     /** Class under test */
     private ApplicationsState mApplicationsState;
+    private Session mSession;
+
 
     @Mock
     private Callbacks mCallbacks;
@@ -85,6 +93,8 @@
     private ArgumentCaptor<ArrayList<AppEntry>> mAppEntriesCaptor;
     @Mock
     private StorageStatsManager mStorageStatsManager;
+    @Mock
+    private IPackageManager mPackageManagerService;
 
     @Implements(value = IconDrawableFactory.class)
     public static class ShadowIconDrawableFactory {
@@ -99,6 +109,11 @@
     public static class ShadowPackageManager extends
             org.robolectric.shadows.ShadowApplicationPackageManager {
 
+        // test installed modules, 2 regular, 2 hidden
+        private final String[] mModuleNames = {
+            "test.module.1", "test.hidden.module.2", "test.hidden.module.3", "test.module.4"};
+        private final List<ModuleInfo> mInstalledModules = new ArrayList<>();
+
         @Implementation
         protected ComponentName getHomeActivities(List<ResolveInfo> outActivities) {
             ResolveInfo resolveInfo = new ResolveInfo();
@@ -109,6 +124,16 @@
             return ComponentName.createRelative(resolveInfo.activityInfo.packageName, "foo");
         }
 
+        @Implementation
+        public List<ModuleInfo> getInstalledModules(int flags) {
+            if (mInstalledModules.isEmpty()) {
+                for (String moduleName : mModuleNames) {
+                    mInstalledModules.add(createModuleInfo(moduleName));
+                }
+            }
+            return mInstalledModules;
+        }
+
         public List<ResolveInfo> queryIntentActivitiesAsUser(Intent intent,
                 @PackageManager.ResolveInfoFlags int flags, @UserIdInt int userId) {
             List<ResolveInfo> resolveInfos = new ArrayList<>();
@@ -121,6 +146,15 @@
             resolveInfos.add(resolveInfo);
             return resolveInfos;
         }
+
+        private ModuleInfo createModuleInfo(String packageName) {
+            final ModuleInfo info = new ModuleInfo();
+            info.setName(packageName);
+            info.setPackageName(packageName);
+            // will treat any app with package name that contains "hidden" as hidden module
+            info.setHidden(!TextUtils.isEmpty(packageName) && packageName.contains("hidden"));
+            return info;
+        }
     }
 
     @Before
@@ -136,12 +170,28 @@
         storageStats.codeBytes = 10;
         storageStats.dataBytes = 20;
         storageStats.cacheBytes = 30;
-        when(mStorageStatsManager.queryStatsForPackage(ArgumentMatchers.any(UUID.class),
-                anyString(), ArgumentMatchers.any(UserHandle.class))).thenReturn(storageStats);
+        when(mStorageStatsManager.queryStatsForPackage(any(UUID.class),
+                anyString(), any(UserHandle.class))).thenReturn(storageStats);
+
+        // Set up 3 installed apps, in which 1 is hidden module
+        final List<ApplicationInfo> infos = new ArrayList<>();
+        infos.add(createApplicationInfo("test.package.1"));
+        infos.add(createApplicationInfo("test.hidden.module.2"));
+        infos.add(createApplicationInfo("test.package.3"));
+        when(mPackageManagerService.getInstalledApplications(
+            anyInt() /* flags */, anyInt() /* userId */)).thenReturn(new ParceledListSlice(infos));
 
         ApplicationsState.sInstance = null;
-        mApplicationsState = ApplicationsState.getInstance(RuntimeEnvironment.application);
+        mApplicationsState =
+            ApplicationsState.getInstance(RuntimeEnvironment.application, mPackageManagerService);
         mApplicationsState.clearEntries();
+
+        mSession = mApplicationsState.newSession(mCallbacks);
+    }
+
+    @After
+    public void tearDown() {
+        mSession.onDestroy();
     }
 
     private ApplicationInfo createApplicationInfo(String packageName) {
@@ -187,12 +237,11 @@
 
     @Test
     public void testDefaultSessionLoadsAll() {
-        Session session = mApplicationsState.newSession(mCallbacks);
-        session.onResume();
+        mSession.onResume();
 
         addApp(HOME_PACKAGE_NAME, 1);
         addApp(LAUNCHABLE_PACKAGE_NAME, 2);
-        session.rebuild(ApplicationsState.FILTER_EVERYTHING, ApplicationsState.SIZE_COMPARATOR);
+        mSession.rebuild(ApplicationsState.FILTER_EVERYTHING, ApplicationsState.SIZE_COMPARATOR);
         processAllMessages();
         verify(mCallbacks).onRebuildComplete(mAppEntriesCaptor.capture());
 
@@ -211,17 +260,15 @@
         AppEntry launchableEntry = findAppEntry(appEntries, 2);
         assertThat(launchableEntry.hasLauncherEntry).isTrue();
         assertThat(launchableEntry.launcherEntryEnabled).isTrue();
-        session.onDestroy();
     }
 
     @Test
     public void testCustomSessionLoadsIconsOnly() {
-        Session session = mApplicationsState.newSession(mCallbacks);
-        session.setSessionFlags(ApplicationsState.FLAG_SESSION_REQUEST_ICONS);
-        session.onResume();
+        mSession.setSessionFlags(ApplicationsState.FLAG_SESSION_REQUEST_ICONS);
+        mSession.onResume();
 
         addApp(LAUNCHABLE_PACKAGE_NAME, 1);
-        session.rebuild(ApplicationsState.FILTER_EVERYTHING, ApplicationsState.SIZE_COMPARATOR);
+        mSession.rebuild(ApplicationsState.FILTER_EVERYTHING, ApplicationsState.SIZE_COMPARATOR);
         processAllMessages();
         verify(mCallbacks).onRebuildComplete(mAppEntriesCaptor.capture());
 
@@ -232,17 +279,15 @@
         assertThat(launchableEntry.icon).isNotNull();
         assertThat(launchableEntry.size).isEqualTo(-1);
         assertThat(launchableEntry.hasLauncherEntry).isFalse();
-        session.onDestroy();
     }
 
     @Test
     public void testCustomSessionLoadsSizesOnly() {
-        Session session = mApplicationsState.newSession(mCallbacks);
-        session.setSessionFlags(ApplicationsState.FLAG_SESSION_REQUEST_SIZES);
-        session.onResume();
+        mSession.setSessionFlags(ApplicationsState.FLAG_SESSION_REQUEST_SIZES);
+        mSession.onResume();
 
         addApp(LAUNCHABLE_PACKAGE_NAME, 1);
-        session.rebuild(ApplicationsState.FILTER_EVERYTHING, ApplicationsState.SIZE_COMPARATOR);
+        mSession.rebuild(ApplicationsState.FILTER_EVERYTHING, ApplicationsState.SIZE_COMPARATOR);
         processAllMessages();
         verify(mCallbacks).onRebuildComplete(mAppEntriesCaptor.capture());
 
@@ -253,17 +298,15 @@
         assertThat(launchableEntry.icon).isNull();
         assertThat(launchableEntry.hasLauncherEntry).isFalse();
         assertThat(launchableEntry.size).isGreaterThan(0L);
-        session.onDestroy();
     }
 
     @Test
     public void testCustomSessionLoadsHomeOnly() {
-        Session session = mApplicationsState.newSession(mCallbacks);
-        session.setSessionFlags(ApplicationsState.FLAG_SESSION_REQUEST_HOME_APP);
-        session.onResume();
+        mSession.setSessionFlags(ApplicationsState.FLAG_SESSION_REQUEST_HOME_APP);
+        mSession.onResume();
 
         addApp(HOME_PACKAGE_NAME, 1);
-        session.rebuild(ApplicationsState.FILTER_EVERYTHING, ApplicationsState.SIZE_COMPARATOR);
+        mSession.rebuild(ApplicationsState.FILTER_EVERYTHING, ApplicationsState.SIZE_COMPARATOR);
         processAllMessages();
         verify(mCallbacks).onRebuildComplete(mAppEntriesCaptor.capture());
 
@@ -275,17 +318,15 @@
         assertThat(launchableEntry.hasLauncherEntry).isFalse();
         assertThat(launchableEntry.size).isEqualTo(-1);
         assertThat(launchableEntry.isHomeApp).isTrue();
-        session.onDestroy();
     }
 
     @Test
     public void testCustomSessionLoadsLeanbackOnly() {
-        Session session = mApplicationsState.newSession(mCallbacks);
-        session.setSessionFlags(ApplicationsState.FLAG_SESSION_REQUEST_LEANBACK_LAUNCHER);
-        session.onResume();
+        mSession.setSessionFlags(ApplicationsState.FLAG_SESSION_REQUEST_LEANBACK_LAUNCHER);
+        mSession.onResume();
 
         addApp(LAUNCHABLE_PACKAGE_NAME, 1);
-        session.rebuild(ApplicationsState.FILTER_EVERYTHING, ApplicationsState.SIZE_COMPARATOR);
+        mSession.rebuild(ApplicationsState.FILTER_EVERYTHING, ApplicationsState.SIZE_COMPARATOR);
         processAllMessages();
         verify(mCallbacks).onRebuildComplete(mAppEntriesCaptor.capture());
 
@@ -298,6 +339,16 @@
         assertThat(launchableEntry.isHomeApp).isFalse();
         assertThat(launchableEntry.hasLauncherEntry).isTrue();
         assertThat(launchableEntry.launcherEntryEnabled).isTrue();
-        session.onDestroy();
     }
+
+    @Test
+    public void onResume_shouldNotIncludeSystemHiddenModule() {
+        mSession.onResume();
+
+        final List<ApplicationInfo> mApplications = mApplicationsState.mApplications;
+        assertThat(mApplications).hasSize(2);
+        assertThat(mApplications.get(0).packageName).isEqualTo("test.package.1");
+        assertThat(mApplications.get(1).packageName).isEqualTo("test.package.3");
+    }
+
 }
diff --git a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/net/NetworkCycleDataLoaderTest.java b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/net/NetworkCycleDataLoaderTest.java
index 2d8ea12..b8a143a 100644
--- a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/net/NetworkCycleDataLoaderTest.java
+++ b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/net/NetworkCycleDataLoaderTest.java
@@ -130,7 +130,8 @@
             .thenReturn(networkHistory);
         final long now = System.currentTimeMillis();
         final long fourWeeksAgo = now - (DateUtils.WEEK_IN_MILLIS * 4);
-        when(networkHistory.getStart()).thenReturn(fourWeeksAgo);
+        final long twoDaysAgo = now - (DateUtils.DAY_IN_MILLIS * 2);
+        when(networkHistory.getStart()).thenReturn(twoDaysAgo);
         when(networkHistory.getEnd()).thenReturn(now);
 
         mLoader.loadFourWeeksData();
diff --git a/packages/SettingsProvider/src/com/android/providers/settings/SettingsProtoDumpUtil.java b/packages/SettingsProvider/src/com/android/providers/settings/SettingsProtoDumpUtil.java
index e843eb4..a1aefab 100644
--- a/packages/SettingsProvider/src/com/android/providers/settings/SettingsProtoDumpUtil.java
+++ b/packages/SettingsProvider/src/com/android/providers/settings/SettingsProtoDumpUtil.java
@@ -19,7 +19,6 @@
 import android.annotation.NonNull;
 import android.os.UserHandle;
 import android.provider.Settings;
-import android.provider.Settings.Global;
 import android.providers.settings.GlobalSettingsProto;
 import android.providers.settings.SecureSettingsProto;
 import android.providers.settings.SettingProto;
@@ -397,7 +396,7 @@
         p.end(certPinToken);
 
         dumpSetting(s, p,
-                Global.CHAINED_BATTERY_ATTRIBUTION_ENABLED,
+                Settings.Global.CHAINED_BATTERY_ATTRIBUTION_ENABLED,
                 GlobalSettingsProto.CHAINED_BATTERY_ATTRIBUTION_ENABLED);
         dumpSetting(s, p,
                 Settings.Global.COMPATIBILITY_MODE,
@@ -699,6 +698,9 @@
                 Settings.Global.GLOBAL_SETTINGS_ANGLE_GL_DRIVER_SELECTION_VALUES,
                 GlobalSettingsProto.Gpu.ANGLE_GL_DRIVER_SELECTION_VALUES);
         dumpSetting(s, p,
+                Settings.Global.GLOBAL_SETTINGS_ANGLE_WHITELIST,
+                GlobalSettingsProto.Gpu.ANGLE_WHITELIST);
+        dumpSetting(s, p,
                 Settings.Global.GPU_DEBUG_LAYER_APP,
                 GlobalSettingsProto.Gpu.DEBUG_LAYER_APP);
         dumpSetting(s, p,
@@ -716,6 +718,9 @@
         dumpSetting(s, p,
                 Settings.Global.GUP_BLACKLIST,
                 GlobalSettingsProto.Gpu.GUP_BLACKLIST);
+        dumpSetting(s, p,
+                Settings.Global.GAME_DRIVER_WHITELIST,
+                GlobalSettingsProto.Gpu.GAME_DRIVER_WHITELIST);
         p.end(gpuToken);
 
         final long hdmiToken = p.start(GlobalSettingsProto.HDMI);
@@ -737,7 +742,7 @@
                 Settings.Global.HEADS_UP_NOTIFICATIONS_ENABLED,
                 GlobalSettingsProto.HEADS_UP_NOTIFICATIONS_ENABLED);
         dumpSetting(s, p,
-                Global.HIDDEN_API_BLACKLIST_EXEMPTIONS,
+                Settings.Global.HIDDEN_API_BLACKLIST_EXEMPTIONS,
                 GlobalSettingsProto.HIDDEN_API_BLACKLIST_EXEMPTIONS);
 
         final long inetCondToken = p.start(GlobalSettingsProto.INET_CONDITION);
@@ -817,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);
@@ -832,6 +840,15 @@
         dumpSetting(s, p,
                 Settings.Global.AUTOMATIC_POWER_SAVER_MODE,
                 GlobalSettingsProto.LowPowerMode.AUTOMATIC_POWER_SAVER_MODE);
+        dumpSetting(s, p,
+                Settings.Global.LOW_POWER_MODE_STICKY,
+                GlobalSettingsProto.LowPowerMode.STICKY_ENABLED);
+        dumpSetting(s, p,
+                Settings.Global.LOW_POWER_MODE_STICKY_AUTO_DISABLE_ENABLED,
+                GlobalSettingsProto.LowPowerMode.STICKY_AUTO_DISABLE_ENABLED);
+        dumpSetting(s, p,
+                Settings.Global.LOW_POWER_MODE_STICKY_AUTO_DISABLE_LEVEL,
+                GlobalSettingsProto.LowPowerMode.STICKY_AUTO_DISABLE_LEVEL);
         p.end(lpmToken);
 
         dumpSetting(s, p,
@@ -882,7 +899,7 @@
         p.end(multiSimToken);
 
         dumpSetting(s, p,
-                Global.NATIVE_FLAGS_HEALTH_CHECK_ENABLED,
+                Settings.Global.NATIVE_FLAGS_HEALTH_CHECK_ENABLED,
                 GlobalSettingsProto.NATIVE_FLAGS_HEALTH_CHECK_ENABLED);
 
         final long netstatsToken = p.start(GlobalSettingsProto.NETSTATS);
@@ -1109,9 +1126,6 @@
         dumpSetting(s, p,
                 Settings.Global.POWER_MANAGER_CONSTANTS,
                 GlobalSettingsProto.POWER_MANAGER_CONSTANTS);
-        dumpSetting(s, p,
-                Settings.Global.PRIV_APP_OOB_ENABLED,
-                GlobalSettingsProto.PRIV_APP_OOB_ENABLED);
 
         final long prepaidSetupToken = p.start(GlobalSettingsProto.PREPAID_SETUP);
         dumpSetting(s, p,
@@ -1262,10 +1276,10 @@
 
         final long soundTriggerToken = p.start(GlobalSettingsProto.SOUND_TRIGGER);
         dumpSetting(s, p,
-                Global.MAX_SOUND_TRIGGER_DETECTION_SERVICE_OPS_PER_DAY,
+                Settings.Global.MAX_SOUND_TRIGGER_DETECTION_SERVICE_OPS_PER_DAY,
                 GlobalSettingsProto.SoundTrigger.MAX_SOUND_TRIGGER_DETECTION_SERVICE_OPS_PER_DAY);
         dumpSetting(s, p,
-                Global.SOUND_TRIGGER_DETECTION_SERVICE_OP_TIMEOUT,
+                Settings.Global.SOUND_TRIGGER_DETECTION_SERVICE_OP_TIMEOUT,
                 GlobalSettingsProto.SoundTrigger.DETECTION_SERVICE_OP_TIMEOUT_MS);
         p.end(soundTriggerToken);
 
@@ -1561,7 +1575,7 @@
                 GlobalSettingsProto.ZRAM_ENABLED);
 
         dumpSetting(s, p,
-                Global.APP_OPS_CONSTANTS,
+                Settings.Global.APP_OPS_CONSTANTS,
                 GlobalSettingsProto.APP_OPS_CONSTANTS);
 
         p.end(token);
@@ -1700,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,
@@ -2377,6 +2397,14 @@
                 Settings.Secure.SILENCE_GESTURE,
                 SecureSettingsProto.SILENCE_GESTURE_ENABLED);
 
+        dumpSetting(s, p,
+                Settings.Secure.THEME_CUSTOMIZATION_OVERLAY_PACKAGES,
+                SecureSettingsProto.THEME_CUSTOMIZATION_OVERLAY_PACKAGES);
+
+        dumpSetting(s, p,
+                Settings.Secure.AWARE_ENABLED,
+                SecureSettingsProto.AWARE_ENABLED);
+
         // Please insert new settings using the same order as in SecureSettingsProto.
         p.end(token);
 
diff --git a/packages/SystemUI/Android.bp b/packages/SystemUI/Android.bp
index 8be67d9..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",
@@ -73,7 +74,7 @@
         "com.android.keyguard",
     ],
 
-    annotation_processors: ["dagger2-compiler-2.19"],
+    plugins: ["dagger2-compiler-2.19"],
 }
 
 android_library {
@@ -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",
@@ -127,7 +129,7 @@
         "--extra-packages",
         "com.android.keyguard:com.android.systemui",
     ],
-    annotation_processors: ["dagger2-compiler-2.19"],
+    plugins: ["dagger2-compiler-2.19"],
 }
 
 android_app {
diff --git a/packages/SystemUI/AndroidManifest.xml b/packages/SystemUI/AndroidManifest.xml
index b4f2711..3453e79 100644
--- a/packages/SystemUI/AndroidManifest.xml
+++ b/packages/SystemUI/AndroidManifest.xml
@@ -386,6 +386,15 @@
             android:excludeFromRecents="true">
         </activity>
 
+        <!-- started from UsbPortManager -->
+        <activity android:name=".usb.UsbContaminantActivity"
+            android:exported="true"
+            android:permission="android.permission.MANAGE_USB"
+            android:theme="@style/Theme.SystemUI.Dialog.Alert"
+            android:finishOnCloseSystemDialogs="true"
+            android:excludeFromRecents="true">
+        </activity>
+
         <!-- started from AdbDebuggingManager -->
         <activity android:name=".usb.UsbDebuggingActivity"
             android:permission="android.permission.MANAGE_DEBUGGING"
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/plugin/src/com/android/systemui/plugins/statusbar/NotificationMenuRowPlugin.java b/packages/SystemUI/plugin/src/com/android/systemui/plugins/statusbar/NotificationMenuRowPlugin.java
index 0b1dab1..fc84332 100644
--- a/packages/SystemUI/plugin/src/com/android/systemui/plugins/statusbar/NotificationMenuRowPlugin.java
+++ b/packages/SystemUI/plugin/src/com/android/systemui/plugins/statusbar/NotificationMenuRowPlugin.java
@@ -20,14 +20,14 @@
 import android.view.View;
 import android.view.ViewGroup;
 
-import java.util.ArrayList;
-
 import com.android.systemui.plugins.Plugin;
 import com.android.systemui.plugins.annotations.DependsOn;
 import com.android.systemui.plugins.annotations.ProvidesInterface;
+import com.android.systemui.plugins.statusbar.NotificationMenuRowPlugin.MenuItem;
 import com.android.systemui.plugins.statusbar.NotificationMenuRowPlugin.OnMenuEventListener;
 import com.android.systemui.plugins.statusbar.NotificationSwipeActionHelper.SnoozeOption;
-import com.android.systemui.plugins.statusbar.NotificationMenuRowPlugin.MenuItem;
+
+import java.util.ArrayList;
 
 @ProvidesInterface(action = NotificationMenuRowPlugin.ACTION,
         version = NotificationMenuRowPlugin.VERSION)
@@ -149,6 +149,12 @@
     public boolean canBeDismissed();
 
     /**
+     * Informs the menu whether dismiss gestures are left-to-right or right-to-left.
+     */
+    default void setDismissRtl(boolean dismissRtl) {
+    }
+
+    /**
      * Determines whether the menu should remain open given its current state, or snap closed.
      * @return true if the menu should remain open, false otherwise.
      */
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..abf9e05 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>
 
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 13d27bb..ffa5de8 100644
--- a/packages/SystemUI/res/values/strings.xml
+++ b/packages/SystemUI/res/values/strings.xml
@@ -159,6 +159,12 @@
     <!-- Message of notification shown when trying to enable USB debugging but a secondary user is the current foreground user. -->
     <string name="usb_debugging_secondary_user_message">The user currently signed in to this device can\'t turn on USB debugging. To use this feature, switch to the primary user.</string>
 
+    <!-- Title of USB contaminant presence dialog [CHAR LIMIT=NONE] -->
+    <string name="usb_contaminant_title">USB port disabled</string>
+
+    <!-- Message of USB contaminant presence dialog [CHAR LIMIT=NONE] -->
+    <string name="usb_contaminant_message">To protect your device from liquid or debris, the USB port is disabled and won\u2019t detect any accessories.\n\nYou\u2019ll be notified when it\u2019s safe to use the USB port again.</string>
+
     <!-- Checkbox label for application compatibility mode ON (zooming app to look like it's running
          on a phone).  [CHAR LIMIT=25] -->
     <string name="compat_mode_on">Zoom to fill screen</string>
@@ -2299,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>
@@ -2331,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 b7d5197..adcb7a1 100644
--- a/packages/SystemUI/src/com/android/keyguard/CarrierText.java
+++ b/packages/SystemUI/src/com/android/keyguard/CarrierText.java
@@ -17,29 +17,14 @@
 package com.android.keyguard;
 
 import android.content.Context;
-import android.content.Intent;
-import android.content.IntentFilter;
 import android.content.res.TypedArray;
-import android.net.ConnectivityManager;
-import android.net.wifi.WifiManager;
-import android.telephony.ServiceState;
-import android.telephony.SubscriptionInfo;
-import android.telephony.TelephonyManager;
 import android.text.TextUtils;
 import android.text.method.SingleLineTransformationMethod;
 import android.util.AttributeSet;
-import android.util.Log;
 import android.view.View;
 import android.widget.TextView;
 
-import com.android.internal.telephony.IccCardConstants;
-import com.android.internal.telephony.IccCardConstants.State;
-import com.android.internal.telephony.TelephonyIntents;
-import com.android.settingslib.WirelessUtils;
-
-import java.util.List;
 import java.util.Locale;
-import java.util.Objects;
 
 public class CarrierText extends TextView {
     private static final boolean DEBUG = KeyguardConstants.DEBUG;
@@ -47,77 +32,30 @@
 
     private static CharSequence mSeparator;
 
-    private final boolean mIsEmergencyCallCapable;
-
-    private boolean mTelephonyCapable;
-
     private boolean mShowMissingSim;
 
     private boolean mShowAirplaneMode;
+    private boolean mShouldMarquee;
 
-    private KeyguardUpdateMonitor mKeyguardUpdateMonitor;
+    private CarrierTextController mCarrierTextController;
 
-    private WifiManager mWifiManager;
+    private CarrierTextController.CarrierTextCallback mCarrierTextCallback =
+            new CarrierTextController.CarrierTextCallback() {
+                @Override
+                public void updateCarrierInfo(CarrierTextController.CarrierTextCallbackInfo info) {
+                    setText(info.carrierText);
+                }
 
-    private boolean[] mSimErrorState = new boolean[TelephonyManager.getDefault().getPhoneCount()];
+                @Override
+                public void startedGoingToSleep() {
+                    setSelected(false);
+                }
 
-    private final KeyguardUpdateMonitorCallback mCallback = new KeyguardUpdateMonitorCallback() {
-        @Override
-        public void onRefreshCarrierInfo() {
-            if (DEBUG) Log.d(TAG, "onRefreshCarrierInfo(), mTelephonyCapable: "
-                    + Boolean.toString(mTelephonyCapable));
-            updateCarrierText();
-        }
-
-        public void onFinishedGoingToSleep(int why) {
-            setSelected(false);
-        };
-
-        public void onStartedWakingUp() {
-            setSelected(true);
-        };
-
-        @Override
-        public void onTelephonyCapable(boolean capable) {
-            if (DEBUG) Log.d(TAG, "onTelephonyCapable() mTelephonyCapable: "
-                    + Boolean.toString(capable));
-            mTelephonyCapable = capable;
-            updateCarrierText();
-        }
-
-        public void onSimStateChanged(int subId, int slotId, IccCardConstants.State simState) {
-            if (slotId < 0) {
-                Log.d(TAG, "onSimStateChanged() - slotId invalid: " + slotId
-                        + " mTelephonyCapable: " + Boolean.toString(mTelephonyCapable));
-                return;
-            }
-
-            if (DEBUG) Log.d(TAG,"onSimStateChanged: " + getStatusForIccState(simState));
-            if (getStatusForIccState(simState) == StatusMode.SimIoError) {
-                mSimErrorState[slotId] = true;
-                updateCarrierText();
-            } else if (mSimErrorState[slotId]) {
-                mSimErrorState[slotId] = false;
-                updateCarrierText();
-            }
-        }
-    };
-
-    /**
-     * The status of this lock screen. Primarily used for widgets on LockScreen.
-     */
-    private static enum StatusMode {
-        Normal, // Normal case (sim card present, it's not locked)
-        NetworkLocked, // SIM card is 'network locked'.
-        SimMissing, // SIM card is missing.
-        SimMissingLocked, // SIM card is missing, and device isn't provisioned; don't allow access
-        SimPukLocked, // SIM card is PUK locked because SIM entered wrong too many times
-        SimLocked, // SIM card is currently locked
-        SimPermDisabled, // SIM card is permanently disabled due to PUK unlock failure
-        SimNotReady, // SIM is not ready yet. May never be on devices w/o a SIM.
-        SimIoError, // SIM card is faulty
-        SimUnknown // SIM card is unknown
-    }
+                @Override
+                public void finishedWakingUp() {
+                    setSelected(true);
+                }
+            };
 
     public CarrierText(Context context) {
         this(context, null);
@@ -125,8 +63,6 @@
 
     public CarrierText(Context context, AttributeSet attrs) {
         super(context, attrs);
-        mIsEmergencyCallCapable = context.getResources().getBoolean(
-                com.android.internal.R.bool.config_voice_capable);
         boolean useAllCaps;
         TypedArray a = context.getTheme().obtainStyledAttributes(
                 attrs, R.styleable.CarrierText, 0, 0);
@@ -138,132 +74,6 @@
             a.recycle();
         }
         setTransformationMethod(new CarrierTextTransformationMethod(mContext, useAllCaps));
-
-        mWifiManager = (WifiManager) context.getSystemService(Context.WIFI_SERVICE);
-    }
-
-    /**
-     * Checks if there are faulty cards. Adds the text depending on the slot of the card
-     * @param text: current carrier text based on the sim state
-     * @param noSims: whether a valid sim card is inserted
-     * @return text
-    */
-    private CharSequence updateCarrierTextWithSimIoError(CharSequence text, boolean noSims) {
-        final CharSequence carrier = "";
-        CharSequence carrierTextForSimIOError = getCarrierTextForSimState(
-            IccCardConstants.State.CARD_IO_ERROR, carrier);
-        for (int index = 0; index < mSimErrorState.length; index++) {
-            if (mSimErrorState[index]) {
-                // In the case when no sim cards are detected but a faulty card is inserted
-                // overwrite the text and only show "Invalid card"
-                if (noSims) {
-                    return concatenate(carrierTextForSimIOError,
-                        getContext().getText(com.android.internal.R.string.emergency_calls_only));
-                } else if (index == 0) {
-                    // prepend "Invalid card" when faulty card is inserted in slot 0
-                    text = concatenate(carrierTextForSimIOError, text);
-                } else {
-                    // concatenate "Invalid card" when faulty card is inserted in slot 1
-                    text = concatenate(text, carrierTextForSimIOError);
-                }
-            }
-        }
-        return text;
-    }
-
-    protected void updateCarrierText() {
-        boolean allSimsMissing = true;
-        boolean anySimReadyAndInService = false;
-        CharSequence displayText = null;
-
-        List<SubscriptionInfo> subs = mKeyguardUpdateMonitor.getSubscriptionInfo(false);
-        final int N = subs.size();
-        if (DEBUG) Log.d(TAG, "updateCarrierText(): " + N);
-        for (int i = 0; i < N; i++) {
-            int subId = subs.get(i).getSubscriptionId();
-            State simState = mKeyguardUpdateMonitor.getSimState(subId);
-            CharSequence carrierName = subs.get(i).getCarrierName();
-            CharSequence carrierTextForSimState = getCarrierTextForSimState(simState, carrierName);
-            if (DEBUG) {
-                Log.d(TAG, "Handling (subId=" + subId + "): " + simState + " " + carrierName);
-            }
-            if (carrierTextForSimState != null) {
-                allSimsMissing = false;
-                displayText = concatenate(displayText, carrierTextForSimState);
-            }
-            if (simState == IccCardConstants.State.READY) {
-                ServiceState ss = mKeyguardUpdateMonitor.mServiceStates.get(subId);
-                if (ss != null && ss.getDataRegState() == ServiceState.STATE_IN_SERVICE) {
-                    // hack for WFC (IWLAN) not turning off immediately once
-                    // Wi-Fi is disassociated or disabled
-                    if (ss.getRilDataRadioTechnology() != ServiceState.RIL_RADIO_TECHNOLOGY_IWLAN
-                            || (mWifiManager.isWifiEnabled()
-                                    && mWifiManager.getConnectionInfo() != null
-                                    && mWifiManager.getConnectionInfo().getBSSID() != null)) {
-                        if (DEBUG) {
-                            Log.d(TAG, "SIM ready and in service: subId=" + subId + ", ss=" + ss);
-                        }
-                        anySimReadyAndInService = true;
-                    }
-                }
-            }
-        }
-        if (allSimsMissing) {
-            if (N != 0) {
-                // Shows "No SIM card | Emergency calls only" on devices that are voice-capable.
-                // This depends on mPlmn containing the text "Emergency calls only" when the radio
-                // has some connectivity. Otherwise, it should be null or empty and just show
-                // "No SIM card"
-                // Grab the first subscripton, because they all should contain the emergency text,
-                // described above.
-                displayText =  makeCarrierStringOnEmergencyCapable(
-                        getMissingSimMessage(), subs.get(0).getCarrierName());
-            } else {
-                // We don't have a SubscriptionInfo to get the emergency calls only from.
-                // Grab it from the old sticky broadcast if possible instead. We can use it
-                // here because no subscriptions are active, so we don't have
-                // to worry about MSIM clashing.
-                CharSequence text =
-                        getContext().getText(com.android.internal.R.string.emergency_calls_only);
-                Intent i = getContext().registerReceiver(null,
-                        new IntentFilter(TelephonyIntents.SPN_STRINGS_UPDATED_ACTION));
-                if (i != null) {
-                    String spn = "";
-                    String plmn = "";
-                    if (i.getBooleanExtra(TelephonyIntents.EXTRA_SHOW_SPN, false)) {
-                        spn = i.getStringExtra(TelephonyIntents.EXTRA_SPN);
-                    }
-                    if (i.getBooleanExtra(TelephonyIntents.EXTRA_SHOW_PLMN, false)) {
-                        plmn = i.getStringExtra(TelephonyIntents.EXTRA_PLMN);
-                    }
-                    if (DEBUG) Log.d(TAG, "Getting plmn/spn sticky brdcst " + plmn + "/" + spn);
-                    if (Objects.equals(plmn, spn)) {
-                        text = plmn;
-                    } else {
-                        text = concatenate(plmn, spn);
-                    }
-                }
-                displayText =  makeCarrierStringOnEmergencyCapable(getMissingSimMessage(), text);
-            }
-        }
-
-        displayText = updateCarrierTextWithSimIoError(displayText, allSimsMissing);
-        // APM (airplane mode) != no carrier state. There are carrier services
-        // (e.g. WFC = Wi-Fi calling) which may operate in APM.
-        if (!anySimReadyAndInService && WirelessUtils.isAirplaneModeOn(mContext)) {
-            displayText = getAirplaneModeMessage();
-        }
-        setText(displayText);
-    }
-
-    private String getMissingSimMessage() {
-        return mShowMissingSim && mTelephonyCapable
-                ? getContext().getString(R.string.keyguard_missing_sim_message_short) : "";
-    }
-
-    private String getAirplaneModeMessage() {
-        return mShowAirplaneMode
-                ? getContext().getString(R.string.airplane_mode) : "";
     }
 
     @Override
@@ -271,36 +81,27 @@
         super.onFinishInflate();
         mSeparator = getResources().getString(
                 com.android.internal.R.string.kg_text_message_separator);
-        boolean shouldMarquee = KeyguardUpdateMonitor.getInstance(mContext).isDeviceInteractive();
-        setSelected(shouldMarquee); // Allow marquee to work.
+        mCarrierTextController = new CarrierTextController(mContext, mSeparator, mShowAirplaneMode,
+                mShowMissingSim);
+        mShouldMarquee = KeyguardUpdateMonitor.getInstance(mContext).isDeviceInteractive();
+        setSelected(mShouldMarquee); // Allow marquee to work.
     }
 
     @Override
     protected void onAttachedToWindow() {
         super.onAttachedToWindow();
-        if (ConnectivityManager.from(mContext).isNetworkSupported(
-                ConnectivityManager.TYPE_MOBILE)) {
-            mKeyguardUpdateMonitor = KeyguardUpdateMonitor.getInstance(mContext);
-            mKeyguardUpdateMonitor.registerCallback(mCallback);
-        } else {
-            // Don't listen and clear out the text when the device isn't a phone.
-            mKeyguardUpdateMonitor = null;
-            setText("");
-        }
+        mCarrierTextController.setListening(mCarrierTextCallback);
     }
 
     @Override
     protected void onDetachedFromWindow() {
         super.onDetachedFromWindow();
-        if (mKeyguardUpdateMonitor != null) {
-            mKeyguardUpdateMonitor.removeCallback(mCallback);
-        }
+        mCarrierTextController.setListening(null);
     }
 
     @Override
     protected void onVisibilityChanged(View changedView, int visibility) {
         super.onVisibilityChanged(changedView, visibility);
-
         // Only show marquee when visible
         if (visibility == VISIBLE) {
             setEllipsize(TextUtils.TruncateAt.MARQUEE);
@@ -309,167 +110,6 @@
         }
     }
 
-    /**
-     * Top-level function for creating carrier text. Makes text based on simState, PLMN
-     * and SPN as well as device capabilities, such as being emergency call capable.
-     *
-     * @param simState
-     * @param text
-     * @param spn
-     * @return Carrier text if not in missing state, null otherwise.
-     */
-    private CharSequence getCarrierTextForSimState(IccCardConstants.State simState,
-            CharSequence text) {
-        CharSequence carrierText = null;
-        StatusMode status = getStatusForIccState(simState);
-        switch (status) {
-            case Normal:
-                carrierText = text;
-                break;
-
-            case SimNotReady:
-                // Null is reserved for denoting missing, in this case we have nothing to display.
-                carrierText = ""; // nothing to display yet.
-                break;
-
-            case NetworkLocked:
-                carrierText = makeCarrierStringOnEmergencyCapable(
-                        mContext.getText(R.string.keyguard_network_locked_message), text);
-                break;
-
-            case SimMissing:
-                carrierText = null;
-                break;
-
-            case SimPermDisabled:
-                carrierText = makeCarrierStringOnEmergencyCapable(
-                        getContext().getText(
-                                R.string.keyguard_permanent_disabled_sim_message_short),
-                        text);
-                break;
-
-            case SimMissingLocked:
-                carrierText = null;
-                break;
-
-            case SimLocked:
-                carrierText = makeCarrierStringOnEmergencyCapable(
-                        getContext().getText(R.string.keyguard_sim_locked_message),
-                        text);
-                break;
-
-            case SimPukLocked:
-                carrierText = makeCarrierStringOnEmergencyCapable(
-                        getContext().getText(R.string.keyguard_sim_puk_locked_message),
-                        text);
-                break;
-            case SimIoError:
-                carrierText = makeCarrierStringOnEmergencyCapable(
-                        getContext().getText(R.string.keyguard_sim_error_message_short),
-                        text);
-                break;
-            case SimUnknown:
-                carrierText = null;
-                break;
-        }
-
-        return carrierText;
-    }
-
-    /*
-     * Add emergencyCallMessage to carrier string only if phone supports emergency calls.
-     */
-    private CharSequence makeCarrierStringOnEmergencyCapable(
-            CharSequence simMessage, CharSequence emergencyCallMessage) {
-        if (mIsEmergencyCallCapable) {
-            return concatenate(simMessage, emergencyCallMessage);
-        }
-        return simMessage;
-    }
-
-    /**
-     * Determine the current status of the lock screen given the SIM state and other stuff.
-     */
-    private StatusMode getStatusForIccState(IccCardConstants.State simState) {
-        // Since reading the SIM may take a while, we assume it is present until told otherwise.
-        if (simState == null) {
-            return StatusMode.Normal;
-        }
-
-        final boolean missingAndNotProvisioned =
-                !KeyguardUpdateMonitor.getInstance(mContext).isDeviceProvisioned()
-                && (simState == IccCardConstants.State.ABSENT ||
-                        simState == IccCardConstants.State.PERM_DISABLED);
-
-        // Assume we're NETWORK_LOCKED if not provisioned
-        simState = missingAndNotProvisioned ? IccCardConstants.State.NETWORK_LOCKED : simState;
-        switch (simState) {
-            case ABSENT:
-                return StatusMode.SimMissing;
-            case NETWORK_LOCKED:
-                return StatusMode.SimMissingLocked;
-            case NOT_READY:
-                return StatusMode.SimNotReady;
-            case PIN_REQUIRED:
-                return StatusMode.SimLocked;
-            case PUK_REQUIRED:
-                return StatusMode.SimPukLocked;
-            case READY:
-                return StatusMode.Normal;
-            case PERM_DISABLED:
-                return StatusMode.SimPermDisabled;
-            case UNKNOWN:
-                return StatusMode.SimUnknown;
-            case CARD_IO_ERROR:
-                return StatusMode.SimIoError;
-        }
-        return StatusMode.SimUnknown;
-    }
-
-    private static CharSequence concatenate(CharSequence plmn, CharSequence spn) {
-        final boolean plmnValid = !TextUtils.isEmpty(plmn);
-        final boolean spnValid = !TextUtils.isEmpty(spn);
-        if (plmnValid && spnValid) {
-            return new StringBuilder().append(plmn).append(mSeparator).append(spn).toString();
-        } else if (plmnValid) {
-            return plmn;
-        } else if (spnValid) {
-            return spn;
-        } else {
-            return "";
-        }
-    }
-
-    private CharSequence getCarrierHelpTextForSimState(IccCardConstants.State simState,
-            String plmn, String spn) {
-        int carrierHelpTextId = 0;
-        StatusMode status = getStatusForIccState(simState);
-        switch (status) {
-            case NetworkLocked:
-                carrierHelpTextId = R.string.keyguard_instructions_when_pattern_disabled;
-                break;
-
-            case SimMissing:
-                carrierHelpTextId = R.string.keyguard_missing_sim_instructions_long;
-                break;
-
-            case SimPermDisabled:
-                carrierHelpTextId = R.string.keyguard_permanent_disabled_sim_instructions;
-                break;
-
-            case SimMissingLocked:
-                carrierHelpTextId = R.string.keyguard_missing_sim_instructions;
-                break;
-
-            case Normal:
-            case SimLocked:
-            case SimPukLocked:
-                break;
-        }
-
-        return mContext.getText(carrierHelpTextId);
-    }
-
     private class CarrierTextTransformationMethod extends SingleLineTransformationMethod {
         private final Locale mLocale;
         private final boolean mAllCaps;
diff --git a/packages/SystemUI/src/com/android/keyguard/CarrierTextController.java b/packages/SystemUI/src/com/android/keyguard/CarrierTextController.java
new file mode 100644
index 0000000..2ce6965
--- /dev/null
+++ b/packages/SystemUI/src/com/android/keyguard/CarrierTextController.java
@@ -0,0 +1,518 @@
+/*
+ * 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.keyguard;
+
+import android.content.Context;
+import android.content.Intent;
+import android.content.IntentFilter;
+import android.net.ConnectivityManager;
+import android.net.wifi.WifiManager;
+import android.telephony.ServiceState;
+import android.telephony.SubscriptionInfo;
+import android.telephony.TelephonyManager;
+import android.text.TextUtils;
+import android.util.Log;
+
+import com.android.internal.telephony.IccCardConstants;
+import com.android.internal.telephony.TelephonyIntents;
+import com.android.settingslib.WirelessUtils;
+import com.android.systemui.Dependency;
+import com.android.systemui.keyguard.WakefulnessLifecycle;
+
+import java.util.List;
+import java.util.Objects;
+
+/**
+ * Controller that generates text including the carrier names and/or the status of all the SIM
+ * interfaces in the device. Through a callback, the updates can be retrieved either as a list or
+ * separated by a given separator {@link CharSequence}.
+ */
+public class CarrierTextController {
+    private static final boolean DEBUG = KeyguardConstants.DEBUG;
+    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;
+    private CharSequence mSeparator;
+    private WakefulnessLifecycle mWakefulnessLifecycle;
+    private final WakefulnessLifecycle.Observer mWakefulnessObserver =
+            new WakefulnessLifecycle.Observer() {
+                @Override
+                public void onFinishedWakingUp() {
+                    mCarrierTextCallback.finishedWakingUp();
+                }
+
+                @Override
+                public void onStartedGoingToSleep() {
+                    mCarrierTextCallback.startedGoingToSleep();
+                }
+            };
+
+    private final KeyguardUpdateMonitorCallback mCallback = new KeyguardUpdateMonitorCallback() {
+        @Override
+        public void onRefreshCarrierInfo() {
+            if (DEBUG) {
+                Log.d(TAG, "onRefreshCarrierInfo(), mTelephonyCapable: "
+                        + Boolean.toString(mTelephonyCapable));
+            }
+            updateCarrierText();
+        }
+
+        @Override
+        public void onTelephonyCapable(boolean capable) {
+            if (DEBUG) {
+                Log.d(TAG, "onTelephonyCapable() mTelephonyCapable: "
+                        + Boolean.toString(capable));
+            }
+            mTelephonyCapable = capable;
+            updateCarrierText();
+        }
+
+        public void onSimStateChanged(int subId, int slotId, IccCardConstants.State simState) {
+            if (slotId < 0) {
+                Log.d(TAG, "onSimStateChanged() - slotId invalid: " + slotId
+                        + " mTelephonyCapable: " + Boolean.toString(mTelephonyCapable));
+                return;
+            }
+
+            if (DEBUG) Log.d(TAG, "onSimStateChanged: " + getStatusForIccState(simState));
+            if (getStatusForIccState(simState) == CarrierTextController.StatusMode.SimIoError) {
+                mSimErrorState[slotId] = true;
+                updateCarrierText();
+            } else if (mSimErrorState[slotId]) {
+                mSimErrorState[slotId] = false;
+                updateCarrierText();
+            }
+        }
+    };
+
+    /**
+     * The status of this lock screen. Primarily used for widgets on LockScreen.
+     */
+    private enum StatusMode {
+        Normal, // Normal case (sim card present, it's not locked)
+        NetworkLocked, // SIM card is 'network locked'.
+        SimMissing, // SIM card is missing.
+        SimMissingLocked, // SIM card is missing, and device isn't provisioned; don't allow access
+        SimPukLocked, // SIM card is PUK locked because SIM entered wrong too many times
+        SimLocked, // SIM card is currently locked
+        SimPermDisabled, // SIM card is permanently disabled due to PUK unlock failure
+        SimNotReady, // SIM is not ready yet. May never be on devices w/o a SIM.
+        SimIoError, // SIM card is faulty
+        SimUnknown // SIM card is unknown
+    }
+
+    /**
+     * Controller that provides updates on text with carriers names or SIM status.
+     * Used by {@link CarrierText}.
+     *
+     * @param separator Separator between different parts of the text
+     */
+    public CarrierTextController(Context context, CharSequence separator, boolean showAirplaneMode,
+            boolean showMissingSim) {
+        mContext = context;
+        mIsEmergencyCallCapable = context.getResources().getBoolean(
+                com.android.internal.R.bool.config_voice_capable);
+
+        mShowAirplaneMode = showAirplaneMode;
+        mShowMissingSim = showMissingSim;
+
+        mWifiManager = (WifiManager) context.getSystemService(Context.WIFI_SERVICE);
+        mSeparator = separator;
+        mWakefulnessLifecycle = Dependency.get(WakefulnessLifecycle.class);
+    }
+
+    /**
+     * Checks if there are faulty cards. Adds the text depending on the slot of the card
+     *
+     * @param text:   current carrier text based on the sim state
+     * @param noSims: whether a valid sim card is inserted
+     * @return text
+     */
+    private CharSequence updateCarrierTextWithSimIoError(CharSequence text, boolean noSims) {
+        final CharSequence carrier = "";
+        CharSequence carrierTextForSimIOError = getCarrierTextForSimState(
+                IccCardConstants.State.CARD_IO_ERROR, carrier);
+        for (int index = 0; index < mSimErrorState.length; index++) {
+            if (mSimErrorState[index]) {
+                // In the case when no sim cards are detected but a faulty card is inserted
+                // overwrite the text and only show "Invalid card"
+                if (noSims) {
+                    return concatenate(carrierTextForSimIOError,
+                            getContext().getText(
+                                    com.android.internal.R.string.emergency_calls_only),
+                            mSeparator);
+                } else if (index == 0) {
+                    // prepend "Invalid card" when faulty card is inserted in slot 0
+                    text = concatenate(carrierTextForSimIOError, text, mSeparator);
+                } else {
+                    // concatenate "Invalid card" when faulty card is inserted in slot 1
+                    text = concatenate(text, carrierTextForSimIOError, mSeparator);
+                }
+            }
+        }
+        return text;
+    }
+
+    /**
+     * 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) {
+        if (callback != null) {
+            mCarrierTextCallback = callback;
+            if (ConnectivityManager.from(mContext).isNetworkSupported(
+                    ConnectivityManager.TYPE_MOBILE)) {
+                mKeyguardUpdateMonitor = KeyguardUpdateMonitor.getInstance(mContext);
+                mKeyguardUpdateMonitor.registerCallback(mCallback);
+                mWakefulnessLifecycle.addObserver(mWakefulnessObserver);
+            } else {
+                // Don't listen and clear out the text when the device isn't a phone.
+                mKeyguardUpdateMonitor = null;
+                callback.updateCarrierInfo(new CarrierTextCallbackInfo("", null, false, null));
+            }
+        } else {
+            mCarrierTextCallback = null;
+            if (mKeyguardUpdateMonitor != null) {
+                mKeyguardUpdateMonitor.removeCallback(mCallback);
+                mWakefulnessLifecycle.removeObserver(mWakefulnessObserver);
+            }
+        }
+    }
+
+    protected void updateCarrierText() {
+        boolean allSimsMissing = true;
+        boolean anySimReadyAndInService = false;
+        CharSequence displayText = null;
+
+        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);
+            if (DEBUG) {
+                Log.d(TAG, "Handling (subId=" + subId + "): " + simState + " " + carrierName);
+            }
+            if (carrierTextForSimState != null) {
+                allSimsMissing = false;
+                displayText = concatenate(displayText, carrierTextForSimState, mSeparator);
+            }
+            if (simState == IccCardConstants.State.READY) {
+                ServiceState ss = mKeyguardUpdateMonitor.mServiceStates.get(subId);
+                if (ss != null && ss.getDataRegState() == ServiceState.STATE_IN_SERVICE) {
+                    // hack for WFC (IWLAN) not turning off immediately once
+                    // Wi-Fi is disassociated or disabled
+                    if (ss.getRilDataRadioTechnology() != ServiceState.RIL_RADIO_TECHNOLOGY_IWLAN
+                            || (mWifiManager.isWifiEnabled()
+                            && mWifiManager.getConnectionInfo() != null
+                            && mWifiManager.getConnectionInfo().getBSSID() != null)) {
+                        if (DEBUG) {
+                            Log.d(TAG, "SIM ready and in service: subId=" + subId + ", ss=" + ss);
+                        }
+                        anySimReadyAndInService = true;
+                    }
+                }
+            }
+        }
+        if (allSimsMissing) {
+            if (numSubs != 0) {
+                // Shows "No SIM card | Emergency calls only" on devices that are voice-capable.
+                // This depends on mPlmn containing the text "Emergency calls only" when the radio
+                // has some connectivity. Otherwise, it should be null or empty and just show
+                // "No SIM card"
+                // Grab the first subscripton, because they all should contain the emergency text,
+                // described above.
+                displayText = makeCarrierStringOnEmergencyCapable(
+                        getMissingSimMessage(), subs.get(0).getCarrierName());
+            } else {
+                // We don't have a SubscriptionInfo to get the emergency calls only from.
+                // Grab it from the old sticky broadcast if possible instead. We can use it
+                // here because no subscriptions are active, so we don't have
+                // to worry about MSIM clashing.
+                CharSequence text =
+                        getContext().getText(com.android.internal.R.string.emergency_calls_only);
+                Intent i = getContext().registerReceiver(null,
+                        new IntentFilter(TelephonyIntents.SPN_STRINGS_UPDATED_ACTION));
+                if (i != null) {
+                    String spn = "";
+                    String plmn = "";
+                    if (i.getBooleanExtra(TelephonyIntents.EXTRA_SHOW_SPN, false)) {
+                        spn = i.getStringExtra(TelephonyIntents.EXTRA_SPN);
+                    }
+                    if (i.getBooleanExtra(TelephonyIntents.EXTRA_SHOW_PLMN, false)) {
+                        plmn = i.getStringExtra(TelephonyIntents.EXTRA_PLMN);
+                    }
+                    if (DEBUG) Log.d(TAG, "Getting plmn/spn sticky brdcst " + plmn + "/" + spn);
+                    if (Objects.equals(plmn, spn)) {
+                        text = plmn;
+                    } else {
+                        text = concatenate(plmn, spn, mSeparator);
+                    }
+                }
+                displayText = makeCarrierStringOnEmergencyCapable(getMissingSimMessage(), text);
+            }
+        }
+
+        displayText = updateCarrierTextWithSimIoError(displayText, allSimsMissing);
+        // APM (airplane mode) != no carrier state. There are carrier services
+        // (e.g. WFC = Wi-Fi calling) which may operate in APM.
+        if (!anySimReadyAndInService && WirelessUtils.isAirplaneModeOn(mContext)) {
+            displayText = getAirplaneModeMessage();
+        }
+
+        if (mCarrierTextCallback != null) {
+            mCarrierTextCallback.updateCarrierInfo(new CarrierTextCallbackInfo(
+                    displayText,
+                    displayText.toString().split(mSeparator.toString()),
+                    anySimReadyAndInService,
+                    subsIds));
+        }
+
+    }
+
+    private Context getContext() {
+        return mContext;
+    }
+
+    private String getMissingSimMessage() {
+        return mShowMissingSim && mTelephonyCapable
+                ? getContext().getString(R.string.keyguard_missing_sim_message_short) : "";
+    }
+
+    private String getAirplaneModeMessage() {
+        return mShowAirplaneMode
+                ? getContext().getString(R.string.airplane_mode) : "";
+    }
+
+    /**
+     * Top-level function for creating carrier text. Makes text based on simState, PLMN
+     * and SPN as well as device capabilities, such as being emergency call capable.
+     *
+     * @return Carrier text if not in missing state, null otherwise.
+     */
+    private CharSequence getCarrierTextForSimState(IccCardConstants.State simState,
+            CharSequence text) {
+        CharSequence carrierText = null;
+        CarrierTextController.StatusMode status = getStatusForIccState(simState);
+        switch (status) {
+            case Normal:
+                carrierText = text;
+                break;
+
+            case SimNotReady:
+                // Null is reserved for denoting missing, in this case we have nothing to display.
+                carrierText = ""; // nothing to display yet.
+                break;
+
+            case NetworkLocked:
+                carrierText = makeCarrierStringOnEmergencyCapable(
+                        mContext.getText(R.string.keyguard_network_locked_message), text);
+                break;
+
+            case SimMissing:
+                carrierText = null;
+                break;
+
+            case SimPermDisabled:
+                carrierText = makeCarrierStringOnEmergencyCapable(
+                        getContext().getText(
+                                R.string.keyguard_permanent_disabled_sim_message_short),
+                        text);
+                break;
+
+            case SimMissingLocked:
+                carrierText = null;
+                break;
+
+            case SimLocked:
+                carrierText = makeCarrierStringOnEmergencyCapable(
+                        getContext().getText(R.string.keyguard_sim_locked_message),
+                        text);
+                break;
+
+            case SimPukLocked:
+                carrierText = makeCarrierStringOnEmergencyCapable(
+                        getContext().getText(R.string.keyguard_sim_puk_locked_message),
+                        text);
+                break;
+            case SimIoError:
+                carrierText = makeCarrierStringOnEmergencyCapable(
+                        getContext().getText(R.string.keyguard_sim_error_message_short),
+                        text);
+                break;
+            case SimUnknown:
+                carrierText = null;
+                break;
+        }
+
+        return carrierText;
+    }
+
+    /*
+     * Add emergencyCallMessage to carrier string only if phone supports emergency calls.
+     */
+    private CharSequence makeCarrierStringOnEmergencyCapable(
+            CharSequence simMessage, CharSequence emergencyCallMessage) {
+        if (mIsEmergencyCallCapable) {
+            return concatenate(simMessage, emergencyCallMessage, mSeparator);
+        }
+        return simMessage;
+    }
+
+    /**
+     * Determine the current status of the lock screen given the SIM state and other stuff.
+     */
+    private CarrierTextController.StatusMode getStatusForIccState(IccCardConstants.State simState) {
+        // Since reading the SIM may take a while, we assume it is present until told otherwise.
+        if (simState == null) {
+            return CarrierTextController.StatusMode.Normal;
+        }
+
+        final boolean missingAndNotProvisioned =
+                !KeyguardUpdateMonitor.getInstance(mContext).isDeviceProvisioned()
+                        && (simState == IccCardConstants.State.ABSENT
+                        || simState == IccCardConstants.State.PERM_DISABLED);
+
+        // Assume we're NETWORK_LOCKED if not provisioned
+        simState = missingAndNotProvisioned ? IccCardConstants.State.NETWORK_LOCKED : simState;
+        switch (simState) {
+            case ABSENT:
+                return CarrierTextController.StatusMode.SimMissing;
+            case NETWORK_LOCKED:
+                return CarrierTextController.StatusMode.SimMissingLocked;
+            case NOT_READY:
+                return CarrierTextController.StatusMode.SimNotReady;
+            case PIN_REQUIRED:
+                return CarrierTextController.StatusMode.SimLocked;
+            case PUK_REQUIRED:
+                return CarrierTextController.StatusMode.SimPukLocked;
+            case READY:
+                return CarrierTextController.StatusMode.Normal;
+            case PERM_DISABLED:
+                return CarrierTextController.StatusMode.SimPermDisabled;
+            case UNKNOWN:
+                return CarrierTextController.StatusMode.SimUnknown;
+            case CARD_IO_ERROR:
+                return CarrierTextController.StatusMode.SimIoError;
+        }
+        return CarrierTextController.StatusMode.SimUnknown;
+    }
+
+    private static CharSequence concatenate(CharSequence plmn, CharSequence spn,
+            CharSequence separator) {
+        final boolean plmnValid = !TextUtils.isEmpty(plmn);
+        final boolean spnValid = !TextUtils.isEmpty(spn);
+        if (plmnValid && spnValid) {
+            return new StringBuilder().append(plmn).append(separator).append(spn).toString();
+        } else if (plmnValid) {
+            return plmn;
+        } else if (spnValid) {
+            return spn;
+        } else {
+            return "";
+        }
+    }
+
+    private static List<CharSequence> append(List<CharSequence> list, CharSequence string) {
+        if (!TextUtils.isEmpty(string)) {
+            list.add(string);
+        }
+        return list;
+    }
+
+    private CharSequence getCarrierHelpTextForSimState(IccCardConstants.State simState,
+            String plmn, String spn) {
+        int carrierHelpTextId = 0;
+        CarrierTextController.StatusMode status = getStatusForIccState(simState);
+        switch (status) {
+            case NetworkLocked:
+                carrierHelpTextId = R.string.keyguard_instructions_when_pattern_disabled;
+                break;
+
+            case SimMissing:
+                carrierHelpTextId = R.string.keyguard_missing_sim_instructions_long;
+                break;
+
+            case SimPermDisabled:
+                carrierHelpTextId = R.string.keyguard_permanent_disabled_sim_instructions;
+                break;
+
+            case SimMissingLocked:
+                carrierHelpTextId = R.string.keyguard_missing_sim_instructions;
+                break;
+
+            case Normal:
+            case SimLocked:
+            case SimPukLocked:
+                break;
+        }
+
+        return mContext.getText(carrierHelpTextId);
+    }
+
+    /**
+     * 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 updated carrier information.
+         */
+        default void updateCarrierInfo(CarrierTextCallbackInfo info) {};
+
+        /**
+         * Notifies the View that the device is going to sleep
+         */
+        default void startedGoingToSleep() {};
+
+        /**
+         * Notifies the View that the device finished waking up
+         */
+        default void finishedWakingUp() {};
+    }
+}
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/assist/AssistManager.java b/packages/SystemUI/src/com/android/systemui/assist/AssistManager.java
index 53cdee5..4d70890 100644
--- a/packages/SystemUI/src/com/android/systemui/assist/AssistManager.java
+++ b/packages/SystemUI/src/com/android/systemui/assist/AssistManager.java
@@ -47,6 +47,10 @@
 public class AssistManager implements ConfigurationChangedReceiver {
 
     private static final String TAG = "AssistManager";
+
+    // Note that VERBOSE logging may leak PII (e.g. transcription contents).
+    private static final boolean VERBOSE = false;
+
     private static final String ASSIST_ICON_METADATA_NAME =
             "com.android.systemui.action_assist_icon";
 
@@ -103,16 +107,41 @@
     protected void registerVoiceInteractionSessionListener() {
         mAssistUtils.registerVoiceInteractionSessionListener(
                 new IVoiceInteractionSessionListener.Stub() {
-            @Override
-            public void onVoiceSessionShown() throws RemoteException {
-                Log.v(TAG, "Voice open");
-            }
+                    @Override
+                    public void onVoiceSessionShown() throws RemoteException {
+                        if (VERBOSE) {
+                            Log.v(TAG, "Voice open");
+                        }
+                    }
 
-            @Override
-            public void onVoiceSessionHidden() throws RemoteException {
-                Log.v(TAG, "Voice closed");
-            }
-        });
+                    @Override
+                    public void onVoiceSessionHidden() throws RemoteException {
+                        if (VERBOSE) {
+                            Log.v(TAG, "Voice closed");
+                        }
+                    }
+
+                    @Override
+                    public void onTranscriptionUpdate(String transcription) {
+                        if (VERBOSE) {
+                            Log.v(TAG, "Transcription Updated: \"" + transcription + "\"");
+                        }
+                    }
+
+                    @Override
+                    public void onTranscriptionComplete(boolean immediate) {
+                        if (VERBOSE) {
+                            Log.v(TAG, "Transcription complete (immediate=" + immediate + ")");
+                        }
+                    }
+
+                    @Override
+                    public void onVoiceStateChange(int state) {
+                        if (VERBOSE) {
+                            Log.v(TAG, "Voice state is now " + state);
+                        }
+                    }
+                });
     }
 
     public void onConfigurationChanged(Configuration newConfiguration) {
@@ -291,8 +320,10 @@
                     }
                 }
             } catch (PackageManager.NameNotFoundException e) {
-                Log.v(TAG, "Assistant component "
-                        + component.flattenToShortString() + " not found");
+                if (VERBOSE) {
+                    Log.v(TAG, "Assistant component "
+                            + component.flattenToShortString() + " not found");
+                }
             } catch (Resources.NotFoundException nfe) {
                 Log.w(TAG, "Failed to swap drawable from "
                         + component.flattenToShortString(), nfe);
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..b7bee30 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.
      */
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 7b6e79b..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;
@@ -33,6 +33,7 @@
 import android.view.View;
 import android.view.ViewGroup;
 import android.widget.FrameLayout;
+import android.widget.LinearLayout;
 import android.widget.TextView;
 
 import com.android.internal.graphics.ColorUtils;
@@ -59,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);
@@ -191,10 +194,10 @@
                         fraction = showDot ? fraction : 1 - fraction;
                         mBadgedImageView.setDotScale(fraction);
                     }).withEndAction(() -> {
-                        if (!showDot) {
-                            mBadgedImageView.setShowDot(false);
-                        }
-                    }).start();
+                if (!showDot) {
+                    mBadgedImageView.setShowDot(false);
+                }
+            }).start();
         }
     }
 
@@ -231,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;
     }
@@ -244,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 ViewGroup.LayoutParams(
+            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/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/NightDisplayTile.java b/packages/SystemUI/src/com/android/systemui/qs/tiles/NightDisplayTile.java
index b04132d..43ce0b5 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/tiles/NightDisplayTile.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/NightDisplayTile.java
@@ -82,7 +82,7 @@
         if ("1".equals(Settings.Global.getString(mContext.getContentResolver(),
                 Settings.Global.NIGHT_DISPLAY_FORCED_AUTO_MODE_AVAILABLE))
                 && mController.getAutoModeRaw() == -1) {
-            mController.setAutoMode(ColorDisplayController.AUTO_MODE_CUSTOM);
+            mController.setAutoMode(ColorDisplayManager.AUTO_MODE_CUSTOM_TIME);
             Log.i("NightDisplayTile", "Enrolled in forced night display auto mode");
         }
 
@@ -127,7 +127,7 @@
     @Nullable
     private String getSecondaryLabel(boolean isNightLightActivated) {
         switch(mController.getAutoMode()) {
-            case ColorDisplayController.AUTO_MODE_TWILIGHT:
+            case ColorDisplayManager.AUTO_MODE_TWILIGHT:
                 // Auto mode related to sunrise & sunset. If the light is on, it's guaranteed to be
                 // turned off at sunrise. If it's off, it's guaranteed to be turned on at sunset.
                 return isNightLightActivated
@@ -136,7 +136,7 @@
                         : mContext.getString(
                                 R.string.quick_settings_night_secondary_label_on_at_sunset);
 
-            case ColorDisplayController.AUTO_MODE_CUSTOM:
+            case ColorDisplayManager.AUTO_MODE_CUSTOM_TIME:
                 // User-specified time, approximated to the nearest hour.
                 final @StringRes int toggleTimeStringRes;
                 final LocalTime toggleTime;
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/MediaArtworkProcessor.kt b/packages/SystemUI/src/com/android/systemui/statusbar/MediaArtworkProcessor.kt
new file mode 100644
index 0000000..4944732
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/MediaArtworkProcessor.kt
@@ -0,0 +1,80 @@
+/*
+ * 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.statusbar
+
+import android.annotation.ColorInt
+import android.content.Context
+import android.graphics.Bitmap
+import android.graphics.Canvas
+import android.graphics.Point
+import android.graphics.Rect
+import android.renderscript.Allocation
+import android.renderscript.Element
+import android.renderscript.RenderScript
+import android.renderscript.ScriptIntrinsicBlur
+import android.util.MathUtils
+import com.android.internal.graphics.ColorUtils
+
+import javax.inject.Inject
+import javax.inject.Singleton
+
+private const val COLOR_ALPHA = (255 * 0.7f).toInt()
+private const val BLUR_RADIUS = 25f
+private const val DOWNSAMPLE = 6
+
+@Singleton
+class MediaArtworkProcessor @Inject constructor() {
+
+    private val mTmpSize = Point()
+    private var mArtworkCache: Bitmap? = null
+
+    fun processArtwork(context: Context, artwork: Bitmap, @ColorInt color: Int): Bitmap {
+        if (mArtworkCache != null) {
+            return mArtworkCache!!
+        }
+
+        context.display.getSize(mTmpSize)
+        val renderScript = RenderScript.create(context)
+        val rect = Rect(0, 0,artwork.width, artwork.height)
+        MathUtils.fitRect(rect, Math.max(mTmpSize.x / DOWNSAMPLE, mTmpSize.y / DOWNSAMPLE))
+        val inBitmap = Bitmap.createScaledBitmap(artwork, rect.width(), rect.height(),
+                true /* filter */)
+        val input = Allocation.createFromBitmap(renderScript, inBitmap,
+                Allocation.MipmapControl.MIPMAP_NONE, Allocation.USAGE_GRAPHICS_TEXTURE)
+        val outBitmap = Bitmap.createBitmap(inBitmap.width, inBitmap.height,
+                Bitmap.Config.ARGB_8888)
+        val output = Allocation.createFromBitmap(renderScript, outBitmap)
+        val blur = ScriptIntrinsicBlur.create(renderScript, Element.U8_4(renderScript))
+        blur.setRadius(BLUR_RADIUS)
+        blur.setInput(input)
+        blur.forEach(output)
+        output.copyTo(outBitmap)
+
+        input.destroy()
+        output.destroy()
+        inBitmap.recycle()
+
+        val canvas = Canvas(outBitmap)
+        canvas.drawColor(ColorUtils.setAlphaComponent(color, COLOR_ALPHA))
+        return outBitmap
+    }
+
+    fun clearCache() {
+        mArtworkCache?.recycle()
+        mArtworkCache = null
+    }
+}
\ No newline at end of file
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/NotificationLockscreenUserManager.java b/packages/SystemUI/src/com/android/systemui/statusbar/NotificationLockscreenUserManager.java
index f46ded4d..c25b7cf 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/NotificationLockscreenUserManager.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/NotificationLockscreenUserManager.java
@@ -39,6 +39,9 @@
 
     boolean isCurrentProfile(int userId);
 
+    /** Adds a listener to be notified when the current user changes. */
+    void addUserChangedListener(UserChangedListener listener);
+
     void destroy();
 
     SparseArray<UserInfo> getCurrentProfiles();
@@ -58,4 +61,9 @@
     boolean needsRedaction(NotificationEntry entry);
 
     boolean userAllowsPrivateNotificationsInPublic(int currentUserId);
+
+    /** Notified when the current user changes. */
+    interface UserChangedListener {
+        void onUserChanged(int userId);
+    }
 }
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/NotificationLockscreenUserManagerImpl.java b/packages/SystemUI/src/com/android/systemui/statusbar/NotificationLockscreenUserManagerImpl.java
index d2ce31d..4f9d428 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/NotificationLockscreenUserManagerImpl.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/NotificationLockscreenUserManagerImpl.java
@@ -55,6 +55,8 @@
 
 import java.io.FileDescriptor;
 import java.io.PrintWriter;
+import java.util.ArrayList;
+import java.util.List;
 
 /**
  * Handles keeping track of the current user, profiles, and various things related to hiding
@@ -78,6 +80,7 @@
     private final SparseBooleanArray mUsersAllowingNotifications = new SparseBooleanArray();
     private final UserManager mUserManager;
     private final IStatusBarService mBarService;
+    private final List<UserChangedListener> mListeners = new ArrayList<>();
 
     private boolean mShowLockscreenNotifications;
     private boolean mAllowLockscreenRemoteInput;
@@ -112,6 +115,10 @@
                 updatePublicMode();
                 mPresenter.onUserSwitched(mCurrentUserId);
                 getEntryManager().getNotificationData().filterAndSort();
+
+                for (UserChangedListener listener : mListeners) {
+                    listener.onUserChanged(mCurrentUserId);
+                }
             } else if (Intent.ACTION_USER_ADDED.equals(action)) {
                 updateCurrentProfilesCache();
             } else if (Intent.ACTION_USER_UNLOCKED.equals(action)) {
@@ -502,6 +509,10 @@
         }
     }
 
+    @Override
+    public void addUserChangedListener(UserChangedListener listener) {
+        mListeners.add(listener);
+    }
 
 //    public void updatePublicMode() {
 //        //TODO: I think there may be a race condition where mKeyguardViewManager.isShowing() returns
@@ -541,6 +552,7 @@
     public void destroy() {
         mContext.unregisterReceiver(mBaseBroadcastReceiver);
         mContext.unregisterReceiver(mAllUsersReceiver);
+        mListeners.clear();
     }
 
     @Override
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/NotificationMediaManager.java b/packages/SystemUI/src/com/android/systemui/statusbar/NotificationMediaManager.java
index 7412702..98a3a54 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/NotificationMediaManager.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/NotificationMediaManager.java
@@ -25,6 +25,7 @@
 import android.app.Notification;
 import android.content.Context;
 import android.graphics.Bitmap;
+import android.graphics.Color;
 import android.graphics.drawable.BitmapDrawable;
 import android.graphics.drawable.ColorDrawable;
 import android.graphics.drawable.Drawable;
@@ -102,6 +103,7 @@
     private final Context mContext;
     private final MediaSessionManager mMediaSessionManager;
     private final ArrayList<MediaListener> mMediaListeners;
+    private final MediaArtworkProcessor mMediaArtworkProcessor;
 
     protected NotificationPresenter mPresenter;
     private MediaController mMediaController;
@@ -133,6 +135,7 @@
             if (DEBUG_MEDIA) {
                 Log.v(TAG, "DEBUG_MEDIA: onMetadataChanged: " + metadata);
             }
+            mMediaArtworkProcessor.clearCache();
             mMediaMetadata = metadata;
             dispatchUpdateMediaMetaData(true /* changed */, true /* allowAnimation */);
         }
@@ -143,8 +146,10 @@
             Context context,
             Lazy<ShadeController> shadeController,
             Lazy<StatusBarWindowController> statusBarWindowController,
-            NotificationEntryManager notificationEntryManager) {
+            NotificationEntryManager notificationEntryManager,
+            MediaArtworkProcessor mediaArtworkProcessor) {
         mContext = context;
+        mMediaArtworkProcessor = mediaArtworkProcessor;
         mMediaListeners = new ArrayList<>();
         mMediaSessionManager
                 = (MediaSessionManager) mContext.getSystemService(Context.MEDIA_SESSION_SERVICE);
@@ -366,6 +371,7 @@
     }
 
     private void clearCurrentMediaNotificationSession() {
+        mMediaArtworkProcessor.clearCache();
         mMediaMetadata = null;
         if (mMediaController != null) {
             if (DEBUG_MEDIA) {
@@ -418,7 +424,19 @@
                 // might still be null
             }
             if (artworkBitmap != null) {
-                artworkDrawable = new BitmapDrawable(mBackdropBack.getResources(), artworkBitmap);
+                int notificationColor;
+                synchronized (mEntryManager.getNotificationData()) {
+                    NotificationEntry entry = mEntryManager.getNotificationData()
+                            .get(mMediaNotificationKey);
+                    if (entry == null || entry.getRow() == null) {
+                        notificationColor = Color.TRANSPARENT;
+                    } else {
+                        notificationColor = entry.getRow().calculateBgColor();
+                    }
+                }
+                Bitmap bmp = mMediaArtworkProcessor.processArtwork(mContext, artworkBitmap,
+                        notificationColor);
+                artworkDrawable = new BitmapDrawable(mBackdropBack.getResources(), bmp);
             }
         }
         boolean allowWhenShade = false;
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/NotificationData.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/NotificationData.java
index 8c29fb5..54ed0d9 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/NotificationData.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/NotificationData.java
@@ -333,6 +333,7 @@
                         mGroupManager.onEntryUpdated(entry, oldSbn);
                     }
                     entry.populateFromRanking(mTmpRanking);
+                    entry.setIsHighPriority(isHighPriority(entry.notification));
                 }
             }
         }
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/NotificationEntry.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/NotificationEntry.java
index ee551ee..2e93c382 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/NotificationEntry.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/NotificationEntry.java
@@ -153,6 +153,12 @@
      */
     private boolean mUserDismissedBubble;
 
+    /**
+     * Whether this notification is shown to the user as a high priority notification: visible on
+     * the lock screen/status bar and in the top section in the shade.
+     */
+    private boolean mHighPriority;
+
     public NotificationEntry(StatusBarNotification n) {
         this(n, null);
     }
@@ -191,6 +197,14 @@
         return interruption;
     }
 
+    public boolean isHighPriority() {
+        return mHighPriority;
+    }
+
+    public void setIsHighPriority(boolean highPriority) {
+        this.mHighPriority = highPriority;
+    }
+
     public void setIsBubble(boolean bubbleable) {
         mIsBubble = bubbleable;
     }
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 9b1a2aa..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;
@@ -167,13 +166,10 @@
      */
     public static NotificationVisibility.NotificationLocation getNotificationLocation(
             NotificationEntry entry) {
-        ExpandableNotificationRow row = entry.getRow();
-        ExpandableViewState childViewState = row.getViewState();
-
-        if (childViewState == null) {
+        if (entry == null || entry.getRow() == null || entry.getRow().getViewState() == null) {
             return NotificationVisibility.NotificationLocation.LOCATION_UNKNOWN;
         }
-        return convertNotificationLocation(childViewState.location);
+        return convertNotificationLocation(entry.getRow().getViewState().location);
     }
 
     private static NotificationVisibility.NotificationLocation convertNotificationLocation(
@@ -224,6 +220,11 @@
             }
 
             @Override
+            public void onEntryReinflated(NotificationEntry entry) {
+                mExpansionStateLogger.onEntryReinflated(entry.key);
+            }
+
+            @Override
             public void onInflationError(
                     StatusBarNotification notification,
                     Exception exception) {
@@ -399,7 +400,9 @@
      * Called when the notification is expanded / collapsed.
      */
     public void onExpansionChanged(String key, boolean isUserAction, boolean isExpanded) {
-        mExpansionStateLogger.onExpansionChanged(key, isUserAction, isExpanded);
+        NotificationVisibility.NotificationLocation location =
+                getNotificationLocation(mEntryManager.getNotificationData().get(key));
+        mExpansionStateLogger.onExpansionChanged(key, isUserAction, isExpanded, location);
     }
 
     /**
@@ -433,10 +436,12 @@
         }
 
         @VisibleForTesting
-        void onExpansionChanged(String key, boolean isUserAction, boolean isExpanded) {
+        void onExpansionChanged(String key, boolean isUserAction, boolean isExpanded,
+                NotificationVisibility.NotificationLocation location) {
             State state = getState(key);
             state.mIsUserAction = isUserAction;
             state.mIsExpanded = isExpanded;
+            state.mLocation = location;
             maybeNotifyOnNotificationExpansionChanged(key, state);
         }
 
@@ -452,6 +457,7 @@
             for (NotificationVisibility nv : newlyVisibleAr) {
                 State state = getState(nv.key);
                 state.mIsVisible = true;
+                state.mLocation = nv.location;
                 maybeNotifyOnNotificationExpansionChanged(nv.key, state);
             }
             for (NotificationVisibility nv : noLongerVisibleAr) {
@@ -466,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) {
@@ -496,10 +509,8 @@
             final State stateToBeLogged = new State(state);
             mUiOffloadThread.submit(() -> {
                 try {
-                    mBarService.onNotificationExpansionChanged(
-                            key, stateToBeLogged.mIsUserAction, stateToBeLogged.mIsExpanded,
-                            // TODO (b/120767764): fill in location
-                            ExpandableViewState.LOCATION_UNKNOWN /* notificationLocation */);
+                    mBarService.onNotificationExpansionChanged(key, stateToBeLogged.mIsUserAction,
+                            stateToBeLogged.mIsExpanded, stateToBeLogged.mLocation.ordinal());
                 } catch (RemoteException e) {
                     Log.e(TAG, "Failed to call onNotificationExpansionChanged: ", e);
                 }
@@ -513,6 +524,8 @@
             Boolean mIsExpanded;
             @Nullable
             Boolean mIsVisible;
+            @Nullable
+            NotificationVisibility.NotificationLocation mLocation;
 
             private State() {}
 
@@ -520,10 +533,12 @@
                 this.mIsUserAction = state.mIsUserAction;
                 this.mIsExpanded = state.mIsExpanded;
                 this.mIsVisible = state.mIsVisible;
+                this.mLocation = state.mLocation;
             }
 
             private boolean isFullySet() {
-                return mIsUserAction != null && mIsExpanded != null && mIsVisible != null;
+                return mIsUserAction != null && mIsExpanded != null && mIsVisible != null
+                        && mLocation != null;
             }
         }
     }
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ExpandableNotificationRow.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ExpandableNotificationRow.java
index 296c061..bed2426 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ExpandableNotificationRow.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ExpandableNotificationRow.java
@@ -3092,6 +3092,11 @@
         }
     }
 
+    /** Sets whether dismiss gestures are right-to-left (instead of left-to-right). */
+    public void setDismissRtl(boolean dismissRtl) {
+        mMenuRow.setDismissRtl(dismissRtl);
+    }
+
     private static class NotificationViewState extends ExpandableViewState {
 
         @Override
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationGutsManager.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationGutsManager.java
index bd1dfb1..cb1384c 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationGutsManager.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationGutsManager.java
@@ -25,6 +25,7 @@
 import android.content.Context;
 import android.content.Intent;
 import android.content.pm.PackageManager;
+import android.metrics.LogMaker;
 import android.net.Uri;
 import android.os.ServiceManager;
 import android.os.UserHandle;
@@ -159,7 +160,8 @@
         return bindGuts(row, mGutsMenuItem);
     }
 
-    private boolean bindGuts(final ExpandableNotificationRow row,
+    @VisibleForTesting
+    protected boolean bindGuts(final ExpandableNotificationRow row,
             NotificationMenuRowPlugin.MenuItem item) {
         StatusBarNotification sbn = row.getStatusBarNotification();
 
@@ -298,7 +300,8 @@
                 row.getIsNonblockable(),
                 isForBlockingHelper,
                 row.getEntry().userSentiment == USER_SENTIMENT_NEGATIVE,
-                row.getEntry().importance);
+                row.getEntry().importance,
+                row.getEntry().isHighPriority());
 
     }
 
@@ -389,7 +392,11 @@
             return false;
         }
 
-        mMetricsLogger.action(MetricsProto.MetricsEvent.ACTION_NOTE_CONTROLS);
+        LogMaker logMaker = (row.getStatusBarNotification() == null)
+                ? new LogMaker(MetricsProto.MetricsEvent.ACTION_NOTE_CONTROLS)
+                : row.getStatusBarNotification().getLogMaker();
+        mMetricsLogger.write(logMaker.setCategory(MetricsProto.MetricsEvent.ACTION_NOTE_CONTROLS)
+                    .setType(MetricsProto.MetricsEvent.TYPE_ACTION));
 
         // ensure that it's laid but not visible until actually laid out
         guts.setVisibility(View.INVISIBLE);
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 5253e38..2a9a815 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
@@ -21,7 +21,6 @@
 import static android.app.NotificationManager.IMPORTANCE_LOW;
 import static android.app.NotificationManager.IMPORTANCE_MIN;
 import static android.app.NotificationManager.IMPORTANCE_NONE;
-import static android.app.NotificationManager.IMPORTANCE_UNSPECIFIED;
 
 import android.animation.Animator;
 import android.animation.AnimatorListenerAdapter;
@@ -97,8 +96,12 @@
     private int mNumUniqueChannelsInRow;
     private NotificationChannel mSingleNotificationChannel;
     private int mStartingChannelImportance;
-    private int mStartingChannelOrNotificationImportance;
-    private int mChosenImportance;
+    private boolean mWasShownHighPriority;
+    /**
+     * The last importance level chosen by the user.  Null if the user has not chosen an importance
+     * level; non-null once the user takes an action which indicates an explicit preference.
+     */
+    @Nullable private Integer mChosenImportance;
     private boolean mIsSingleDefaultChannel;
     private boolean mIsNonblockable;
     private StatusBarNotification mSbn;
@@ -195,13 +198,14 @@
             final OnAppSettingsClickListener onAppSettingsClick,
             boolean isDeviceProvisioned,
             boolean isNonblockable,
-            int importance)
+            int importance,
+            boolean wasShownHighPriority)
             throws RemoteException {
         bindNotification(pm, iNotificationManager, pkg, notificationChannel,
                 numUniqueChannelsInRow, sbn, checkSaveListener, onSettingsClick,
                 onAppSettingsClick, isDeviceProvisioned, isNonblockable,
                 false /* isBlockingHelper */, false /* isUserSentimentNegative */,
-                importance);
+                importance, wasShownHighPriority);
     }
 
     public void bindNotification(
@@ -218,7 +222,8 @@
             boolean isNonblockable,
             boolean isForBlockingHelper,
             boolean isUserSentimentNegative,
-            int importance)
+            int importance,
+            boolean wasShownHighPriority)
             throws RemoteException {
         mINotificationManager = iNotificationManager;
         mMetricsLogger = Dependency.get(MetricsLogger.class);
@@ -231,10 +236,8 @@
         mCheckSaveListener = checkSaveListener;
         mOnSettingsClickListener = onSettingsClick;
         mSingleNotificationChannel = notificationChannel;
-        int channelImportance = mSingleNotificationChannel.getImportance();
-        mStartingChannelImportance = mChosenImportance = channelImportance;
-        mStartingChannelOrNotificationImportance =
-                channelImportance == IMPORTANCE_UNSPECIFIED ? importance : channelImportance;
+        mStartingChannelImportance = mSingleNotificationChannel.getImportance();
+        mWasShownHighPriority = wasShownHighPriority;
         mNegativeUserSentiment = isUserSentimentNegative;
         mIsNonblockable = isNonblockable;
         mIsForeground =
@@ -400,19 +403,27 @@
      * @return new LogMaker
      */
     private LogMaker importanceChangeLogMaker() {
+        Integer chosenImportance =
+                mChosenImportance != null ? mChosenImportance : mStartingChannelImportance;
         return new LogMaker(MetricsEvent.ACTION_SAVE_IMPORTANCE)
                 .setType(MetricsEvent.TYPE_ACTION)
-                .setSubtype(mChosenImportance - mStartingChannelImportance);
+                .setSubtype(chosenImportance - mStartingChannelImportance);
     }
 
     private boolean hasImportanceChanged() {
         return mSingleNotificationChannel != null
-                && mStartingChannelImportance != mChosenImportance;
+                && mChosenImportance != null
+                && (mStartingChannelImportance != mChosenImportance
+                || (mWasShownHighPriority && mChosenImportance < IMPORTANCE_DEFAULT)
+                || (!mWasShownHighPriority && mChosenImportance >= IMPORTANCE_DEFAULT));
     }
 
     private void saveImportance() {
         if (!mIsNonblockable
                 || mExitReason != NotificationCounters.BLOCKING_HELPER_STOP_NOTIFICATIONS) {
+            if (mChosenImportance == null) {
+                mChosenImportance = mStartingChannelImportance;
+            }
             updateImportance();
         }
     }
@@ -421,12 +432,15 @@
      * Commits the updated importance values on the background thread.
      */
     private void updateImportance() {
-        mMetricsLogger.write(importanceChangeLogMaker());
+        if (mChosenImportance != null) {
+            mMetricsLogger.write(importanceChangeLogMaker());
 
-        Handler bgHandler = new Handler(Dependency.get(Dependency.BG_LOOPER));
-        bgHandler.post(new UpdateImportanceRunnable(mINotificationManager, mPackageName, mAppUid,
-                mNumUniqueChannelsInRow == 1 ? mSingleNotificationChannel : null,
-                mStartingChannelImportance, mChosenImportance));
+            Handler bgHandler = new Handler(Dependency.get(Dependency.BG_LOOPER));
+            bgHandler.post(
+                    new UpdateImportanceRunnable(mINotificationManager, mPackageName, mAppUid,
+                            mNumUniqueChannelsInRow == 1 ? mSingleNotificationChannel : null,
+                            mStartingChannelImportance, mChosenImportance));
+        }
     }
 
     private void bindButtons() {
@@ -444,11 +458,8 @@
             TextView silent = findViewById(R.id.int_silent);
             TextView alert = findViewById(R.id.int_alert);
 
-            boolean isCurrentlyAlerting =
-                    mStartingChannelOrNotificationImportance >= IMPORTANCE_DEFAULT;
-
             block.setOnClickListener(mOnStopOrMinimizeNotifications);
-            if (isCurrentlyAlerting) {
+            if (mWasShownHighPriority) {
                 silent.setOnClickListener(mOnToggleSilent);
                 silent.setText(R.string.inline_silent_button_silent);
                 alert.setOnClickListener(mOnKeepShowing);
@@ -517,7 +528,7 @@
                 break;
             case ACTION_TOGGLE_SILENT:
                 mExitReason = NotificationCounters.BLOCKING_HELPER_TOGGLE_SILENT;
-                if (mStartingChannelOrNotificationImportance >= IMPORTANCE_DEFAULT) {
+                if (mWasShownHighPriority) {
                     mChosenImportance = IMPORTANCE_LOW;
                     confirmationText.setText(R.string.notification_channel_silenced);
                 } else {
@@ -584,9 +595,8 @@
 
     @Override
     public void onFinishedClosing() {
-        mStartingChannelImportance = mChosenImportance;
-        if (mChosenImportance != IMPORTANCE_UNSPECIFIED) {
-            mStartingChannelOrNotificationImportance = mChosenImportance;
+        if (mChosenImportance != null) {
+            mStartingChannelImportance = mChosenImportance;
         }
         mExitReason = NotificationCounters.BLOCKING_HELPER_DISMISSED;
 
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationMenuRow.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationMenuRow.java
index d97162c..d83a158 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationMenuRow.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationMenuRow.java
@@ -23,7 +23,6 @@
 import android.animation.ValueAnimator;
 import android.annotation.Nullable;
 import android.app.Notification;
-import android.app.NotificationManager;
 import android.content.Context;
 import android.content.res.Resources;
 import android.graphics.drawable.Drawable;
@@ -78,6 +77,8 @@
     private ArrayList<MenuItem> mRightMenuItems;
     private final Map<View, MenuItem> mMenuItemsByView = new ArrayMap<>();
     private OnMenuEventListener mMenuListener;
+    private boolean mDismissRtl;
+    private boolean mIsForeground;
 
     private ValueAnimator mFadeAnimator;
     private boolean mAnimating;
@@ -238,6 +239,8 @@
     }
 
     private void createMenuViews(boolean resetState, final boolean isForeground) {
+        mIsForeground = isForeground;
+
         final Resources res = mContext.getResources();
         mHorizSpaceForIcon = res.getDimensionPixelSize(R.dimen.notification_menu_icon_size);
         mVertSpaceForIcons = res.getDimensionPixelSize(R.dimen.notification_min_height);
@@ -250,12 +253,7 @@
         }
         mAppOpsItem = createAppOpsItem(mContext);
         if (NotificationUtils.useNewInterruptionModel(mContext)) {
-            int channelImportance = mParent.getEntry().channel.getImportance();
-            int effectiveImportance =
-                    channelImportance == NotificationManager.IMPORTANCE_UNSPECIFIED
-                            ? mParent.getEntry().importance : channelImportance;
-            mInfoItem = createInfoItem(mContext,
-                    effectiveImportance < NotificationManager.IMPORTANCE_DEFAULT);
+            mInfoItem = createInfoItem(mContext, !mParent.getEntry().isHighPriority());
         } else {
             mInfoItem = createInfoItem(mContext);
         }
@@ -268,10 +266,11 @@
             mRightMenuItems.add(mAppOpsItem);
             mLeftMenuItems.addAll(mRightMenuItems);
         } else {
-            mRightMenuItems.add(mInfoItem);
-            mRightMenuItems.add(mAppOpsItem);
+            ArrayList<MenuItem> menuItems = mDismissRtl ? mLeftMenuItems : mRightMenuItems;
+            menuItems.add(mInfoItem);
+            menuItems.add(mAppOpsItem);
             if (!isForeground) {
-                mRightMenuItems.add(mSnoozeItem);
+                menuItems.add(mSnoozeItem);
             }
         }
 
@@ -729,6 +728,14 @@
         return getParent().canViewBeDismissed();
     }
 
+    @Override
+    public void setDismissRtl(boolean dismissRtl) {
+        mDismissRtl = dismissRtl;
+        if (mMenuContainer != null) {
+            createMenuViews(true, mIsForeground);
+        }
+    }
+
     public static class NotificationMenuItem implements MenuItem {
         View mMenuView;
         GutsContent mGutsContent;
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/notification/stack/NotificationStackScrollLayout.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayout.java
index 2129b81..63b34d1 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayout.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayout.java
@@ -174,6 +174,7 @@
     private final boolean mShouldDrawNotificationBackground;
     private boolean mLowPriorityBeforeSpeedBump;
     private final boolean mAllowLongPress;
+    private boolean mDismissRtl;
 
     private float mExpandedHeight;
     private int mOwnScrollY;
@@ -533,8 +534,10 @@
         tunerService.addTunable((key, newValue) -> {
             if (key.equals(LOW_PRIORITY)) {
                 mLowPriorityBeforeSpeedBump = "1".equals(newValue);
+            } else if (key.equals(Settings.Secure.NOTIFICATION_DISMISS_RTL)) {
+                updateDismissRtlSetting("1".equals(newValue));
             }
-        }, LOW_PRIORITY);
+        }, LOW_PRIORITY, Settings.Secure.NOTIFICATION_DISMISS_RTL);
 
         mEntryManager.addNotificationEntryListener(new NotificationEntryListener() {
             @Override
@@ -548,6 +551,16 @@
         });
     }
 
+    private void updateDismissRtlSetting(boolean dismissRtl) {
+        mDismissRtl = dismissRtl;
+        for (int i = 0; i < getChildCount(); i++) {
+            View child = getChildAt(i);
+            if (child instanceof ExpandableNotificationRow) {
+                ((ExpandableNotificationRow) child).setDismissRtl(dismissRtl);
+            }
+        }
+    }
+
     @Override
     @ShadeViewRefactor(RefactorComponent.SHADE_VIEW)
     protected void onFinishInflate() {
@@ -2600,8 +2613,7 @@
             View child = getChildAt(i);
             if (child.getVisibility() != View.GONE && child instanceof ExpandableNotificationRow) {
                 ExpandableNotificationRow row = (ExpandableNotificationRow) child;
-                if (!mEntryManager.getNotificationData().isHighPriority(
-                        row.getStatusBarNotification())) {
+                if (!row.getEntry().isHighPriority()) {
                     break;
                 } else {
                     lastChildBeforeGap = row;
@@ -2619,8 +2631,7 @@
             View child = getChildAt(i);
             if (child.getVisibility() != View.GONE && child instanceof ExpandableNotificationRow) {
                 ExpandableNotificationRow row = (ExpandableNotificationRow) child;
-                if (!mEntryManager.getNotificationData().isHighPriority(
-                        row.getStatusBarNotification())) {
+                if (!row.getEntry().isHighPriority()) {
                     return row;
                 }
             }
@@ -3255,6 +3266,9 @@
         generateAddAnimation(child, false /* fromMoreCard */);
         updateAnimationState(child);
         updateChronometerForChild(child);
+        if (child instanceof ExpandableNotificationRow) {
+            ((ExpandableNotificationRow) child).setDismissRtl(mDismissRtl);
+        }
         if (ANCHOR_SCROLLING) {
             // TODO: once we're recycling this will need to check the adapter position of the child
             if (child == getFirstChildNotGone() && (isScrolledToTop() || !mIsExpanded)) {
@@ -5756,11 +5770,9 @@
             currentIndex++;
             boolean beforeSpeedBump;
             if (mLowPriorityBeforeSpeedBump) {
-                beforeSpeedBump = !mEntryManager.getNotificationData().isAmbient(
-                        row.getStatusBarNotification().getKey());
+                beforeSpeedBump = !row.getEntry().ambient;
             } else {
-                beforeSpeedBump = mEntryManager.getNotificationData().isHighPriority(
-                        row.getStatusBarNotification());
+                beforeSpeedBump = row.getEntry().isHighPriority();
             }
             if (beforeSpeedBump) {
                 speedBumpIndex = currentIndex;
@@ -5784,8 +5796,7 @@
                     continue;
                 }
                 ExpandableNotificationRow row = (ExpandableNotificationRow) view;
-                if (!mEntryManager.getNotificationData().isHighPriority(
-                        row.getStatusBarNotification())) {
+                if (!row.getEntry().isHighPriority()) {
                     if (currentIndex > 0) {
                         gapIndex = currentIndex;
                     }
@@ -6315,7 +6326,7 @@
         public boolean canChildBeDismissedInDirection(View v, boolean isRightOrDown) {
             boolean isValidDirection;
             if (NotificationUtils.useNewInterruptionModel(mContext)) {
-                isValidDirection = isLayoutRtl() ? !isRightOrDown : isRightOrDown;
+                isValidDirection = mDismissRtl ? !isRightOrDown : isRightOrDown;
             } else {
                 isValidDirection = true;
             }
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/AutoTileManager.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/AutoTileManager.java
index fac4dbb..b96c55b 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/AutoTileManager.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/AutoTileManager.java
@@ -168,8 +168,8 @@
 
         @Override
         public void onAutoModeChanged(int autoMode) {
-            if (autoMode == ColorDisplayController.AUTO_MODE_CUSTOM
-                    || autoMode == ColorDisplayController.AUTO_MODE_TWILIGHT) {
+            if (autoMode == ColorDisplayManager.AUTO_MODE_CUSTOM_TIME
+                    || autoMode == ColorDisplayManager.AUTO_MODE_TWILIGHT) {
                 addNightTile();
             }
         }
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/NavigationBarFragment.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/NavigationBarFragment.java
index d3c6a1d..ee047e4 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/NavigationBarFragment.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/NavigationBarFragment.java
@@ -243,6 +243,9 @@
             mDisabledFlags2 = savedInstanceState.getInt(EXTRA_DISABLE2_STATE, 0);
         }
         mAccessibilityManagerWrapper.addCallback(mAccessibilityListener);
+
+        // Respect the latest disabled-flags.
+        mCommandQueue.recomputeDisableFlags(mDisplayId, false);
     }
 
     @Override
@@ -799,7 +802,9 @@
     }
 
     private void onAccessibilityClick(View v) {
-        mAccessibilityManager.notifyAccessibilityButtonClicked();
+        final Display display = v.getDisplay();
+        mAccessibilityManager.notifyAccessibilityButtonClicked(
+                display != null ? display.getDisplayId() : Display.DEFAULT_DISPLAY);
     }
 
     private boolean onAccessibilityLongClick(View v) {
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/NavigationGestureAction.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/NavigationGestureAction.java
index a5d9382..39fbbb1 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/NavigationGestureAction.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/NavigationGestureAction.java
@@ -20,6 +20,7 @@
 import static android.view.WindowManagerPolicyConstants.NAV_BAR_RIGHT;
 
 import static com.android.systemui.shared.system.NavigationBarCompat.HIT_TARGET_NONE;
+import static com.android.systemui.statusbar.phone.NavigationPrototypeController.PROTOTYPE_ENABLED;
 
 import android.annotation.NonNull;
 import android.content.Context;
@@ -84,7 +85,7 @@
 
         // Tell launcher that this action requires a stable task list or not
         boolean flag = requiresStableTaskList();
-        if (flag != sLastTaskStabilizationFlag) {
+        if (getGlobalBoolean(PROTOTYPE_ENABLED) && flag != sLastTaskStabilizationFlag) {
             Settings.Global.putInt(mNavigationBarView.getContext().getContentResolver(),
                     ENABLE_TASK_STABILIZER_FLAG, flag ? 1 : 0);
             sLastTaskStabilizationFlag = flag;
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/NavigationPrototypeController.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/NavigationPrototypeController.java
index a09e585..f762a6a 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/NavigationPrototypeController.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/NavigationPrototypeController.java
@@ -37,6 +37,7 @@
 
     private final String GESTURE_MATCH_SETTING = "quickstepcontroller_gesture_match_map";
     public static final String NAV_COLOR_ADAPT_ENABLE_SETTING = "navbar_color_adapt_enable";
+    public static final String PROTOTYPE_ENABLED = "prototype_enabled";
 
     @Retention(RetentionPolicy.SOURCE)
     @IntDef({ACTION_DEFAULT, ACTION_QUICKSTEP, ACTION_QUICKSCRUB, ACTION_BACK,
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationIconAreaController.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationIconAreaController.java
index 077fcda..e86996a 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationIconAreaController.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationIconAreaController.java
@@ -194,8 +194,7 @@
         if (mEntryManager.getNotificationData().isAmbient(entry.key) && !showAmbient) {
             return false;
         }
-        if (!showLowPriority
-                && !mEntryManager.getNotificationData().isHighPriority(entry.notification)) {
+        if (!showLowPriority && !entry.isHighPriority()) {
             return false;
         }
         if (!entry.isTopLevelChild()) {
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..c108371 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;
@@ -2792,6 +2793,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/src/com/android/systemui/usb/UsbContaminantActivity.java b/packages/SystemUI/src/com/android/systemui/usb/UsbContaminantActivity.java
new file mode 100644
index 0000000..fa4b3fe
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/usb/UsbContaminantActivity.java
@@ -0,0 +1,73 @@
+/*
+ * 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.usb;
+
+import android.content.DialogInterface;
+import android.content.Intent;
+import android.hardware.usb.ParcelableUsbPort;
+import android.hardware.usb.UsbManager;
+import android.hardware.usb.UsbPort;
+import android.os.Bundle;
+import android.view.Window;
+import android.view.WindowManager;
+
+import com.android.internal.app.AlertActivity;
+import com.android.internal.app.AlertController;
+import com.android.systemui.R;
+
+/**
+ * Activity that alerts the user when contaminant is detected on USB port.
+ */
+public class UsbContaminantActivity extends AlertActivity
+                                  implements DialogInterface.OnClickListener {
+    private static final String TAG = "UsbContaminantActivity";
+
+    private UsbDisconnectedReceiver mDisconnectedReceiver;
+    private UsbPort mUsbPort;
+
+    @Override
+    public void onCreate(Bundle icicle) {
+        Window window = getWindow();
+        window.addSystemFlags(WindowManager.LayoutParams
+                .SYSTEM_FLAG_HIDE_NON_SYSTEM_OVERLAY_WINDOWS);
+        window.setType(WindowManager.LayoutParams.TYPE_SYSTEM_DIALOG);
+
+        super.onCreate(icicle);
+
+        Intent intent = getIntent();
+        ParcelableUsbPort port = intent.getParcelableExtra(UsbManager.EXTRA_PORT);
+        mUsbPort = port.getUsbPort(getSystemService(UsbManager.class));
+
+        final AlertController.AlertParams ap = mAlertParams;
+        ap.mTitle = getString(R.string.usb_contaminant_title);
+        ap.mMessage = getString(R.string.usb_contaminant_message);
+        ap.mPositiveButtonText = getString(android.R.string.ok);
+        ap.mPositiveButtonListener = this;
+
+        setupAlert();
+    }
+
+    @Override
+    public void onWindowAttributesChanged(WindowManager.LayoutParams params) {
+        super.onWindowAttributesChanged(params);
+    }
+
+    @Override
+    public void onClick(DialogInterface dialog, int which) {
+        finish();
+    }
+}
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..e802757 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/bubbles/BubbleControllerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/bubbles/BubbleControllerTest.java
@@ -61,7 +61,6 @@
     private IActivityManager mActivityManager;
     @Mock
     private DozeParameters mDozeParameters;
-    @Mock
     private FrameLayout mStatusBarView;
     @Captor
     private ArgumentCaptor<NotificationEntryListener> mEntryListenerCaptor;
@@ -80,6 +79,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
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 2f6b221..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;
@@ -28,10 +28,10 @@
 
 import com.android.internal.statusbar.IStatusBarService;
 import com.android.internal.statusbar.NotificationVisibility;
-import com.android.systemui.statusbar.notification.stack.ExpandableViewState;
 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;
@@ -73,7 +73,8 @@
 
     @Test
     public void testExpanded() throws RemoteException {
-        mLogger.onExpansionChanged(NOTIFICATION_KEY, false, true);
+        mLogger.onExpansionChanged(NOTIFICATION_KEY, false, true,
+                NotificationVisibility.NotificationLocation.LOCATION_UNKNOWN);
         waitForUiOffloadThread();
 
         verify(mBarService, Mockito.never()).onNotificationExpansionChanged(
@@ -82,7 +83,8 @@
 
     @Test
     public void testVisibleAndNotExpanded() throws RemoteException {
-        mLogger.onExpansionChanged(NOTIFICATION_KEY, true, false);
+        mLogger.onExpansionChanged(NOTIFICATION_KEY, true, false,
+                NotificationVisibility.NotificationLocation.LOCATION_UNKNOWN);
         mLogger.onVisibilityChanged(
                 Collections.singletonList(createNotificationVisibility(NOTIFICATION_KEY, true)),
                 Collections.emptyList());
@@ -94,26 +96,33 @@
 
     @Test
     public void testVisibleAndExpanded() throws RemoteException {
-        mLogger.onExpansionChanged(NOTIFICATION_KEY, true, true);
+        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);
+                NOTIFICATION_KEY, true, true,
+                NotificationVisibility.NotificationLocation.LOCATION_UNKNOWN.toMetricsEventEnum());
     }
 
     @Test
     public void testExpandedAndVisible_expandedBeforeVisible() throws RemoteException {
-        mLogger.onExpansionChanged(NOTIFICATION_KEY, false, true);
+        mLogger.onExpansionChanged(NOTIFICATION_KEY, false, true,
+                NotificationVisibility.NotificationLocation.LOCATION_UNKNOWN);
         mLogger.onVisibilityChanged(
-                Collections.singletonList(createNotificationVisibility(NOTIFICATION_KEY, true)),
+                Collections.singletonList(createNotificationVisibility(NOTIFICATION_KEY, true,
+                    NotificationVisibility.NotificationLocation.LOCATION_MAIN_AREA)),
                 Collections.emptyList());
         waitForUiOffloadThread();
 
         verify(mBarService).onNotificationExpansionChanged(
-                NOTIFICATION_KEY, false, true, ExpandableViewState.LOCATION_UNKNOWN);
+                NOTIFICATION_KEY, false, true,
+                // The last location seen should be logged (the one passed to onVisibilityChanged).
+                NotificationVisibility.NotificationLocation.LOCATION_MAIN_AREA.toMetricsEventEnum()
+        );
     }
 
     @Test
@@ -121,11 +130,14 @@
         mLogger.onVisibilityChanged(
                 Collections.singletonList(createNotificationVisibility(NOTIFICATION_KEY, true)),
                 Collections.emptyList());
-        mLogger.onExpansionChanged(NOTIFICATION_KEY, false, true);
+        mLogger.onExpansionChanged(NOTIFICATION_KEY, false, true,
+                NotificationVisibility.NotificationLocation.LOCATION_FIRST_HEADS_UP);
         waitForUiOffloadThread();
 
         verify(mBarService).onNotificationExpansionChanged(
-                NOTIFICATION_KEY, false, true, ExpandableViewState.LOCATION_UNKNOWN);
+                NOTIFICATION_KEY, false, true,
+                // The last location seen should be logged (the one passed to onExpansionChanged).
+                NotificationVisibility.NotificationLocation.LOCATION_FIRST_HEADS_UP.toMetricsEventEnum());
     }
 
     @Test
@@ -133,15 +145,45 @@
         mLogger.onVisibilityChanged(
                 Collections.singletonList(createNotificationVisibility(NOTIFICATION_KEY, true)),
                 Collections.emptyList());
-        mLogger.onExpansionChanged(NOTIFICATION_KEY, false, true);
-        mLogger.onExpansionChanged(NOTIFICATION_KEY, false, true);
+        mLogger.onExpansionChanged(NOTIFICATION_KEY, false, true,
+                NotificationVisibility.NotificationLocation.LOCATION_UNKNOWN);
+        mLogger.onExpansionChanged(NOTIFICATION_KEY, false, true,
+                NotificationVisibility.NotificationLocation.LOCATION_UNKNOWN);
         waitForUiOffloadThread();
 
         verify(mBarService).onNotificationExpansionChanged(
-                NOTIFICATION_KEY, false, true, ExpandableViewState.LOCATION_UNKNOWN);
+                NOTIFICATION_KEY, false, true,
+                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 NotificationVisibility.obtain(key, 0, 0, visibility);
+        return createNotificationVisibility(key, visibility,
+                NotificationVisibility.NotificationLocation.LOCATION_UNKNOWN);
+    }
+
+    private NotificationVisibility createNotificationVisibility(String key, boolean visibility,
+            NotificationVisibility.NotificationLocation location) {
+        return NotificationVisibility.obtain(key, 0, 0, visibility, location);
     }
 }
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/NotificationGutsManagerTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/NotificationGutsManagerTest.java
index 0899c73..fdc9e0c 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/NotificationGutsManagerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/NotificationGutsManagerTest.java
@@ -45,6 +45,7 @@
 import android.app.NotificationChannel;
 import android.content.Intent;
 import android.content.pm.PackageManager;
+import android.metrics.LogMaker;
 import android.os.Binder;
 import android.os.Handler;
 import android.provider.Settings;
@@ -55,6 +56,8 @@
 import android.util.ArraySet;
 import android.view.View;
 
+import com.android.internal.logging.MetricsLogger;
+import com.android.internal.logging.nano.MetricsProto;
 import com.android.systemui.SysuiTestCase;
 import com.android.systemui.plugins.statusbar.NotificationMenuRowPlugin;
 import com.android.systemui.statusbar.NotificationPresenter;
@@ -92,6 +95,7 @@
     private NotificationGutsManager mGutsManager;
 
     @Rule public MockitoRule mockito = MockitoJUnit.rule();
+    @Mock private MetricsLogger mMetricsLogger;
     @Mock private NotificationPresenter mPresenter;
     @Mock private NotificationActivityStarter mNotificationActivityStarter;
     @Mock private NotificationStackScrollLayout mStackScroller;
@@ -105,6 +109,7 @@
         Assert.sMainLooper = TestableLooper.get(this).getLooper();
         mDependency.injectTestDependency(DeviceProvisionedController.class,
                 mDeviceProvisionedController);
+        mDependency.injectTestDependency(MetricsLogger.class, mMetricsLogger);
         mHandler = Handler.createAsync(mTestableLooper.getLooper());
 
         mHelper = new NotificationTestHelper(mContext);
@@ -141,7 +146,7 @@
         when(row.getWindowToken()).thenReturn(new Binder());
         when(row.getGuts()).thenReturn(guts);
 
-        mGutsManager.openGuts(row, 0, 0, menuItem);
+        assertTrue(mGutsManager.openGuts(row, 0, 0, menuItem));
         assertEquals(View.INVISIBLE, guts.getVisibility());
         mTestableLooper.processAllMessages();
         verify(guts).openControls(
@@ -189,7 +194,7 @@
         when(entry.getRow()).thenReturn(row);
         when(entry.getGuts()).thenReturn(guts);
 
-        mGutsManager.openGuts(row, 0, 0, menuItem);
+        assertTrue(mGutsManager.openGuts(row, 0, 0, menuItem));
         mTestableLooper.processAllMessages();
         verify(guts).openControls(
                 eq(true),
@@ -215,6 +220,34 @@
     }
 
     @Test
+    public void testOpenGutsLogging() {
+        NotificationGutsManager gutsManager = spy(mGutsManager);
+        doReturn(true).when(gutsManager).bindGuts(any(), any());
+
+        NotificationGuts guts = spy(new NotificationGuts(mContext));
+        doReturn(true).when(guts).post(any());
+
+        ExpandableNotificationRow realRow = createTestNotificationRow();
+        NotificationMenuRowPlugin.MenuItem menuItem = createTestMenuItem(realRow);
+
+        ExpandableNotificationRow row = spy(realRow);
+        when(row.getWindowToken()).thenReturn(new Binder());
+        when(row.getGuts()).thenReturn(guts);
+        StatusBarNotification notification = spy(realRow.getStatusBarNotification());
+        when(row.getStatusBarNotification()).thenReturn(notification);
+
+        assertTrue(gutsManager.openGuts(row, 0, 0, menuItem));
+
+        ArgumentCaptor<LogMaker> logMakerCaptor = ArgumentCaptor.forClass(LogMaker.class);
+        verify(notification).getLogMaker();
+        verify(mMetricsLogger).write(logMakerCaptor.capture());
+        assertEquals(MetricsProto.MetricsEvent.ACTION_NOTE_CONTROLS,
+                logMakerCaptor.getValue().getCategory());
+        assertEquals(MetricsProto.MetricsEvent.TYPE_ACTION,
+                logMakerCaptor.getValue().getType());
+    }
+
+    @Test
     public void testAppOpsSettingsIntent_camera() {
         ArraySet<Integer> ops = new ArraySet<>();
         ops.add(OP_CAMERA);
@@ -321,7 +354,8 @@
                 eq(false),
                 eq(true) /* isForBlockingHelper */,
                 eq(true) /* isUserSentimentNegative */,
-                eq(0));
+                eq(0),
+                eq(false) /* wasShownHighPriority */);
     }
 
     @Test
@@ -349,16 +383,18 @@
                 eq(false),
                 eq(false) /* isForBlockingHelper */,
                 eq(true) /* isUserSentimentNegative */,
-                eq(0));
+                eq(0),
+                eq(false) /* wasShownHighPriority */);
     }
 
     @Test
-    public void testInitializeNotificationInfoView_importance() throws Exception {
+    public void testInitializeNotificationInfoView_highPriority() throws Exception {
         NotificationInfo notificationInfoView = mock(NotificationInfo.class);
         ExpandableNotificationRow row = spy(mHelper.createRow());
         row.setBlockingHelperShowing(true);
         row.getEntry().userSentiment = USER_SENTIMENT_NEGATIVE;
         row.getEntry().importance = IMPORTANCE_DEFAULT;
+        row.getEntry().setIsHighPriority(true);
         when(row.getIsNonblockable()).thenReturn(false);
         StatusBarNotification statusBarNotification = row.getStatusBarNotification();
 
@@ -378,7 +414,8 @@
                 eq(false),
                 eq(true) /* isForBlockingHelper */,
                 eq(true) /* isUserSentimentNegative */,
-                eq(IMPORTANCE_DEFAULT));
+                eq(IMPORTANCE_DEFAULT),
+                eq(true) /* wasShownHighPriority */);
     }
 
     @Test
@@ -407,7 +444,8 @@
                 eq(false),
                 eq(false) /* isForBlockingHelper */,
                 eq(true) /* isUserSentimentNegative */,
-                eq(0));
+                eq(0),
+                eq(false) /* wasShownHighPriority */);
     }
 
     @Test
@@ -435,7 +473,8 @@
                 eq(false),
                 eq(true) /* isForBlockingHelper */,
                 eq(true) /* isUserSentimentNegative */,
-                eq(0));
+                eq(0),
+                eq(false) /* wasShownHighPriority */);
     }
 
     @Test
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 f6791dd5..08955e3 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
@@ -210,7 +210,7 @@
         when(mMockPackageManager.getApplicationLabel(any())).thenReturn("App Name");
         mNotificationInfo.bindNotification(mMockPackageManager, mMockINotificationManager,
                 TEST_PACKAGE_NAME, mNotificationChannel, 1, mSbn, null, null, null, true, false,
-                IMPORTANCE_DEFAULT);
+                IMPORTANCE_DEFAULT, true);
         final TextView textView = mNotificationInfo.findViewById(R.id.pkgname);
         assertTrue(textView.getText().toString().contains("App Name"));
         assertEquals(VISIBLE, mNotificationInfo.findViewById(R.id.header).getVisibility());
@@ -223,7 +223,7 @@
                 .thenReturn(iconDrawable);
         mNotificationInfo.bindNotification(mMockPackageManager, mMockINotificationManager,
                 TEST_PACKAGE_NAME, mNotificationChannel, 1, mSbn, null, null, null, true, false,
-                IMPORTANCE_DEFAULT);
+                IMPORTANCE_DEFAULT, true);
         final ImageView iconView = mNotificationInfo.findViewById(R.id.pkgicon);
         assertEquals(iconDrawable, iconView.getDrawable());
     }
@@ -232,7 +232,7 @@
     public void testBindNotification_noDelegate() throws Exception {
         mNotificationInfo.bindNotification(mMockPackageManager, mMockINotificationManager,
                 TEST_PACKAGE_NAME, mNotificationChannel, 1, mSbn, null, null, null, true, false,
-                IMPORTANCE_DEFAULT);
+                IMPORTANCE_DEFAULT, true);
         final TextView nameView = mNotificationInfo.findViewById(R.id.delegate_name);
         assertEquals(GONE, nameView.getVisibility());
         final TextView dividerView = mNotificationInfo.findViewById(R.id.pkg_divider);
@@ -251,7 +251,7 @@
 
         mNotificationInfo.bindNotification(mMockPackageManager, mMockINotificationManager,
                 TEST_PACKAGE_NAME, mNotificationChannel, 1, mSbn, null, null, null, true, false,
-                IMPORTANCE_DEFAULT);
+                IMPORTANCE_DEFAULT, true);
         final TextView nameView = mNotificationInfo.findViewById(R.id.delegate_name);
         assertEquals(VISIBLE, nameView.getVisibility());
         assertTrue(nameView.getText().toString().contains("Other"));
@@ -263,7 +263,7 @@
     public void testBindNotification_GroupNameHiddenIfNoGroup() throws Exception {
         mNotificationInfo.bindNotification(mMockPackageManager, mMockINotificationManager,
                 TEST_PACKAGE_NAME, mNotificationChannel, 1, mSbn, null, null, null, true, false,
-                IMPORTANCE_DEFAULT);
+                IMPORTANCE_DEFAULT, true);
         final TextView groupNameView = mNotificationInfo.findViewById(R.id.group_name);
         assertEquals(GONE, groupNameView.getVisibility());
         final TextView groupDividerView = mNotificationInfo.findViewById(R.id.pkg_group_divider);
@@ -280,7 +280,7 @@
                 .thenReturn(notificationChannelGroup);
         mNotificationInfo.bindNotification(mMockPackageManager, mMockINotificationManager,
                 TEST_PACKAGE_NAME, mNotificationChannel, 1, mSbn, null, null, null, true, false,
-                IMPORTANCE_DEFAULT);
+                IMPORTANCE_DEFAULT, true);
         final TextView groupNameView = mNotificationInfo.findViewById(R.id.group_name);
         assertEquals(View.VISIBLE, groupNameView.getVisibility());
         assertEquals("Test Group Name", groupNameView.getText());
@@ -292,7 +292,7 @@
     public void testBindNotification_SetsTextChannelName() throws Exception {
         mNotificationInfo.bindNotification(mMockPackageManager, mMockINotificationManager,
                 TEST_PACKAGE_NAME, mNotificationChannel, 1, mSbn, null, null, null, true, false,
-                IMPORTANCE_DEFAULT);
+                IMPORTANCE_DEFAULT, true);
         final TextView textView = mNotificationInfo.findViewById(R.id.channel_name);
         assertEquals(TEST_CHANNEL_NAME, textView.getText());
     }
@@ -301,7 +301,7 @@
     public void testBindNotification_DefaultChannelDoesNotUseChannelName() throws Exception {
         mNotificationInfo.bindNotification(mMockPackageManager, mMockINotificationManager,
                 TEST_PACKAGE_NAME, mDefaultNotificationChannel, 1, mSbn, null, null, null, true,
-                false, IMPORTANCE_DEFAULT);
+                false, IMPORTANCE_DEFAULT, true);
         final TextView textView = mNotificationInfo.findViewById(R.id.channel_name);
         assertEquals(GONE, textView.getVisibility());
     }
@@ -314,7 +314,7 @@
                 eq(TEST_PACKAGE_NAME), eq(TEST_UID), anyBoolean())).thenReturn(10);
         mNotificationInfo.bindNotification(mMockPackageManager, mMockINotificationManager,
                 TEST_PACKAGE_NAME, mDefaultNotificationChannel, 1, mSbn, null, null, null, true,
-                false, IMPORTANCE_DEFAULT);
+                false, IMPORTANCE_DEFAULT, true);
         final TextView textView = mNotificationInfo.findViewById(R.id.channel_name);
         assertEquals(VISIBLE, textView.getVisibility());
     }
@@ -323,7 +323,7 @@
     public void testBindNotification_UnblockablePackageUsesChannelName() throws Exception {
         mNotificationInfo.bindNotification(mMockPackageManager, mMockINotificationManager,
                 TEST_PACKAGE_NAME, mNotificationChannel, 1, mSbn, null, null, null, true, true,
-                IMPORTANCE_DEFAULT);
+                IMPORTANCE_DEFAULT, true);
         final TextView textView = mNotificationInfo.findViewById(R.id.channel_name);
         assertEquals(VISIBLE, textView.getVisibility());
     }
@@ -332,7 +332,7 @@
     public void testBindNotification_BlockButton() throws Exception {
        mNotificationInfo.bindNotification(mMockPackageManager, mMockINotificationManager,
                TEST_PACKAGE_NAME, mNotificationChannel, 1, mSbn, null, null, null, true, false,
-               IMPORTANCE_DEFAULT);
+               IMPORTANCE_DEFAULT, true);
         final View block = mNotificationInfo.findViewById(R.id.int_block);
         final View minimize = mNotificationInfo.findViewById(R.id.block_or_minimize);
         assertEquals(VISIBLE, block.getVisibility());
@@ -343,7 +343,7 @@
     public void testBindNotification_BlockButton_BlockHelper() throws Exception {
         mNotificationInfo.bindNotification(mMockPackageManager, mMockINotificationManager,
                 TEST_PACKAGE_NAME, mNotificationChannel, 1, mSbn, null, null, null, true, false,
-                true /* isBlockingHelper */, false, IMPORTANCE_DEFAULT);
+                true /* isBlockingHelper */, false, IMPORTANCE_DEFAULT, true);
         final View block = mNotificationInfo.findViewById(R.id.block);
         final View interruptivenessSettings = mNotificationInfo.findViewById(
                 R.id.interruptiveness_settings);
@@ -356,7 +356,7 @@
         mNotificationChannel.setImportance(IMPORTANCE_DEFAULT);
         mNotificationInfo.bindNotification(mMockPackageManager, mMockINotificationManager,
                 TEST_PACKAGE_NAME, mNotificationChannel, 1, mSbn, null, null, null, true, false,
-                IMPORTANCE_DEFAULT);
+                IMPORTANCE_DEFAULT, true);
         final TextView silent = mNotificationInfo.findViewById(R.id.int_silent);
         assertEquals(VISIBLE, silent.getVisibility());
         assertEquals(
@@ -368,7 +368,7 @@
         mNotificationChannel.setImportance(IMPORTANCE_LOW);
         mNotificationInfo.bindNotification(mMockPackageManager, mMockINotificationManager,
                 TEST_PACKAGE_NAME, mNotificationChannel, 1, mSbn, null, null, null, true, false,
-                IMPORTANCE_LOW);
+                IMPORTANCE_LOW, false);
         final TextView silent = mNotificationInfo.findViewById(R.id.int_silent);
         assertEquals(VISIBLE, silent.getVisibility());
         assertEquals(
@@ -381,7 +381,7 @@
         mNotificationChannel.setImportance(IMPORTANCE_LOW);
         mNotificationInfo.bindNotification(mMockPackageManager, mMockINotificationManager,
                 TEST_PACKAGE_NAME, mNotificationChannel, 1, mSbn, null, null, null, true, false,
-                IMPORTANCE_LOW);
+                IMPORTANCE_LOW, false);
         final TextView alert = mNotificationInfo.findViewById(R.id.int_alert);
         assertEquals(VISIBLE, alert.getVisibility());
         assertEquals(
@@ -393,7 +393,7 @@
         mNotificationChannel.setImportance(IMPORTANCE_DEFAULT);
         mNotificationInfo.bindNotification(mMockPackageManager, mMockINotificationManager,
                 TEST_PACKAGE_NAME, mNotificationChannel, 1, mSbn, null, null, null, true, false,
-                IMPORTANCE_DEFAULT);
+                IMPORTANCE_DEFAULT, true);
         final TextView alert = mNotificationInfo.findViewById(R.id.int_alert);
         assertEquals(VISIBLE, alert.getVisibility());
         assertEquals(
@@ -405,7 +405,7 @@
         mNotificationChannel.setImportance(IMPORTANCE_UNSPECIFIED);
         mNotificationInfo.bindNotification(mMockPackageManager, mMockINotificationManager,
                 TEST_PACKAGE_NAME, mNotificationChannel, 1, mSbn, null, null, null, true, false,
-                IMPORTANCE_DEFAULT);
+                IMPORTANCE_DEFAULT, true);
         final TextView silent = mNotificationInfo.findViewById(R.id.int_silent);
         final TextView alert = mNotificationInfo.findViewById(R.id.int_alert);
         assertEquals(VISIBLE, silent.getVisibility());
@@ -421,7 +421,7 @@
         mNotificationChannel.setImportance(IMPORTANCE_UNSPECIFIED);
         mNotificationInfo.bindNotification(mMockPackageManager, mMockINotificationManager,
                 TEST_PACKAGE_NAME, mNotificationChannel, 1, mSbn, null, null, null, true, false,
-                IMPORTANCE_LOW);
+                IMPORTANCE_LOW, false);
         final TextView silent = mNotificationInfo.findViewById(R.id.int_silent);
         final TextView alert = mNotificationInfo.findViewById(R.id.int_alert);
         assertEquals(VISIBLE, silent.getVisibility());
@@ -437,7 +437,7 @@
         mSbn.getNotification().flags = Notification.FLAG_FOREGROUND_SERVICE;
         mNotificationInfo.bindNotification(mMockPackageManager, mMockINotificationManager,
                 TEST_PACKAGE_NAME, mNotificationChannel, 1, mSbn, null, null, null, true, false,
-                IMPORTANCE_DEFAULT);
+                IMPORTANCE_DEFAULT, true);
         final View block = mNotificationInfo.findViewById(R.id.block);
         final View interruptivenessSettings = mNotificationInfo.findViewById(
                 R.id.interruptiveness_settings);
@@ -455,7 +455,7 @@
                 (View v, NotificationChannel c, int appUid) -> {
                     assertEquals(mNotificationChannel, c);
                     latch.countDown();
-                }, null, true, false, IMPORTANCE_DEFAULT);
+                }, null, true, false, IMPORTANCE_DEFAULT, true);
 
         final View settingsButton = mNotificationInfo.findViewById(R.id.info);
         settingsButton.performClick();
@@ -467,7 +467,7 @@
     public void testBindNotification_SettingsButtonInvisibleWhenNoClickListener() throws Exception {
         mNotificationInfo.bindNotification(mMockPackageManager, mMockINotificationManager,
                 TEST_PACKAGE_NAME, mNotificationChannel, 1, mSbn, null, null, null, true, false,
-                IMPORTANCE_DEFAULT);
+                IMPORTANCE_DEFAULT, true);
         final View settingsButton = mNotificationInfo.findViewById(R.id.info);
         assertTrue(settingsButton.getVisibility() != View.VISIBLE);
     }
@@ -479,7 +479,7 @@
                 TEST_PACKAGE_NAME, mNotificationChannel, 1, mSbn, null,
                 (View v, NotificationChannel c, int appUid) -> {
                     assertEquals(mNotificationChannel, c);
-                }, null, false, false, IMPORTANCE_DEFAULT);
+                }, null, false, false, IMPORTANCE_DEFAULT, true);
         final View settingsButton = mNotificationInfo.findViewById(R.id.info);
         assertTrue(settingsButton.getVisibility() != View.VISIBLE);
     }
@@ -488,11 +488,11 @@
     public void testBindNotification_SettingsButtonReappearsAfterSecondBind() throws Exception {
         mNotificationInfo.bindNotification(mMockPackageManager, mMockINotificationManager,
                 TEST_PACKAGE_NAME, mNotificationChannel, 1, mSbn, null, null, null, true, false,
-                IMPORTANCE_DEFAULT);
+                IMPORTANCE_DEFAULT, true);
         mNotificationInfo.bindNotification(mMockPackageManager, mMockINotificationManager,
                 TEST_PACKAGE_NAME, mNotificationChannel, 1, mSbn, null,
                 (View v, NotificationChannel c, int appUid) -> {
-                }, null, true, false, IMPORTANCE_DEFAULT);
+                }, null, true, false, IMPORTANCE_DEFAULT, true);
         final View settingsButton = mNotificationInfo.findViewById(R.id.info);
         assertEquals(View.VISIBLE, settingsButton.getVisibility());
     }
@@ -501,7 +501,7 @@
     public void testLogBlockingHelperCounter_logGutsViewDisplayed() throws Exception {
         mNotificationInfo.bindNotification(mMockPackageManager, mMockINotificationManager,
                 TEST_PACKAGE_NAME, mNotificationChannel, 1, mSbn, null, null, null, true, false,
-                IMPORTANCE_DEFAULT);
+                IMPORTANCE_DEFAULT, true);
         mNotificationInfo.logBlockingHelperCounter("HowCanNotifsBeRealIfAppsArent");
         verify(mMetricsLogger).write(argThat(logMaker ->
                 logMaker.getType() == MetricsEvent.NOTIFICATION_BLOCKING_HELPER
@@ -513,7 +513,7 @@
     public void testLogBlockingHelperCounter_logsForBlockingHelper() throws Exception {
         mNotificationInfo.bindNotification(mMockPackageManager, mMockINotificationManager,
                 TEST_PACKAGE_NAME, mNotificationChannel, 1, mSbn, null, null, null, false, true,
-                true, true, IMPORTANCE_DEFAULT);
+                true, true, IMPORTANCE_DEFAULT, true);
         mNotificationInfo.logBlockingHelperCounter("HowCanNotifsBeRealIfAppsArent");
         verify(mMetricsLogger).count(eq("HowCanNotifsBeRealIfAppsArent"), eq(1));
     }
@@ -526,7 +526,7 @@
                 (View v, NotificationChannel c, int appUid) -> {
                     assertEquals(null, c);
                     latch.countDown();
-                }, null, true, true, IMPORTANCE_DEFAULT);
+                }, null, true, true, IMPORTANCE_DEFAULT, true);
 
         mNotificationInfo.findViewById(R.id.info).performClick();
         // Verify that listener was triggered.
@@ -539,7 +539,7 @@
             throws Exception {
         mNotificationInfo.bindNotification(mMockPackageManager, mMockINotificationManager,
                 TEST_PACKAGE_NAME, mNotificationChannel, MULTIPLE_CHANNEL_COUNT, mSbn, null, null,
-                null, true, true, IMPORTANCE_DEFAULT);
+                null, true, true, IMPORTANCE_DEFAULT, true);
         final TextView channelNameView =
                 mNotificationInfo.findViewById(R.id.channel_name);
         assertEquals(GONE, channelNameView.getVisibility());
@@ -550,7 +550,7 @@
     public void testStopInvisibleIfBundleFromDifferentChannels() throws Exception {
         mNotificationInfo.bindNotification(mMockPackageManager, mMockINotificationManager,
                 TEST_PACKAGE_NAME, mNotificationChannel, MULTIPLE_CHANNEL_COUNT, mSbn, null, null,
-                null, true, true, IMPORTANCE_DEFAULT);
+                null, true, true, IMPORTANCE_DEFAULT, true);
         final TextView blockView = mNotificationInfo.findViewById(R.id.block);
         assertEquals(GONE, blockView.getVisibility());
     }
@@ -559,7 +559,7 @@
     public void testbindNotification_BlockingHelper() throws Exception {
         mNotificationInfo.bindNotification(mMockPackageManager, mMockINotificationManager,
                 TEST_PACKAGE_NAME, mNotificationChannel, 1, mSbn, null, null, null, false, false,
-                true, true, IMPORTANCE_DEFAULT);
+                true, true, IMPORTANCE_DEFAULT, true);
         final TextView view = mNotificationInfo.findViewById(R.id.block_prompt);
         assertEquals(View.VISIBLE, view.getVisibility());
         assertEquals(mContext.getString(R.string.inline_blocking_helper), view.getText());
@@ -569,7 +569,7 @@
     public void testbindNotification_UnblockableTextVisibleWhenAppUnblockable() throws Exception {
         mNotificationInfo.bindNotification(mMockPackageManager, mMockINotificationManager,
                 TEST_PACKAGE_NAME, mNotificationChannel, 1, mSbn, null, null, null, true, true,
-                IMPORTANCE_DEFAULT);
+                IMPORTANCE_DEFAULT, true);
         final TextView view = mNotificationInfo.findViewById(R.id.block_prompt);
         assertEquals(View.VISIBLE, view.getVisibility());
         assertEquals(mContext.getString(R.string.notification_unblockable_desc),
@@ -580,7 +580,7 @@
     public void testBindNotification_DoesNotUpdateNotificationChannel() throws Exception {
         mNotificationInfo.bindNotification(mMockPackageManager, mMockINotificationManager,
                 TEST_PACKAGE_NAME, mNotificationChannel, 1, mSbn, null, null, null, true, false,
-                IMPORTANCE_DEFAULT);
+                IMPORTANCE_DEFAULT, true);
         mTestableLooper.processAllMessages();
         verify(mMockINotificationManager, never()).updateNotificationChannelForPackage(
                 anyString(), eq(TEST_UID), any());
@@ -591,7 +591,7 @@
         mNotificationChannel.setImportance(IMPORTANCE_LOW);
         mNotificationInfo.bindNotification(mMockPackageManager, mMockINotificationManager,
                 TEST_PACKAGE_NAME, mNotificationChannel, 1, mSbn, null, null, null, true, false,
-                IMPORTANCE_DEFAULT);
+                IMPORTANCE_DEFAULT, false);
 
         mNotificationInfo.findViewById(R.id.int_block).performClick();
         mTestableLooper.processAllMessages();
@@ -605,7 +605,7 @@
         mNotificationChannel.setImportance(IMPORTANCE_LOW);
         mNotificationInfo.bindNotification(mMockPackageManager, mMockINotificationManager,
                 TEST_PACKAGE_NAME, mNotificationChannel, 1, mSbn, null, null, null, true, false,
-                IMPORTANCE_DEFAULT);
+                IMPORTANCE_DEFAULT, false);
 
         mNotificationInfo.findViewById(R.id.minimize).performClick();
         mTestableLooper.processAllMessages();
@@ -619,7 +619,7 @@
         mNotificationChannel.setImportance(IMPORTANCE_DEFAULT);
         mNotificationInfo.bindNotification(mMockPackageManager, mMockINotificationManager,
                 TEST_PACKAGE_NAME, mNotificationChannel, 1, mSbn, null, null, null, true, false,
-                IMPORTANCE_DEFAULT);
+                IMPORTANCE_DEFAULT, true);
 
         mNotificationInfo.findViewById(R.id.int_silent).performClick();
         mTestableLooper.processAllMessages();
@@ -633,7 +633,7 @@
         mNotificationChannel.setImportance(IMPORTANCE_LOW);
         mNotificationInfo.bindNotification(mMockPackageManager, mMockINotificationManager,
                 TEST_PACKAGE_NAME, mNotificationChannel, 1, mSbn, null, null, null, true, false,
-                IMPORTANCE_DEFAULT);
+                IMPORTANCE_DEFAULT, false);
 
         mNotificationInfo.findViewById(R.id.int_alert).performClick();
         mTestableLooper.processAllMessages();
@@ -647,7 +647,7 @@
         int originalImportance = mNotificationChannel.getImportance();
         mNotificationInfo.bindNotification(mMockPackageManager, mMockINotificationManager,
                 TEST_PACKAGE_NAME, mNotificationChannel, 1, mSbn, null, null, null, true, false,
-                IMPORTANCE_DEFAULT);
+                IMPORTANCE_DEFAULT, true);
 
         mNotificationInfo.handleCloseControls(true, false);
         mTestableLooper.processAllMessages();
@@ -662,7 +662,7 @@
         mNotificationChannel.setImportance(IMPORTANCE_UNSPECIFIED);
         mNotificationInfo.bindNotification(mMockPackageManager, mMockINotificationManager,
                 TEST_PACKAGE_NAME, mNotificationChannel, 1, mSbn, null, null, null, true, false,
-                IMPORTANCE_DEFAULT);
+                IMPORTANCE_DEFAULT, true);
 
         mNotificationInfo.handleCloseControls(true, false);
 
@@ -680,7 +680,7 @@
                 TEST_PACKAGE_NAME, mNotificationChannel /* notificationChannel */,
                 10 /* numUniqueChannelsInRow */, mSbn, null /* checkSaveListener */,
                 null /* onSettingsClick */, null /* onAppSettingsClick */ ,
-                true, false /* isNonblockable */, IMPORTANCE_DEFAULT
+                true, false /* isNonblockable */, IMPORTANCE_DEFAULT, false
         );
 
         mNotificationInfo.findViewById(R.id.int_block).performClick();
@@ -702,7 +702,7 @@
                 TEST_PACKAGE_NAME, mNotificationChannel /* notificationChannel */,
                 10 /* numUniqueChannelsInRow */, mSbn, null /* checkSaveListener */,
                 null /* onSettingsClick */, null /* onAppSettingsClick */,
-                true, false /* isNonblockable */, IMPORTANCE_DEFAULT
+                true, false /* isNonblockable */, IMPORTANCE_DEFAULT, false
         );
 
         mNotificationInfo.findViewById(R.id.int_block).performClick();
@@ -724,7 +724,7 @@
                 null /* onSettingsClick */, null /* onAppSettingsClick */ ,
                 true /* provisioned */,
                 false /* isNonblockable */, true /* isForBlockingHelper */,
-                true /* isUserSentimentNegative */, IMPORTANCE_DEFAULT);
+                true /* isUserSentimentNegative */, IMPORTANCE_DEFAULT, true);
 
         NotificationGuts guts = spy(new NotificationGuts(mContext, null));
         when(guts.getWindowToken()).thenReturn(mock(IBinder.class));
@@ -752,7 +752,7 @@
                 10 /* numUniqueChannelsInRow */, mSbn, listener /* checkSaveListener */,
                 null /* onSettingsClick */, null /* onAppSettingsClick */ , true /* provisioned */,
                 false /* isNonblockable */, true /* isForBlockingHelper */,
-                true /* isUserSentimentNegative */, IMPORTANCE_DEFAULT);
+                true /* isUserSentimentNegative */, IMPORTANCE_DEFAULT, true);
 
         NotificationGuts guts = spy(new NotificationGuts(mContext, null));
         when(guts.getWindowToken()).thenReturn(mock(IBinder.class));
@@ -781,7 +781,7 @@
                 null /* onSettingsClick */, null /* onAppSettingsClick */ ,
                 false /* isNonblockable */, true /* isForBlockingHelper */,
                 true, true /* isUserSentimentNegative */,  /* isNoisy */
-                IMPORTANCE_DEFAULT);
+                IMPORTANCE_DEFAULT, true);
 
         mNotificationInfo.handleCloseControls(true /* save */, false /* force */);
 
@@ -800,7 +800,7 @@
                 null /* onSettingsClick */, null /* onAppSettingsClick */,
                 true /* provisioned */,
                 false /* isNonblockable */, true /* isForBlockingHelper */,
-                true /* isUserSentimentNegative */, IMPORTANCE_DEFAULT);
+                true /* isUserSentimentNegative */, IMPORTANCE_DEFAULT, true);
 
         mNotificationInfo.findViewById(R.id.block).performClick();
         mTestableLooper.processAllMessages();
@@ -823,7 +823,7 @@
                 true /* isForBlockingHelper */,
                 true,
                 false /* isUserSentimentNegative */,
-                IMPORTANCE_DEFAULT);
+                IMPORTANCE_DEFAULT, true);
         NotificationGuts guts = mock(NotificationGuts.class);
         doCallRealMethod().when(guts).closeControls(anyInt(), anyInt(), anyBoolean(), anyBoolean());
         mNotificationInfo.setGutsParent(guts);
@@ -838,7 +838,7 @@
         mNotificationChannel.setImportance(IMPORTANCE_LOW);
         mNotificationInfo.bindNotification(mMockPackageManager, mMockINotificationManager,
                 TEST_PACKAGE_NAME, mNotificationChannel, 1, mSbn, null, null, null, true, true,
-                IMPORTANCE_DEFAULT);
+                IMPORTANCE_DEFAULT, false);
         mNotificationInfo.findViewById(R.id.block).performClick();
         waitForUndoButton();
 
@@ -852,7 +852,7 @@
         mNotificationChannel.setImportance(IMPORTANCE_LOW);
         mNotificationInfo.bindNotification(mMockPackageManager, mMockINotificationManager,
                 TEST_PACKAGE_NAME, mNotificationChannel, 1, mSbn, null, null, null, true, false,
-                IMPORTANCE_DEFAULT);
+                IMPORTANCE_DEFAULT, false);
 
         mNotificationInfo.findViewById(R.id.int_block).performClick();
         waitForUndoButton();
@@ -888,7 +888,8 @@
                 false /* isNonblockable */,
                 true /* isForBlockingHelper */,
                 true /* isUserSentimentNegative */,
-                IMPORTANCE_DEFAULT);
+                IMPORTANCE_DEFAULT,
+                false);
 
         mNotificationInfo.findViewById(R.id.block).performClick();
         waitForUndoButton();
@@ -913,7 +914,7 @@
         mNotificationChannel.setImportance(IMPORTANCE_LOW);
         mNotificationInfo.bindNotification(mMockPackageManager, mMockINotificationManager,
                 TEST_PACKAGE_NAME, mNotificationChannel, 1, mSbn, null, null, null, true, true,
-                IMPORTANCE_DEFAULT);
+                IMPORTANCE_DEFAULT, false);
         mNotificationInfo.findViewById(R.id.minimize).performClick();
         waitForUndoButton();
 
@@ -928,7 +929,7 @@
         mSbn.getNotification().flags = Notification.FLAG_FOREGROUND_SERVICE;
         mNotificationInfo.bindNotification(mMockPackageManager, mMockINotificationManager,
                 TEST_PACKAGE_NAME, mNotificationChannel, 1, mSbn, null, null, null, true, false,
-                IMPORTANCE_DEFAULT);
+                IMPORTANCE_DEFAULT, false);
 
         mNotificationInfo.findViewById(R.id.minimize).performClick();
         waitForUndoButton();
@@ -949,7 +950,7 @@
         mNotificationChannel.setImportance(IMPORTANCE_LOW);
         mNotificationInfo.bindNotification(mMockPackageManager, mMockINotificationManager,
                 TEST_PACKAGE_NAME, mNotificationChannel, 1, mSbn, null, null, null, true, false,
-                IMPORTANCE_DEFAULT);
+                IMPORTANCE_DEFAULT, false);
 
         mNotificationInfo.handleCloseControls(true, false);
 
@@ -967,7 +968,7 @@
         mNotificationChannel.setImportance(IMPORTANCE_LOW);
         mNotificationInfo.bindNotification(mMockPackageManager, mMockINotificationManager,
                 TEST_PACKAGE_NAME, mNotificationChannel, 1, mSbn, null, null, null, true, false,
-                IMPORTANCE_DEFAULT);
+                IMPORTANCE_DEFAULT, false);
 
         mNotificationInfo.findViewById(R.id.int_block).performClick();
         waitForUndoButton();
@@ -988,7 +989,7 @@
         mNotificationChannel.setImportance(IMPORTANCE_LOW);
         mNotificationInfo.bindNotification(mMockPackageManager, mMockINotificationManager,
                 TEST_PACKAGE_NAME, mNotificationChannel, 1, mSbn, null, null, null, true, true,
-                IMPORTANCE_DEFAULT);
+                IMPORTANCE_DEFAULT, false);
 
         mNotificationInfo.findViewById(R.id.minimize).performClick();
         waitForUndoButton();
@@ -1006,7 +1007,7 @@
         mNotificationChannel.setImportance(IMPORTANCE_DEFAULT);
         mNotificationInfo.bindNotification(mMockPackageManager, mMockINotificationManager,
                 TEST_PACKAGE_NAME, mNotificationChannel, 1, mSbn, null, null, null, true, false,
-                IMPORTANCE_DEFAULT);
+                IMPORTANCE_DEFAULT, true);
 
         mNotificationInfo.findViewById(R.id.int_silent).performClick();
         waitForUndoButton();
@@ -1027,7 +1028,7 @@
         mNotificationChannel.setImportance(IMPORTANCE_LOW);
         mNotificationInfo.bindNotification(mMockPackageManager, mMockINotificationManager,
                 TEST_PACKAGE_NAME, mNotificationChannel, 1, mSbn, null, null, null, true, false,
-                IMPORTANCE_DEFAULT);
+                IMPORTANCE_DEFAULT, false);
 
         mNotificationInfo.findViewById(R.id.int_alert).performClick();
         waitForUndoButton();
@@ -1049,7 +1050,7 @@
         mNotificationChannel.setImportance(IMPORTANCE_UNSPECIFIED);
         mNotificationInfo.bindNotification(mMockPackageManager, mMockINotificationManager,
                 TEST_PACKAGE_NAME, mNotificationChannel, 1, mSbn, null, null, null, true, false,
-                IMPORTANCE_DEFAULT);
+                IMPORTANCE_DEFAULT, true);
 
         mNotificationInfo.findViewById(R.id.int_silent).performClick();
         waitForUndoButton();
@@ -1071,7 +1072,7 @@
         mNotificationChannel.setImportance(IMPORTANCE_UNSPECIFIED);
         mNotificationInfo.bindNotification(mMockPackageManager, mMockINotificationManager,
                 TEST_PACKAGE_NAME, mNotificationChannel, 1, mSbn, null, null, null, true, false,
-                IMPORTANCE_LOW);
+                IMPORTANCE_LOW, false);
 
         mNotificationInfo.findViewById(R.id.int_alert).performClick();
         waitForUndoButton();
@@ -1092,7 +1093,7 @@
         mNotificationChannel.setImportance(IMPORTANCE_LOW);
         mNotificationInfo.bindNotification(mMockPackageManager, mMockINotificationManager,
                 TEST_PACKAGE_NAME, mNotificationChannel, 1, mSbn, null, null, null, true, true,
-                IMPORTANCE_DEFAULT);
+                IMPORTANCE_DEFAULT, false);
 
         mNotificationInfo.findViewById(R.id.minimize).performClick();
         waitForUndoButton();
@@ -1108,7 +1109,7 @@
         mNotificationChannel.setImportance(IMPORTANCE_LOW);
         mNotificationInfo.bindNotification(mMockPackageManager, mMockINotificationManager,
                 TEST_PACKAGE_NAME, mNotificationChannel, 1, mSbn, null, null, null, true, false,
-                IMPORTANCE_DEFAULT);
+                IMPORTANCE_DEFAULT, false);
 
         mNotificationInfo.findViewById(R.id.int_block).performClick();
         waitForUndoButton();
@@ -1125,7 +1126,7 @@
         mNotificationInfo.bindNotification(mMockPackageManager, mMockINotificationManager,
                 TEST_PACKAGE_NAME, mNotificationChannel, 1, mSbn,
                 (Runnable saveImportance, StatusBarNotification sbn) -> {
-                }, null, null, true, true, IMPORTANCE_DEFAULT);
+                }, null, null, true, true, IMPORTANCE_DEFAULT, false);
 
         mNotificationInfo.findViewById(R.id.int_block).performClick();
         mTestableLooper.processAllMessages();
@@ -1143,7 +1144,7 @@
                 TEST_PACKAGE_NAME, mNotificationChannel, 1, mSbn,
                 (Runnable saveImportance, StatusBarNotification sbn) -> {
                     saveImportance.run();
-                }, null, null, true, false, IMPORTANCE_DEFAULT
+                }, null, null, true, false, IMPORTANCE_DEFAULT, false
         );
 
         mNotificationInfo.findViewById(R.id.int_block).performClick();
@@ -1170,7 +1171,7 @@
         mNotificationChannel.setImportance(IMPORTANCE_LOW);
         mNotificationInfo.bindNotification(mMockPackageManager, mMockINotificationManager,
                 TEST_PACKAGE_NAME, mNotificationChannel, 1, mSbn, null, null, null, true, true,
-                IMPORTANCE_DEFAULT);
+                IMPORTANCE_DEFAULT, false);
 
         mNotificationInfo.findViewById(R.id.minimize).performClick();
         waitForUndoButton();
@@ -1183,7 +1184,7 @@
         mNotificationChannel.setImportance(IMPORTANCE_LOW);
         mNotificationInfo.bindNotification(mMockPackageManager, mMockINotificationManager,
                 TEST_PACKAGE_NAME, mNotificationChannel, 1, mSbn, null, null, null, true, false,
-                IMPORTANCE_DEFAULT);
+                IMPORTANCE_DEFAULT, false);
 
         mNotificationInfo.findViewById(R.id.int_block).performClick();
         waitForUndoButton();
@@ -1196,7 +1197,7 @@
         mNotificationChannel.setImportance(IMPORTANCE_DEFAULT);
         mNotificationInfo.bindNotification(mMockPackageManager, mMockINotificationManager,
                 TEST_PACKAGE_NAME, mNotificationChannel, 1, mSbn, null, null, null, true, false,
-                IMPORTANCE_DEFAULT);
+                IMPORTANCE_DEFAULT, true);
 
         mNotificationInfo.findViewById(R.id.int_silent).performClick();
         waitForUndoButton();
@@ -1210,7 +1211,7 @@
         mNotificationChannel.setImportance(IMPORTANCE_LOW);
         mNotificationInfo.bindNotification(mMockPackageManager, mMockINotificationManager,
                 TEST_PACKAGE_NAME, mNotificationChannel, 1, mSbn, null, null, null, true, false,
-                IMPORTANCE_DEFAULT);
+                IMPORTANCE_DEFAULT, false);
 
         mNotificationInfo.findViewById(R.id.int_alert).performClick();
         waitForUndoButton();
@@ -1224,7 +1225,7 @@
         mNotificationChannel.setImportance(IMPORTANCE_LOW);
         mNotificationInfo.bindNotification(mMockPackageManager, mMockINotificationManager,
                 TEST_PACKAGE_NAME, mNotificationChannel, 1, mSbn, null, null, null, true, false,
-                IMPORTANCE_DEFAULT);
+                IMPORTANCE_DEFAULT, false);
 
         mNotificationInfo.findViewById(R.id.int_block).performClick();
         waitForUndoButton();
@@ -1236,7 +1237,7 @@
         mNotificationChannel.setImportance(IMPORTANCE_LOW);
         mNotificationInfo.bindNotification(mMockPackageManager, mMockINotificationManager,
                 TEST_PACKAGE_NAME, mNotificationChannel, 1, mSbn, null, null, null, true, false,
-                IMPORTANCE_DEFAULT);
+                IMPORTANCE_DEFAULT, false);
 
         mNotificationInfo.findViewById(R.id.int_block).performClick();
         waitForUndoButton();
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayoutTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayoutTest.java
index 736f384..ae70b01 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayoutTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayoutTest.java
@@ -352,7 +352,7 @@
                     RETURNS_DEEP_STUBS);
             String key = Integer.toString(i);
             when(row.getStatusBarNotification().getKey()).thenReturn(key);
-            when(mNotificationData.isHighPriority(row.getStatusBarNotification())).thenReturn(true);
+            when(row.getEntry().isHighPriority()).thenReturn(true);
             when(mStackScroller.getChildAt(i)).thenReturn(row);
         }
 
@@ -368,8 +368,7 @@
                     RETURNS_DEEP_STUBS);
             String key = Integer.toString(i);
             when(row.getStatusBarNotification().getKey()).thenReturn(key);
-            when(mNotificationData.isHighPriority(row.getStatusBarNotification()))
-                    .thenReturn(false);
+            when(row.getEntry().isHighPriority()).thenReturn(false);
             when(mStackScroller.getChildAt(i)).thenReturn(row);
         }
 
@@ -385,8 +384,7 @@
                     RETURNS_DEEP_STUBS);
             String key = Integer.toString(i);
             when(row.getStatusBarNotification().getKey()).thenReturn(key);
-            when(mNotificationData.isHighPriority(row.getStatusBarNotification()))
-                    .thenReturn(i < 3);
+            when(row.getEntry().isHighPriority()).thenReturn(i < 3);
             when(mStackScroller.getChildAt(i)).thenReturn(row);
         }
 
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/AutoTileManagerTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/AutoTileManagerTest.java
index c0f7f0c..1ded835 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/AutoTileManagerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/AutoTileManagerTest.java
@@ -85,7 +85,7 @@
             return;
         }
         mAutoTileManager.mColorDisplayCallback.onAutoModeChanged(
-                ColorDisplayController.AUTO_MODE_TWILIGHT);
+                ColorDisplayManager.AUTO_MODE_TWILIGHT);
         verify(mQsTileHost).addTile("night");
     }
 
@@ -95,7 +95,7 @@
             return;
         }
         mAutoTileManager.mColorDisplayCallback.onAutoModeChanged(
-                ColorDisplayController.AUTO_MODE_CUSTOM);
+                ColorDisplayManager.AUTO_MODE_CUSTOM_TIME);
         verify(mQsTileHost).addTile("night");
     }
 
@@ -105,7 +105,7 @@
             return;
         }
         mAutoTileManager.mColorDisplayCallback.onAutoModeChanged(
-                ColorDisplayController.AUTO_MODE_DISABLED);
+                ColorDisplayManager.AUTO_MODE_DISABLED);
         verify(mQsTileHost, never()).addTile("night");
     }
 }
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 efa4e79..73fcb01 100644
--- a/proto/src/metrics_constants/metrics_constants.proto
+++ b/proto/src/metrics_constants/metrics_constants.proto
@@ -6780,22 +6780,22 @@
     CONVERSATION_ACTIONS = 1615;
 
     // ACTION: Actions from a text classifier are shown to user.
-    // CATEGORY: CONVERSATION_ACTIONS
+    // CATEGORY: LANGUAGE_DETECTION, CONVERSATION_ACTIONS
     // OS: Q
     ACTION_TEXT_CLASSIFIER_ACTIONS_SHOWN = 1616;
 
-    // ACTION: Event time of a text classifier event in unix timestamp.
-    // CATEGORY: CONVERSATION_ACTIONS, LANGUAGE_DETECTION
+    // FIELD: Event time of a text classifier event in unix timestamp.
+    // CATEGORY: LANGUAGE_DETECTION, CONVERSATION_ACTIONS
     // OS: Q
     FIELD_TEXT_CLASSIFIER_EVENT_TIME = 1617;
 
     // ACTION: Users compose their own replies instead of using suggested ones.
-    // CATEGORY: CONVERSATION_ACTIONS
+    // CATEGORY: LANGUAGE_DETECTION, CONVERSATION_ACTIONS
     // OS: Q
     ACTION_TEXT_CLASSIFIER_MANUAL_REPLY = 1618;
 
     // ACTION: Text classifier generates an action.
-    // CATEGORY: CONVERSATION_ACTIONS
+    // CATEGORY: LANGUAGE_DETECTION, CONVERSATION_ACTIONS
     // OS: Q
     ACTION_TEXT_CLASSIFIER_ACTIONS_GENERATED = 1619;
 
@@ -6840,7 +6840,7 @@
     // OPEN: Settings > Display > Adaptive sleep
     // OS: Q
     SETTINGS_ADAPTIVE_SLEEP = 1628;
-    
+
     // Tagged data for SMART_REPLY_VISIBLE and NOTIFICATION_ITEM_ACTION.
     // The UI location of the notification containing the smart suggestions.
     // This is a NotificationLocation object (see the NotificationLocation
@@ -6862,6 +6862,48 @@
     // OS: Q
     FIELD_AUTOFILL_NUMBER_AUGMENTED_REQUESTS = 1631;
 
+    // OPEN: Settings > System > Aware
+    // OS: Q
+    SETTINGS_AWARE = 1632;
+
+    // OPEN: Settings > System > Aware > Disable > Dialog
+    // OS: Q
+    DIALOG_AWARE_DISABLE = 1633;
+
+    // FIELD: Session ID of TextClassifierEvent.
+    // CATEGORY: LANGUAGE_DETECTION, CONVERSATION_ACTIONS
+    // OS: Q
+    FIELD_TEXT_CLASSIFIER_SESSION_ID = 1634;
+
+    // FIELD: First entity type.
+    // CATEGORY: LANGUAGE_DETECTION, CONVERSATION_ACTIONS
+    // OS: Q
+    FIELD_TEXT_CLASSIFIER_FIRST_ENTITY_TYPE = 1635;
+    // FIELD: Second entity type.
+    // CATEGORY: LANGUAGE_DETECTION, CONVERSATION_ACTIONS
+    // OS: Q
+    FIELD_TEXT_CLASSIFIER_SECOND_ENTITY_TYPE = 1636;
+
+    // FIELD: Third entity type.
+    // CATEGORY: LANGUAGE_DETECTION, CONVERSATION_ACTIONS
+    // OS: Q
+    FIELD_TEXT_CLASSIFIER_THIRD_ENTITY_TYPE = 1637;
+
+    // FIELD: Score of the suggestion.
+    // CATEGORY: LANGUAGE_DETECTION, CONVERSATION_ACTIONS
+    // OS: Q
+    FIELD_TEXT_CLASSIFIER_SCORE = 1638;
+
+    // FIELD: widget type, e.g: notification, textview
+    // CATEGORY: LANGUAGE_DETECTION, CONVERSATION_ACTIONS
+    // OS: Q
+    FIELD_TEXT_CLASSIFIER_WIDGET_TYPE = 1639;
+
+    // FIELD: version of the widget.
+    // CATEGORY: LANGUAGE_DETECTION, CONVERSATION_ACTIONS
+    // OS: Q
+    FIELD_TEXT_CLASSIFIER_WIDGET_VERSION = 1640;
+
     // ---- End Q Constants, all Q constants go above this line ----
     // Add new aosp constants above this line.
     // END OF AOSP CONSTANTS
diff --git a/proto/src/system_messages.proto b/proto/src/system_messages.proto
index 6ff2b35..3a89316 100644
--- a/proto/src/system_messages.proto
+++ b/proto/src/system_messages.proto
@@ -220,6 +220,12 @@
     // Package: android
     NOTE_NETWORK_SUGGESTION_AVAILABLE = 51;
 
+    // Inform the user that the contaminant is detected on the USB port
+    NOTE_USB_CONTAMINANT_DETECTED = 52;
+
+    // Inform that user that the USB port is free of contaminants.
+    NOTE_USB_CONTAMINANT_NOT_DETECTED = 53;
+
     // ADD_NEW_IDS_ABOVE_THIS_LINE
     // Legacy IDs with arbitrary values appear below
     // Legacy IDs existed as stable non-conflicting constants prior to the O release
@@ -235,6 +241,8 @@
     NOTE_NETWORK_LOST_INTERNET = 742;
     // The system default network switched to a different network
     NOTE_NETWORK_SWITCH = 743;
+    // Device logged-in captive portal network successfully
+    NOTE_NETWORK_LOGGED_IN = 744;
 
     // Notify the user that their work profile has been deleted
     // Package: android
diff --git a/proto/src/wifi.proto b/proto/src/wifi.proto
index 79b63bc..c063e82 100644
--- a/proto/src/wifi.proto
+++ b/proto/src/wifi.proto
@@ -491,6 +491,9 @@
 
   // List of PNO scan stats, one element for each mobility state
   repeated DeviceMobilityStatePnoScanStats mobility_state_pno_stats_list = 128;
+
+  // Wifi p2p statistics
+  optional WifiP2pStats wifi_p2p_stats = 129;
 }
 
 // Information that gets logged for every WiFi connection.
@@ -962,6 +965,10 @@
 
   // NetworkAgent Wifi usability score of connected wifi
   optional int32 last_wifi_usability_score = 15 [default = -1];
+
+  // Prediction horizon (in second) of Wifi usability score provided by external
+  // system app
+  optional int32 last_prediction_horizon_sec = 16 [default = -1];
 }
 
 // Wi-Fi Aware metrics
@@ -1679,6 +1686,10 @@
   // NetworkAgent wifi usability score of connected wifi.
   // Defaults to -1 if the score was never set.
   optional int32 last_wifi_usability_score = 11 [default = -1];
+
+  // Prediction horizon (in second) of Wifi usability score provided by external
+  // system app
+  optional int32 last_prediction_horizon_sec = 12 [default = -1];
 }
 
 message PasspointProfileTypeCount {
@@ -1800,6 +1811,21 @@
 
   // Sequence number from external system app to framework
   optional int32 seq_num_to_framework = 19;
+
+  // The total time CCA is on busy status on the current frequency in ms
+  // counted from the last radio chip reset
+  optional int64 total_cca_busy_freq_time_ms = 20;
+
+  // The total radio on time of the current frequency from the last radio
+  // chip reset
+  optional int64 total_radio_on_freq_time_ms = 21;
+
+  // The total number of beacons received from the last radio chip reset
+  optional int64 total_beacon_rx = 22;
+
+  // Prediction horizon (in second) of Wifi usability score provided by external
+  // system app
+  optional int32 prediction_horizon_sec = 23;
 }
 
 message WifiUsabilityStats {
@@ -1849,3 +1875,135 @@
   // the total duration elapsed while in this mobility state with PNO scans running, in ms
   optional int64 pno_duration_ms = 4;
 }
+
+// The information about the Wifi P2p events.
+message WifiP2pStats {
+
+  // Group event list tracking sessions and client counts in tethered mode.
+  repeated GroupEvent group_event = 1;
+
+  // Session information that gets logged for every Wifi P2p connection.
+  repeated P2pConnectionEvent connection_event = 2;
+
+  // Number of persistent group in the user profile.
+  optional int32 num_persistent_group = 3;
+
+  // Number of peer scan.
+  optional int32 num_total_peer_scans = 4;
+
+  // Number of service scan.
+  optional int32 num_total_service_scans = 5;
+}
+
+message P2pConnectionEvent {
+
+  enum ConnectionType {
+
+    // fresh new connection.
+    CONNECTION_FRESH = 0;
+
+    // reinvoke a group.
+    CONNECTION_REINVOKE = 1;
+
+    // create a group with the current device as the group owner locally.
+    CONNECTION_LOCAL = 2;
+
+    // create a group or join a group with config.
+    CONNECTION_FAST = 3;
+  }
+
+  enum ConnectivityLevelFailure {
+
+    // Failure is unknown.
+    CLF_UNKNOWN = 0;
+
+    // No failure.
+    CLF_NONE = 1;
+
+    // Timeout for current connecting request.
+    CLF_TIMEOUT = 2;
+
+    // The connecting request is canceled by the user.
+    CLF_CANCEL = 3;
+
+    // Provision discovery failure, e.g. no pin code, timeout, rejected by the peer.
+    CLF_PROV_DISC_FAIL = 4;
+
+    // Invitation failure, e.g. rejected by the peer.
+    CLF_INVITATION_FAIL = 5;
+
+    // Incoming request is rejected by the user.
+    CLF_USER_REJECT = 6;
+
+    // New connection request is issued before ending previous connecting request.
+    CLF_NEW_CONNECTION_ATTEMPT = 7;
+  }
+
+  // WPS method.
+  enum WpsMethod {
+    // WPS is skipped for Group Reinvoke.
+    WPS_NA = -1;
+
+    // Push button configuration.
+    WPS_PBC = 0;
+
+    // Display pin method configuration - pin is generated and displayed on device.
+    WPS_DISPLAY = 1;
+
+    // Keypad pin method configuration - pin is entered on device.
+    WPS_KEYPAD = 2;
+
+    // Label pin method configuration - pin is labelled on device.
+    WPS_LABEL = 3;
+  }
+
+  // Start time of the connection.
+  optional int64 start_time_millis = 1;
+
+  // Type of the connection.
+  optional ConnectionType connection_type = 2;
+
+  // WPS method.
+  optional WpsMethod wps_method = 3 [default = WPS_NA];
+
+  // Duration to connect.
+  optional int32 duration_taken_to_connect_millis = 4;
+
+  // Failures that happen at the connectivity layer.
+  optional ConnectivityLevelFailure connectivity_level_failure_code = 5;
+}
+
+// GroupEvent tracking group information from GroupStarted to GroupRemoved.
+message GroupEvent {
+
+  enum GroupRole {
+
+    GROUP_OWNER = 0;
+
+    GROUP_CLIENT = 1;
+  }
+
+  // The ID of network in supplicant for this group.
+  optional int32 net_id = 1;
+
+  // Start time of the group.
+  optional int64 start_time_millis = 2;
+
+  // Channel frequency used for Group.
+  optional int32 channel_frequency = 3;
+
+  // Is group owner or group client.
+  optional GroupRole group_role = 5;
+
+  // Number of connected clients.
+  optional int32 num_connected_clients = 6;
+
+  // Cumulative number of connected clients.
+  optional int32 num_cumulative_clients = 7;
+
+  // The session duration.
+  optional int32 session_duration_millis = 8;
+
+  // The idle duration. A group without any client is in idle.
+  optional int32 idle_duration_millis = 9;
+}
diff --git a/services/accessibility/java/com/android/server/accessibility/AccessibilityInputFilter.java b/services/accessibility/java/com/android/server/accessibility/AccessibilityInputFilter.java
index cf08681..303230b 100644
--- a/services/accessibility/java/com/android/server/accessibility/AccessibilityInputFilter.java
+++ b/services/accessibility/java/com/android/server/accessibility/AccessibilityInputFilter.java
@@ -17,13 +17,11 @@
 package com.android.server.accessibility;
 
 import android.content.Context;
-import android.os.Handler;
 import android.os.PowerManager;
-import android.util.DebugUtils;
-import android.util.ExceptionUtils;
-import android.util.Log;
 import android.util.Slog;
+import android.util.SparseArray;
 import android.util.SparseBooleanArray;
+import android.view.Display;
 import android.view.InputDevice;
 import android.view.InputEvent;
 import android.view.InputFilter;
@@ -31,10 +29,11 @@
 import android.view.MotionEvent;
 import android.view.accessibility.AccessibilityEvent;
 
-import com.android.internal.util.BitUtils;
 import com.android.server.LocalServices;
 import com.android.server.policy.WindowManagerPolicy;
 
+import java.util.ArrayList;
+
 /**
  * This class is an input filter for implementing accessibility features such
  * as display magnification and explore by touch.
@@ -108,23 +107,24 @@
 
     private final AccessibilityManagerService mAms;
 
-    private boolean mInstalled;
+    private final SparseArray<EventStreamTransformation> mEventHandler;
 
-    private int mUserId;
+    private final SparseArray<TouchExplorer> mTouchExplorer = new SparseArray<>(0);
 
-    private int mEnabledFeatures;
+    private final SparseArray<MagnificationGestureHandler> mMagnificationGestureHandler =
+            new SparseArray<>(0);
 
-    private TouchExplorer mTouchExplorer;
-
-    private MagnificationGestureHandler mMagnificationGestureHandler;
-
-    private MotionEventInjector mMotionEventInjector;
+    private final SparseArray<MotionEventInjector> mMotionEventInjector = new SparseArray<>(0);
 
     private AutoclickController mAutoclickController;
 
     private KeyboardInterceptor mKeyboardInterceptor;
 
-    private EventStreamTransformation mEventHandler;
+    private boolean mInstalled;
+
+    private int mUserId;
+
+    private int mEnabledFeatures;
 
     private EventStreamState mMouseStreamState;
 
@@ -133,10 +133,16 @@
     private EventStreamState mKeyboardStreamState;
 
     AccessibilityInputFilter(Context context, AccessibilityManagerService service) {
+        this(context, service, new SparseArray<>(0));
+    }
+
+    AccessibilityInputFilter(Context context, AccessibilityManagerService service,
+            SparseArray<EventStreamTransformation> eventHandler) {
         super(context.getMainLooper());
         mContext = context;
         mAms = service;
         mPm = (PowerManager) context.getSystemService(Context.POWER_SERVICE);
+        mEventHandler = eventHandler;
     }
 
     @Override
@@ -160,6 +166,13 @@
         super.onUninstalled();
     }
 
+    void onDisplayChanged() {
+        if (mInstalled) {
+            disableFeatures();
+            enableFeatures();
+        }
+    }
+
     @Override
     public void onInputEvent(InputEvent event, int policyFlags) {
         if (DEBUG) {
@@ -167,8 +180,8 @@
                     + Integer.toHexString(policyFlags));
         }
 
-        if (mEventHandler == null) {
-            if (DEBUG) Slog.d(TAG, "mEventHandler == null for event " + event);
+        if (mEventHandler.size() == 0) {
+            if (DEBUG) Slog.d(TAG, "No mEventHandler for event " + event);
             super.onInputEvent(event, policyFlags);
             return;
         }
@@ -182,16 +195,16 @@
         int eventSource = event.getSource();
         if ((policyFlags & WindowManagerPolicy.FLAG_PASS_TO_USER) == 0) {
             state.reset();
-            mEventHandler.clearEvents(eventSource);
+            clearEventsForAllEventHandlers(eventSource);
             super.onInputEvent(event, policyFlags);
             return;
         }
 
-        if (state.updateDeviceId(event.getDeviceId())) {
-            mEventHandler.clearEvents(eventSource);
+        if (state.updateInputSource(event.getSource())) {
+            clearEventsForAllEventHandlers(eventSource);
         }
 
-        if (!state.deviceIdValid()) {
+        if (!state.inputSourceValid()) {
             super.onInputEvent(event, policyFlags);
             return;
         }
@@ -240,6 +253,15 @@
         return null;
     }
 
+    private void clearEventsForAllEventHandlers(int eventSource) {
+        for (int i = 0; i < mEventHandler.size(); i++) {
+            final EventStreamTransformation eventHandler = mEventHandler.valueAt(i);
+            if (eventHandler != null) {
+                eventHandler.clearEvents(eventSource);
+            }
+        }
+    }
+
     private void processMotionEvent(EventStreamState state, MotionEvent event, int policyFlags) {
         if (!state.shouldProcessScroll() && event.getActionMasked() == MotionEvent.ACTION_SCROLL) {
             super.onInputEvent(event, policyFlags);
@@ -258,7 +280,10 @@
             super.onInputEvent(event, policyFlags);
             return;
         }
-        mEventHandler.onKeyEvent(event, policyFlags);
+        // Since the display id of KeyEvent always would be -1 and there is only one
+        // KeyboardInterceptor for all display, pass KeyEvent to the mEventHandler of
+        // DEFAULT_DISPLAY to handle.
+        mEventHandler.get(Display.DEFAULT_DISPLAY).onKeyEvent(event, policyFlags);
     }
 
     private void handleMotionEvent(MotionEvent event, int policyFlags) {
@@ -267,10 +292,16 @@
         }
         mPm.userActivity(event.getEventTime(), false);
         MotionEvent transformedEvent = MotionEvent.obtain(event);
-        mEventHandler.onMotionEvent(transformedEvent, event, policyFlags);
+        final int displayId = event.getDisplayId();
+        mEventHandler.get(isDisplayIdValid(displayId) ? displayId : Display.DEFAULT_DISPLAY)
+                .onMotionEvent(transformedEvent, event, policyFlags);
         transformedEvent.recycle();
     }
 
+    private boolean isDisplayIdValid(int displayId) {
+        return mEventHandler.get(displayId) != null;
+    }
+
     @Override
     public void onMotionEvent(MotionEvent transformedEvent, MotionEvent rawEvent,
             int policyFlags) {
@@ -323,14 +354,20 @@
     }
 
     void notifyAccessibilityEvent(AccessibilityEvent event) {
-        if (mEventHandler != null) {
-            mEventHandler.onAccessibilityEvent(event);
+        for (int i = 0; i < mEventHandler.size(); i++) {
+            final EventStreamTransformation eventHandler = mEventHandler.valueAt(i);
+            if (eventHandler != null) {
+                eventHandler.onAccessibilityEvent(event);
+            }
         }
     }
 
-    void notifyAccessibilityButtonClicked() {
-        if (mMagnificationGestureHandler != null) {
-            mMagnificationGestureHandler.notifyShortcutTriggered();
+    void notifyAccessibilityButtonClicked(int displayId) {
+        if (mMagnificationGestureHandler.size() != 0) {
+            final MagnificationGestureHandler handler = mMagnificationGestureHandler.get(displayId);
+            if (handler != null) {
+                handler.notifyShortcutTriggered();
+            }
         }
     }
 
@@ -339,81 +376,124 @@
 
         resetStreamState();
 
+        final ArrayList<Display> displaysList = mAms.getValidDisplayList();
+
         if ((mEnabledFeatures & FLAG_FEATURE_AUTOCLICK) != 0) {
             mAutoclickController = new AutoclickController(mContext, mUserId);
-            addFirstEventHandler(mAutoclickController);
+            addFirstEventHandlerForAllDisplays(displaysList, mAutoclickController);
         }
 
-        if ((mEnabledFeatures & FLAG_FEATURE_TOUCH_EXPLORATION) != 0) {
-            mTouchExplorer = new TouchExplorer(mContext, mAms);
-            addFirstEventHandler(mTouchExplorer);
-        }
+        for (int i = displaysList.size() - 1; i >= 0; i--) {
+            final int displayId = displaysList.get(i).getDisplayId();
 
-        if ((mEnabledFeatures & FLAG_FEATURE_CONTROL_SCREEN_MAGNIFIER) != 0
-                || ((mEnabledFeatures & FLAG_FEATURE_SCREEN_MAGNIFIER) != 0)
-                || ((mEnabledFeatures & FLAG_FEATURE_TRIGGERED_SCREEN_MAGNIFIER) != 0)) {
-            final boolean detectControlGestures = (mEnabledFeatures
-                    & FLAG_FEATURE_SCREEN_MAGNIFIER) != 0;
-            final boolean triggerable = (mEnabledFeatures
-                    & FLAG_FEATURE_TRIGGERED_SCREEN_MAGNIFIER) != 0;
-            mMagnificationGestureHandler = new MagnificationGestureHandler(
-                    mContext, mAms.getMagnificationController(),
-                    detectControlGestures, triggerable);
-            addFirstEventHandler(mMagnificationGestureHandler);
-        }
+            if ((mEnabledFeatures & FLAG_FEATURE_TOUCH_EXPLORATION) != 0) {
+                TouchExplorer explorer = new TouchExplorer(mContext, mAms);
+                addFirstEventHandler(displayId, explorer);
+                mTouchExplorer.put(displayId, explorer);
+            }
 
-        if ((mEnabledFeatures & FLAG_FEATURE_INJECT_MOTION_EVENTS) != 0) {
-            mMotionEventInjector = new MotionEventInjector(mContext.getMainLooper());
-            addFirstEventHandler(mMotionEventInjector);
-            mAms.setMotionEventInjector(mMotionEventInjector);
+            if ((mEnabledFeatures & FLAG_FEATURE_CONTROL_SCREEN_MAGNIFIER) != 0
+                    || ((mEnabledFeatures & FLAG_FEATURE_SCREEN_MAGNIFIER) != 0)
+                    || ((mEnabledFeatures & FLAG_FEATURE_TRIGGERED_SCREEN_MAGNIFIER) != 0)) {
+                final boolean detectControlGestures = (mEnabledFeatures
+                        & FLAG_FEATURE_SCREEN_MAGNIFIER) != 0;
+                final boolean triggerable = (mEnabledFeatures
+                        & FLAG_FEATURE_TRIGGERED_SCREEN_MAGNIFIER) != 0;
+                MagnificationGestureHandler magnificationGestureHandler =
+                        new MagnificationGestureHandler(mContext,
+                                mAms.getMagnificationController(),
+                                detectControlGestures, triggerable, displayId);
+                addFirstEventHandler(displayId, magnificationGestureHandler);
+                mMagnificationGestureHandler.put(displayId, magnificationGestureHandler);
+            }
+
+            if ((mEnabledFeatures & FLAG_FEATURE_INJECT_MOTION_EVENTS) != 0) {
+                MotionEventInjector injector = new MotionEventInjector(
+                        mContext.getMainLooper());
+                addFirstEventHandler(displayId, injector);
+                // TODO: Need to set MotionEventInjector per display.
+                mAms.setMotionEventInjector(injector);
+                mMotionEventInjector.put(displayId, injector);
+            }
         }
 
         if ((mEnabledFeatures & FLAG_FEATURE_FILTER_KEY_EVENTS) != 0) {
             mKeyboardInterceptor = new KeyboardInterceptor(mAms,
                     LocalServices.getService(WindowManagerPolicy.class));
-            addFirstEventHandler(mKeyboardInterceptor);
+            // Since the display id of KeyEvent always would be -1 and it would be dispatched to
+            // the display with input focus directly, we only need one KeyboardInterceptor for
+            // default display.
+            addFirstEventHandler(Display.DEFAULT_DISPLAY, mKeyboardInterceptor);
         }
     }
 
     /**
-     * Adds an event handler to the event handler chain. The handler is added at the beginning of
-     * the chain.
+     * Adds an event handler to the event handler chain for giving display. The handler is added at
+     * the beginning of the chain.
      *
+     * @param displayId The logical display id.
      * @param handler The handler to be added to the event handlers list.
      */
-    private void addFirstEventHandler(EventStreamTransformation handler) {
-        if (mEventHandler != null) {
-            handler.setNext(mEventHandler);
+    private void addFirstEventHandler(int displayId, EventStreamTransformation handler) {
+        EventStreamTransformation eventHandler = mEventHandler.get(displayId);
+        if (eventHandler != null) {
+            handler.setNext(eventHandler);
         } else {
             handler.setNext(this);
         }
-        mEventHandler = handler;
+        eventHandler = handler;
+        mEventHandler.put(displayId, eventHandler);
+    }
+
+    /**
+     * Adds an event handler to the event handler chain for all displays. The handler is added at
+     * the beginning of the chain.
+     *
+     * @param displayList The list of displays
+     * @param handler The handler to be added to the event handlers list.
+     */
+    private void addFirstEventHandlerForAllDisplays(ArrayList<Display> displayList,
+            EventStreamTransformation handler) {
+        for (int i = 0; i < displayList.size(); i++) {
+            final int displayId = displayList.get(i).getDisplayId();
+            addFirstEventHandler(displayId, handler);
+        }
     }
 
     private void disableFeatures() {
-        if (mMotionEventInjector != null) {
+        for (int i = mMotionEventInjector.size() - 1; i >= 0; i--) {
+            final MotionEventInjector injector = mMotionEventInjector.valueAt(i);
+            // TODO: Need to set MotionEventInjector per display.
             mAms.setMotionEventInjector(null);
-            mMotionEventInjector.onDestroy();
-            mMotionEventInjector = null;
+            if (injector != null) {
+                injector.onDestroy();
+            }
         }
+        mMotionEventInjector.clear();
         if (mAutoclickController != null) {
             mAutoclickController.onDestroy();
             mAutoclickController = null;
         }
-        if (mTouchExplorer != null) {
-            mTouchExplorer.onDestroy();
-            mTouchExplorer = null;
+        for (int i = mTouchExplorer.size() - 1; i >= 0; i--) {
+            final TouchExplorer explorer = mTouchExplorer.valueAt(i);
+            if (explorer != null) {
+                explorer.onDestroy();
+            }
         }
-        if (mMagnificationGestureHandler != null) {
-            mMagnificationGestureHandler.onDestroy();
-            mMagnificationGestureHandler = null;
+        mTouchExplorer.clear();
+        for (int i = mMagnificationGestureHandler.size() - 1; i >= 0; i--) {
+            final MagnificationGestureHandler handler = mMagnificationGestureHandler.valueAt(i);
+            if (handler != null) {
+                handler.onDestroy();
+            }
         }
+        mMagnificationGestureHandler.clear();
         if (mKeyboardInterceptor != null) {
             mKeyboardInterceptor.onDestroy();
             mKeyboardInterceptor = null;
         }
 
-        mEventHandler = null;
+        mEventHandler.clear();
         resetStreamState();
     }
 
@@ -441,41 +521,41 @@
      * whose events should not be handled by a11y event stream transformations.
      */
     private static class EventStreamState {
-        private int mDeviceId;
+        private int mSource;
 
         EventStreamState() {
-            mDeviceId = -1;
+            mSource = -1;
         }
 
         /**
-         * Updates the ID of the device associated with the state. If the ID changes, resets
-         * internal state.
+         * Updates the input source of the device associated with the state. If the source changes,
+         * resets internal state.
          *
-         * @param deviceId Updated input device ID.
-         * @return Whether the device ID has changed.
+         * @param source Updated input source.
+         * @return Whether the input source has changed.
          */
-        public boolean updateDeviceId(int deviceId) {
-            if (mDeviceId == deviceId) {
+        public boolean updateInputSource(int source) {
+            if (mSource == source) {
                 return false;
             }
-            // Reset clears internal state, so make sure it's called before |mDeviceId| is updated.
+            // Reset clears internal state, so make sure it's called before |mSource| is updated.
             reset();
-            mDeviceId = deviceId;
+            mSource = source;
             return true;
         }
 
         /**
-         * @return Whether device ID is valid.
+         * @return Whether input source is valid.
          */
-        public boolean deviceIdValid() {
-            return mDeviceId >= 0;
+        public boolean inputSourceValid() {
+            return mSource >= 0;
         }
 
         /**
          * Resets the event stream state.
          */
         public void reset() {
-            mDeviceId = -1;
+            mSource = -1;
         }
 
         /**
@@ -592,20 +672,19 @@
 
         /*
          * Key events from different devices may be interleaved. For example, the volume up and
-         * down keys can come from different device IDs.
+         * down keys can come from different input sources.
          */
         @Override
-        public boolean updateDeviceId(int deviceId) {
+        public boolean updateInputSource(int deviceId) {
             return false;
         }
 
-        // We manage all device ids simultaneously; there is no concept of validity.
+        // We manage all input source simultaneously; there is no concept of validity.
         @Override
-        public boolean deviceIdValid() {
+        public boolean inputSourceValid() {
             return true;
         }
 
-
         @Override
         final public boolean shouldProcessKeyEvent(KeyEvent event) {
             // For each keyboard device, wait for a down event from a device to start processing
diff --git a/services/accessibility/java/com/android/server/accessibility/AccessibilityManagerService.java b/services/accessibility/java/com/android/server/accessibility/AccessibilityManagerService.java
index dbe86c1..305c53e 100644
--- a/services/accessibility/java/com/android/server/accessibility/AccessibilityManagerService.java
+++ b/services/accessibility/java/com/android/server/accessibility/AccessibilityManagerService.java
@@ -38,6 +38,7 @@
 import android.accessibilityservice.IAccessibilityServiceClient;
 import android.annotation.NonNull;
 import android.annotation.Nullable;
+import android.app.ActivityOptions;
 import android.app.AlertDialog;
 import android.app.AppOpsManager;
 import android.app.PendingIntent;
@@ -208,12 +209,12 @@
 
     private final WindowManagerInternal mWindowManagerService;
 
-    private final DisplayManager mDisplayManager;
-
     private AppWidgetManagerInternal mAppWidgetService;
 
     private final SecurityPolicy mSecurityPolicy;
 
+    private final AccessibilityDisplayListener mA11yDisplayListener;
+
     private final AppOpsManager mAppOpsManager;
 
     private final MainHandler mMainHandler;
@@ -306,12 +307,11 @@
         mAppOpsManager = (AppOpsManager) context.getSystemService(Context.APP_OPS_SERVICE);
         mMainHandler = new MainHandler(mContext.getMainLooper());
         mGlobalActionPerformer = new GlobalActionPerformer(mContext, mWindowManagerService);
-        mDisplayManager = mContext.getSystemService(DisplayManager.class);
+        mA11yDisplayListener = new AccessibilityDisplayListener(mContext, mMainHandler);
 
         registerBroadcastReceivers();
         new AccessibilityContentObserver(mMainHandler).register(
                 context.getContentResolver());
-        registerDisplayListener(mMainHandler);
     }
 
     @Override
@@ -527,30 +527,6 @@
         }, UserHandle.ALL, intentFilter, null, null);
     }
 
-    private void registerDisplayListener(Handler handler) {
-        mDisplayManager.registerDisplayListener(new DisplayManager.DisplayListener() {
-            @Override
-            public void onDisplayAdded(int displayId) {
-                synchronized (mLock) {
-                    UserState userState = getCurrentUserStateLocked();
-                    updateMagnificationLocked(userState);
-                }
-            }
-
-            @Override
-            public void onDisplayRemoved(int displayId) {
-                if (mMagnificationController != null) {
-                    mMagnificationController.onDisplayRemoved(displayId);
-                }
-            }
-
-            @Override
-            public void onDisplayChanged(int displayId) {
-                // do nothing
-            }
-        }, handler);
-    }
-
     @Override
     public long addClient(IAccessibilityManagerClient callback, int userId) {
         synchronized (mLock) {
@@ -937,16 +913,18 @@
     /**
      * Invoked remotely over AIDL by SysUi when the accessibility button within the system's
      * navigation area has been clicked.
+     *
+     * @param displayId The logical display id.
      */
     @Override
-    public void notifyAccessibilityButtonClicked() {
+    public void notifyAccessibilityButtonClicked(int displayId) {
         if (mContext.checkCallingOrSelfPermission(android.Manifest.permission.STATUS_BAR_SERVICE)
                 != PackageManager.PERMISSION_GRANTED) {
             throw new SecurityException("Caller does not hold permission "
                     + android.Manifest.permission.STATUS_BAR_SERVICE);
         }
         synchronized (mLock) {
-            notifyAccessibilityButtonClickedLocked();
+            notifyAccessibilityButtonClickedLocked(displayId);
         }
     }
 
@@ -1249,7 +1227,7 @@
         }
     }
 
-    private void notifyAccessibilityButtonClickedLocked() {
+    private void notifyAccessibilityButtonClickedLocked(int displayId) {
         final UserState state = getCurrentUserStateLocked();
 
         int potentialTargets = state.mIsNavBarMagnificationEnabled ? 1 : 0;
@@ -1266,12 +1244,15 @@
         if (potentialTargets == 1) {
             if (state.mIsNavBarMagnificationEnabled) {
                 mMainHandler.sendMessage(obtainMessage(
-                        AccessibilityManagerService::sendAccessibilityButtonToInputFilter, this));
+                        AccessibilityManagerService::sendAccessibilityButtonToInputFilter, this,
+                        displayId));
                 return;
             } else {
                 for (int i = state.mBoundServices.size() - 1; i >= 0; i--) {
                     final AccessibilityServiceConnection service = state.mBoundServices.get(i);
                     if (service.mRequestAccessibilityButton) {
+                        // TODO(b/120762691): Need to notify each accessibility service if
+                        // accessibility button is clicked per display.
                         service.notifyAccessibilityButtonClickedLocked();
                         return;
                     }
@@ -1281,17 +1262,21 @@
             if (state.mServiceAssignedToAccessibilityButton == null
                     && !state.mIsNavBarMagnificationAssignedToAccessibilityButton) {
                 mMainHandler.sendMessage(obtainMessage(
-                        AccessibilityManagerService::showAccessibilityButtonTargetSelection, this));
+                        AccessibilityManagerService::showAccessibilityButtonTargetSelection, this,
+                        displayId));
             } else if (state.mIsNavBarMagnificationEnabled
                     && state.mIsNavBarMagnificationAssignedToAccessibilityButton) {
                 mMainHandler.sendMessage(obtainMessage(
-                        AccessibilityManagerService::sendAccessibilityButtonToInputFilter, this));
+                        AccessibilityManagerService::sendAccessibilityButtonToInputFilter, this,
+                        displayId));
                 return;
             } else {
                 for (int i = state.mBoundServices.size() - 1; i >= 0; i--) {
                     final AccessibilityServiceConnection service = state.mBoundServices.get(i);
                     if (service.mRequestAccessibilityButton && (service.mComponentName.equals(
                             state.mServiceAssignedToAccessibilityButton))) {
+                        // TODO(b/120762691): Need to notify each accessibility service if
+                        // accessibility button is clicked per display.
                         service.notifyAccessibilityButtonClickedLocked();
                         return;
                     }
@@ -1299,22 +1284,24 @@
             }
             // The user may have turned off the assigned service or feature
             mMainHandler.sendMessage(obtainMessage(
-                    AccessibilityManagerService::showAccessibilityButtonTargetSelection, this));
+                    AccessibilityManagerService::showAccessibilityButtonTargetSelection, this,
+                    displayId));
         }
     }
 
-    private void sendAccessibilityButtonToInputFilter() {
+    private void sendAccessibilityButtonToInputFilter(int displayId) {
         synchronized (mLock) {
             if (mHasInputFilter && mInputFilter != null) {
-                mInputFilter.notifyAccessibilityButtonClicked();
+                mInputFilter.notifyAccessibilityButtonClicked(displayId);
             }
         }
     }
 
-    private void showAccessibilityButtonTargetSelection() {
+    private void showAccessibilityButtonTargetSelection(int displayId) {
         Intent intent = new Intent(AccessibilityManager.ACTION_CHOOSE_ACCESSIBILITY_BUTTON);
         intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK | Intent.FLAG_ACTIVITY_CLEAR_TASK);
-        mContext.startActivityAsUser(intent, UserHandle.of(mCurrentUserId));
+        final Bundle bundle = ActivityOptions.makeBasic().setLaunchDisplayId(displayId).toBundle();
+        mContext.startActivityAsUser(intent, bundle, UserHandle.of(mCurrentUserId));
     }
 
     private void notifyAccessibilityButtonVisibilityChangedLocked(boolean available) {
@@ -2226,32 +2213,22 @@
             return;
         }
 
-        // register all display if global magnification is enabled.
-        final Display[] displays = mDisplayManager.getDisplays();
+        // Get all valid displays and register them if global magnification is enabled.
+        // We would skip overlay display because it uses overlay window to simulate secondary
+        // displays in one display. It's not a real display and there's no input events for it.
+        final ArrayList<Display> displays = getValidDisplayList();
         if (userState.mIsDisplayMagnificationEnabled
                 || userState.mIsNavBarMagnificationEnabled) {
-            for (int i = 0; i < displays.length; i++) {
-                final Display display = displays[i];
-                // Overlay display uses overlay window to simulate secondary displays in
-                // one display. It's not a real display and there's no input events for it.
-                // We should ignore it.
-                if (display.getType() == Display.TYPE_OVERLAY) {
-                    continue;
-                }
+            for (int i = 0; i < displays.size(); i++) {
+                final Display display = displays.get(i);
                 getMagnificationController().register(display.getDisplayId());
             }
             return;
         }
 
-        // register if display has listening magnification services.
-        for (int i = 0; i < displays.length; i++) {
-            final Display display = displays[i];
-            // Overlay display uses overlay window to simulate secondary displays in
-            // one display. It's not a real display and there's no input events for it.
-            // We should ignore it.
-            if (display.getType() == Display.TYPE_OVERLAY) {
-                continue;
-            }
+        // Register if display has listening magnification services.
+        for (int i = 0; i < displays.size(); i++) {
+            final Display display = displays.get(i);
             final int displayId = display.getDisplayId();
             if (userHasListeningMagnificationServicesLocked(userState, displayId)) {
                 getMagnificationController().register(displayId);
@@ -3812,6 +3789,92 @@
         }
     }
 
+    /**
+     * Gets all currently valid logical displays.
+     *
+     * @return An array list containing all valid logical displays.
+     */
+    public ArrayList<Display> getValidDisplayList() {
+        return mA11yDisplayListener.getValidDisplayList();
+    }
+
+    /**
+     * A Utility class to handle display state.
+     */
+    public class AccessibilityDisplayListener implements DisplayManager.DisplayListener {
+        private final DisplayManager mDisplayManager;
+        private final ArrayList<Display> mDisplaysList = new ArrayList<>();
+
+        AccessibilityDisplayListener(Context context, MainHandler handler) {
+            mDisplayManager = (DisplayManager) context.getSystemService(Context.DISPLAY_SERVICE);
+            mDisplayManager.registerDisplayListener(this, handler);
+            initializeDisplayList();
+        }
+
+        ArrayList<Display> getValidDisplayList() {
+            synchronized (mLock) {
+                return mDisplaysList;
+            }
+        }
+
+        private void initializeDisplayList() {
+            final Display[] displays = mDisplayManager.getDisplays();
+            synchronized (mLock) {
+                mDisplaysList.clear();
+                for (int i = 0; i < displays.length; i++) {
+                    // Exclude overlay virtual displays. The display list is for A11yInputFilter
+                    // to create event handler per display. The events should be handled by the
+                    // display which is overlaid by it.
+                    final Display display = displays[i];
+                    if (display.getType() == Display.TYPE_OVERLAY) {
+                        continue;
+                    }
+                    mDisplaysList.add(display);
+                }
+            }
+        }
+
+        @Override
+        public void onDisplayAdded(int displayId) {
+            final Display display = mDisplayManager.getDisplay(displayId);
+            if (display == null || display.getType() == Display.TYPE_OVERLAY) {
+                return;
+            }
+
+            synchronized (mLock) {
+                mDisplaysList.add(display);
+                if (mInputFilter != null) {
+                    mInputFilter.onDisplayChanged();
+                }
+                UserState userState = getCurrentUserStateLocked();
+                updateMagnificationLocked(userState);
+            }
+        }
+
+        @Override
+        public void onDisplayRemoved(int displayId) {
+            synchronized (mLock) {
+                for (int i = 0; i < mDisplaysList.size(); i++) {
+                    if (mDisplaysList.get(i).getDisplayId() == displayId) {
+                        mDisplaysList.remove(i);
+                        break;
+                    }
+                }
+                if (mInputFilter != null) {
+                    mInputFilter.onDisplayChanged();
+                }
+            }
+            if (mMagnificationController != null) {
+                mMagnificationController.onDisplayRemoved(displayId);
+            }
+        }
+
+        @Override
+        public void onDisplayChanged(int displayId) {
+            /* do nothing */
+        }
+    }
+
     /** Represents an {@link AccessibilityManager} */
     class Client {
         final IAccessibilityManagerClient mCallback;
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/Trampoline.java b/services/backup/java/com/android/server/backup/Trampoline.java
index 4f58d79..303734a 100644
--- a/services/backup/java/com/android/server/backup/Trampoline.java
+++ b/services/backup/java/com/android/server/backup/Trampoline.java
@@ -18,6 +18,7 @@
 
 import static com.android.server.backup.BackupManagerService.TAG;
 
+import android.Manifest;
 import android.annotation.Nullable;
 import android.annotation.UserIdInt;
 import android.app.admin.DevicePolicyManager;
@@ -41,9 +42,10 @@
 import android.os.SystemProperties;
 import android.os.Trace;
 import android.os.UserHandle;
-import android.provider.Settings;
+import android.os.UserManager;
 import android.util.Slog;
 
+import com.android.internal.annotations.GuardedBy;
 import com.android.internal.util.DumpUtils;
 
 import java.io.File;
@@ -75,19 +77,25 @@
  * system user is unlocked before any other users.
  */
 public class Trampoline extends IBackupManager.Stub {
-    // When this file is present, the backup service is inactive.
+    /**
+     * Name of file that disables the backup service. If this file exists, then backup is disabled
+     * for all users.
+     */
     private static final String BACKUP_SUPPRESS_FILENAME = "backup-suppress";
 
+    /**
+     * Name of file for non-system users that enables the backup service for the user. Backup is
+     * disabled by default in non-system users.
+     */
+    private static final String BACKUP_ACTIVATED_FILENAME = "backup-activated";
+
     // Product-level suppression of backup/restore.
     private static final String BACKUP_DISABLE_PROPERTY = "ro.backup.disable";
 
     private static final String BACKUP_THREAD = "backup";
 
-    /** Values for setting {@link Settings.Global#BACKUP_MULTI_USER_ENABLED} */
-    private static final int MULTI_USER_DISABLED = 0;
-    private static final int MULTI_USER_ENABLED = 1;
-
     private final Context mContext;
+    private final UserManager mUserManager;
 
     private final boolean mGlobalDisable;
     // Lock to write backup suppress files.
@@ -104,20 +112,13 @@
         mHandlerThread = new HandlerThread(BACKUP_THREAD, Process.THREAD_PRIORITY_BACKGROUND);
         mHandlerThread.start();
         mHandler = new Handler(mHandlerThread.getLooper());
+        mUserManager = UserManager.get(context);
     }
 
     protected boolean isBackupDisabled() {
         return SystemProperties.getBoolean(BACKUP_DISABLE_PROPERTY, false);
     }
 
-    private boolean isMultiUserEnabled() {
-        return Settings.Global.getInt(
-                mContext.getContentResolver(),
-                Settings.Global.BACKUP_MULTI_USER_ENABLED,
-                MULTI_USER_DISABLED)
-                == MULTI_USER_ENABLED;
-    }
-
     protected int binderGetCallingUserId() {
         return Binder.getCallingUserHandle().getIdentifier();
     }
@@ -126,21 +127,65 @@
         return Binder.getCallingUid();
     }
 
-    protected File getSuppressFileForUser(int userId) {
-        return new File(UserBackupManagerFiles.getBaseStateDir(userId),
+    /** Stored in the system user's directory. */
+    protected File getSuppressFileForSystemUser() {
+        return new File(UserBackupManagerFiles.getBaseStateDir(UserHandle.USER_SYSTEM),
                 BACKUP_SUPPRESS_FILENAME);
     }
 
-    protected void createBackupSuppressFileForUser(int userId) throws IOException {
-        synchronized (mStateLock) {
-            getSuppressFileForUser(userId).getParentFile().mkdirs();
-            getSuppressFileForUser(userId).createNewFile();
+    /** Stored in the system user's directory and the file is indexed by the user it refers to. */
+    protected File getActivatedFileForNonSystemUser(int userId) {
+        return new File(UserBackupManagerFiles.getBaseStateDir(UserHandle.USER_SYSTEM),
+                BACKUP_ACTIVATED_FILENAME + "-" + userId);
+    }
+
+    private void createFile(File file) throws IOException {
+        if (file.exists()) {
+            return;
+        }
+
+        file.getParentFile().mkdirs();
+        if (!file.createNewFile()) {
+            Slog.w(TAG, "Failed to create file " + file.getPath());
         }
     }
 
-    private void deleteBackupSuppressFileForUser(int userId) {
-        if (!getSuppressFileForUser(userId).delete()) {
-            Slog.w(TAG, "Failed deleting backup suppressed file for user: " + userId);
+    private void deleteFile(File file) {
+        if (!file.exists()) {
+            return;
+        }
+
+        if (!file.delete()) {
+            Slog.w(TAG, "Failed to delete file " + file.getPath());
+        }
+    }
+
+    /**
+     * Deactivates the backup service for user {@code userId}. If this is the system user, it
+     * creates a suppress file which disables backup for all users. If this is a non-system user, it
+     * only deactivates backup for that user by deleting its activate file.
+     */
+    @GuardedBy("mStateLock")
+    private void deactivateBackupForUserLocked(int userId) throws IOException {
+        if (userId == UserHandle.USER_SYSTEM) {
+            createFile(getSuppressFileForSystemUser());
+        } else {
+            deleteFile(getActivatedFileForNonSystemUser(userId));
+        }
+    }
+
+    /**
+     * Enables the backup service for user {@code userId}. If this is the system user, it deletes
+     * the suppress file. If this is a non-system user, it creates the user's activate file. Note,
+     * deleting the suppress file does not automatically enable backup for non-system users, they
+     * need their own activate file in order to participate in the service.
+     */
+    @GuardedBy("mStateLock")
+    private void activateBackupForUserLocked(int userId) throws IOException {
+        if (userId == UserHandle.USER_SYSTEM) {
+            deleteFile(getSuppressFileForSystemUser());
+        } else {
+            createFile(getActivatedFileForNonSystemUser(userId));
         }
     }
 
@@ -148,24 +193,31 @@
     // admin (device owner or profile owner).
     private boolean isUserReadyForBackup(int userId) {
         return mService != null && mService.getServiceUsers().get(userId) != null
-                && !isBackupSuppressedForUser(userId);
+                && isBackupActivatedForUser(userId);
     }
 
-    private boolean isBackupSuppressedForUser(int userId) {
-        // If backup is disabled for system user, it's disabled for all other users on device.
-        if (getSuppressFileForUser(UserHandle.USER_SYSTEM).exists()) {
-            return true;
+    /**
+     * Backup is activated for the system user if the suppress file does not exist. Backup is
+     * activated for non-system users if the suppress file does not exist AND the user's activated
+     * file exists.
+     */
+    private boolean isBackupActivatedForUser(int userId) {
+        if (getSuppressFileForSystemUser().exists()) {
+            return false;
         }
-        if (userId != UserHandle.USER_SYSTEM) {
-            return getSuppressFileForUser(userId).exists();
-        }
-        return false;
+
+        return userId == UserHandle.USER_SYSTEM
+                || getActivatedFileForNonSystemUser(userId).exists();
     }
 
     protected Context getContext() {
         return mContext;
     }
 
+    protected UserManager getUserManager() {
+        return mUserManager;
+    }
+
     protected BackupManagerService createBackupManagerService() {
         return new BackupManagerService(mContext, this, mHandlerThread);
     }
@@ -198,23 +250,17 @@
 
     /**
      * Called from {@link BackupManagerService.Lifecycle} when a user {@code userId} is unlocked.
-     * Starts the backup service for this user if it's the system user or if the service supports
-     * multi-user. Offloads work onto the handler thread {@link #mHandlerThread} to keep unlock time
-     * low.
+     * Starts the backup service for this user if backup is active for this user. Offloads work onto
+     * the handler thread {@link #mHandlerThread} to keep unlock time low.
      */
     void unlockUser(int userId) {
-        if (userId != UserHandle.USER_SYSTEM && !isMultiUserEnabled()) {
-            Slog.i(TAG, "Multi-user disabled, cannot start service for user: " + userId);
-            return;
-        }
-
         postToHandler(() -> startServiceForUser(userId));
     }
 
     private void startServiceForUser(int userId) {
         // We know that the user is unlocked here because it is called from setBackupServiceActive
         // and unlockUser which have these guarantees. So we can check if the file exists.
-        if (mService != null && !isBackupSuppressedForUser(userId)) {
+        if (mService != null && isBackupActivatedForUser(userId)) {
             Slog.i(TAG, "Starting service for user: " + userId);
             mService.startServiceForUser(userId);
         }
@@ -225,11 +271,6 @@
      * Offloads work onto the handler thread {@link #mHandlerThread} to keep stopping time low.
      */
     void stopUser(int userId) {
-        if (userId != UserHandle.USER_SYSTEM && !isMultiUserEnabled()) {
-            Slog.i(TAG, "Multi-user disabled, cannot stop service for user: " + userId);
-            return;
-        }
-
         postToHandler(
                 () -> {
                     if (mService != null) {
@@ -240,45 +281,63 @@
     }
 
     /**
-     * Only privileged callers should be changing the backup state. This method only acts on {@link
-     * UserHandle#USER_SYSTEM} and is a no-op if passed non-system users. Deactivating backup in the
-     * system user also deactivates backup in all users.
-     *
-     * This call will only work if the calling {@code userID} is unlocked.
+     * The system user and managed profiles can only be acted on by callers in the system or root
+     * processes. Other users can be acted on by callers who have both android.permission.BACKUP and
+     * android.permission.INTERACT_ACROSS_USERS_FULL permissions.
+     */
+    private void enforcePermissionsOnUser(int userId) throws SecurityException {
+        boolean isRestrictedUser =
+                userId == UserHandle.USER_SYSTEM
+                        || getUserManager().getUserInfo(userId).isManagedProfile();
+
+        if (isRestrictedUser) {
+            int caller = binderGetCallingUid();
+            if (caller != Process.SYSTEM_UID && caller != Process.ROOT_UID) {
+                throw new SecurityException("No permission to configure backup activity");
+            }
+        } else {
+            mContext.enforceCallingOrSelfPermission(
+                    Manifest.permission.BACKUP, "No permission to configure backup activity");
+            mContext.enforceCallingOrSelfPermission(
+                    Manifest.permission.INTERACT_ACROSS_USERS_FULL,
+                    "No permission to configure backup activity");
+        }
+    }
+
+    /**
+     * Only privileged callers should be changing the backup state. Deactivating backup in the
+     * system user also deactivates backup in all users. We are not guaranteed that {@code userId}
+     * is unlocked at this point yet, so handle both cases.
      */
     public void setBackupServiceActive(int userId, boolean makeActive) {
-        int caller = binderGetCallingUid();
-        if (caller != Process.SYSTEM_UID && caller != Process.ROOT_UID) {
-            throw new SecurityException("No permission to configure backup activity");
-        }
+        enforcePermissionsOnUser(userId);
 
         if (mGlobalDisable) {
             Slog.i(TAG, "Backup service not supported");
             return;
         }
 
-        if (userId != UserHandle.USER_SYSTEM) {
-            Slog.i(TAG, "Cannot set backup service activity for non-system user: " + userId);
-            return;
-        }
-
-        if (makeActive == isBackupServiceActive(userId)) {
-            Slog.i(TAG, "No change in backup service activity");
-            return;
-        }
-
         synchronized (mStateLock) {
             Slog.i(TAG, "Making backup " + (makeActive ? "" : "in") + "active");
             if (makeActive) {
                 if (mService == null) {
                     mService = createBackupManagerService();
                 }
-                deleteBackupSuppressFileForUser(userId);
-                startServiceForUser(userId);
+                try {
+                    activateBackupForUserLocked(userId);
+                } catch (IOException e) {
+                    Slog.e(TAG, "Unable to persist backup service activity");
+                }
+
+                // If the user is unlocked, we can start the backup service for it. Otherwise we
+                // will start the service when the user is unlocked as part of its unlock callback.
+                if (getUserManager().isUserUnlocked(userId)) {
+                    startServiceForUser(userId);
+                }
             } else {
                 try {
                     //TODO(b/121198006): what if this throws an exception?
-                    createBackupSuppressFileForUser(userId);
+                    deactivateBackupForUserLocked(userId);
                 } catch (IOException e) {
                     Slog.e(TAG, "Unable to persist backup service inactivity");
                 }
diff --git a/services/contentcapture/java/com/android/server/contentcapture/ContentCaptureManagerService.java b/services/contentcapture/java/com/android/server/contentcapture/ContentCaptureManagerService.java
index e4bbcd6..844096d 100644
--- a/services/contentcapture/java/com/android/server/contentcapture/ContentCaptureManagerService.java
+++ b/services/contentcapture/java/com/android/server/contentcapture/ContentCaptureManagerService.java
@@ -35,6 +35,7 @@
 import android.util.LocalLog;
 import android.util.Slog;
 import android.view.contentcapture.IContentCaptureManager;
+import android.view.contentcapture.UserDataRemovalRequest;
 
 import com.android.internal.annotations.GuardedBy;
 import com.android.internal.os.IResultReceiver;
@@ -224,6 +225,15 @@
         }
 
         @Override
+        public void removeUserData(@UserIdInt int userId, @NonNull UserDataRemovalRequest request) {
+            Preconditions.checkNotNull(request);
+            synchronized (mLock) {
+                final ContentCapturePerUserService service = getServiceForUserLocked(userId);
+                service.removeUserDataLocked(request);
+            }
+        }
+
+        @Override
         public void dump(FileDescriptor fd, PrintWriter pw, String[] args) {
             if (!DumpUtils.checkDumpPermission(getContext(), TAG, pw)) return;
 
diff --git a/services/contentcapture/java/com/android/server/contentcapture/ContentCapturePerUserService.java b/services/contentcapture/java/com/android/server/contentcapture/ContentCapturePerUserService.java
index 8d2c79b..bc0e19a 100644
--- a/services/contentcapture/java/com/android/server/contentcapture/ContentCapturePerUserService.java
+++ b/services/contentcapture/java/com/android/server/contentcapture/ContentCapturePerUserService.java
@@ -28,6 +28,7 @@
 import android.Manifest;
 import android.annotation.NonNull;
 import android.annotation.UserIdInt;
+import android.app.ActivityManagerInternal;
 import android.app.AppGlobals;
 import android.app.assist.AssistContent;
 import android.app.assist.AssistStructure;
@@ -35,18 +36,22 @@
 import android.content.pm.PackageManager;
 import android.content.pm.PackageManager.NameNotFoundException;
 import android.content.pm.ServiceInfo;
+import android.os.Binder;
 import android.os.Bundle;
 import android.os.IBinder;
 import android.os.RemoteException;
+import android.os.UserHandle;
 import android.service.contentcapture.ContentCaptureService;
 import android.service.contentcapture.IContentCaptureServiceCallback;
 import android.service.contentcapture.SnapshotData;
 import android.util.ArrayMap;
 import android.util.Log;
 import android.util.Slog;
+import android.view.contentcapture.UserDataRemovalRequest;
 
 import com.android.internal.annotations.GuardedBy;
 import com.android.internal.os.IResultReceiver;
+import com.android.server.LocalServices;
 import com.android.server.contentcapture.RemoteContentCaptureService.ContentCaptureServiceCallbacks;
 import com.android.server.infra.AbstractPerUserSystemService;
 
@@ -249,6 +254,39 @@
     }
 
     @GuardedBy("mLock")
+    public void removeUserDataLocked(@NonNull UserDataRemovalRequest request) {
+        if (!isEnabledLocked()) {
+            return;
+        }
+        assertCallerLocked(request.getPackageName());
+        mRemoteService.onUserDataRemovalRequest(request);
+    }
+
+    /**
+     * Asserts the component is owned by the caller.
+     */
+    @GuardedBy("mLock")
+    private void assertCallerLocked(@NonNull String packageName) {
+        final PackageManager pm = getContext().getPackageManager();
+        final int callingUid = Binder.getCallingUid();
+        final int packageUid;
+        try {
+            packageUid = pm.getPackageUidAsUser(packageName, UserHandle.getCallingUserId());
+        } catch (NameNotFoundException e) {
+            throw new SecurityException("Could not verify UID for " + packageName);
+        }
+        if (callingUid != packageUid && !LocalServices.getService(ActivityManagerInternal.class)
+                .hasRunningActivity(callingUid, packageName)) {
+            final String[] packages = pm.getPackagesForUid(callingUid);
+            final String callingPackage = packages != null ? packages[0] : "uid-" + callingUid;
+            Slog.w(TAG, "App (package=" + callingPackage + ", UID=" + callingUid
+                    + ") passed package (" + packageName + ") owned by UID " + packageUid);
+
+            throw new SecurityException("Invalid package: " + packageName);
+        }
+    }
+
+    @GuardedBy("mLock")
     public boolean sendActivityAssistDataLocked(@NonNull IBinder activityToken,
             @NonNull Bundle data) {
         final String id = getSessionId(activityToken);
diff --git a/services/contentcapture/java/com/android/server/contentcapture/RemoteContentCaptureService.java b/services/contentcapture/java/com/android/server/contentcapture/RemoteContentCaptureService.java
index 12742ca..54eea5d 100644
--- a/services/contentcapture/java/com/android/server/contentcapture/RemoteContentCaptureService.java
+++ b/services/contentcapture/java/com/android/server/contentcapture/RemoteContentCaptureService.java
@@ -26,6 +26,7 @@
 import android.text.format.DateUtils;
 import android.util.Slog;
 import android.view.contentcapture.ContentCaptureContext;
+import android.view.contentcapture.UserDataRemovalRequest;
 
 import com.android.internal.infra.AbstractMultiplePendingRequestsRemoteService;
 import com.android.internal.os.IResultReceiver;
@@ -108,6 +109,13 @@
         scheduleAsyncRequest((s) -> s.onActivitySnapshot(sessionId, snapshotData));
     }
 
+    /**
+     * Called by {@link ContentCaptureServerSession} to request removal of user data.
+     */
+    public void onUserDataRemovalRequest(@NonNull UserDataRemovalRequest request) {
+        scheduleAsyncRequest((s) -> s.onUserDataRemovalRequest(request));
+    }
+
     public interface ContentCaptureServiceCallbacks
             extends VultureCallback<RemoteContentCaptureService> {
         // NOTE: so far we don't need to notify the callback implementation
diff --git a/services/core/java/com/android/server/AlarmManagerService.java b/services/core/java/com/android/server/AlarmManagerService.java
index fcd136c..e3dcb7d 100644
--- a/services/core/java/com/android/server/AlarmManagerService.java
+++ b/services/core/java/com/android/server/AlarmManagerService.java
@@ -77,6 +77,7 @@
 import android.util.ArraySet;
 import android.util.KeyValueListParser;
 import android.util.Log;
+import android.util.LongArrayQueue;
 import android.util.NtpTrustedTime;
 import android.util.Pair;
 import android.util.Slog;
@@ -91,6 +92,7 @@
 import com.android.internal.annotations.VisibleForTesting;
 import com.android.internal.util.ArrayUtils;
 import com.android.internal.util.DumpUtils;
+import com.android.internal.util.IndentingPrintWriter;
 import com.android.internal.util.LocalLog;
 import com.android.internal.util.StatLogger;
 import com.android.server.AppStateTracker.Listener;
@@ -145,6 +147,7 @@
     static final String TIMEZONE_PROPERTY = "persist.sys.timezone";
 
     static final int TICK_HISTORY_DEPTH = 10;
+    static final long MILLIS_IN_DAY = 24 * 60 * 60 * 1000;
 
     // Indices into the APP_STANDBY_MIN_DELAYS and KEYS_APP_STANDBY_DELAY arrays
     static final int ACTIVE_INDEX = 0;
@@ -195,6 +198,7 @@
     ArrayList<Alarm> mPendingNonWakeupAlarms = new ArrayList<>();
     ArrayList<InFlight> mInFlight = new ArrayList<>();
     AlarmHandler mHandler;
+    AppWakeupHistory mAppWakeupHistory;
     ClockReceiver mClockReceiver;
     final DeliveryTracker mDeliveryTracker = new DeliveryTracker();
     Intent mTimeTickIntent;
@@ -277,7 +281,91 @@
 
     private AppStateTracker mAppStateTracker;
     private boolean mAppStandbyParole;
-    private ArrayMap<Pair<String, Integer>, Long> mLastAlarmDeliveredForPackage = new ArrayMap<>();
+
+    /**
+     * A rolling window history of previous times when an alarm was sent to a package.
+     */
+    private static class AppWakeupHistory {
+        private ArrayMap<Pair<String, Integer>, LongArrayQueue> mPackageHistory =
+                new ArrayMap<>();
+        private long mWindowSize;
+
+        AppWakeupHistory(long windowSize) {
+            mWindowSize = windowSize;
+        }
+
+        void recordAlarmForPackage(String packageName, int userId, long nowElapsed) {
+            final Pair<String, Integer> packageUser = Pair.create(packageName, userId);
+            LongArrayQueue history = mPackageHistory.get(packageUser);
+            if (history == null) {
+                history = new LongArrayQueue();
+                mPackageHistory.put(packageUser, history);
+            }
+            if (history.size() == 0 || history.peekLast() < nowElapsed) {
+                history.addLast(nowElapsed);
+            }
+            snapToWindow(history);
+        }
+
+        void removeForUser(int userId) {
+            for (int i = mPackageHistory.size() - 1; i >= 0; i--) {
+                final Pair<String, Integer> packageUserKey = mPackageHistory.keyAt(i);
+                if (packageUserKey.second == userId) {
+                    mPackageHistory.removeAt(i);
+                }
+            }
+        }
+
+        void removeForPackage(String packageName, int userId) {
+            final Pair<String, Integer> packageUser = Pair.create(packageName, userId);
+            mPackageHistory.remove(packageUser);
+        }
+
+        private void snapToWindow(LongArrayQueue history) {
+            while (history.peekFirst() + mWindowSize < history.peekLast()) {
+                history.removeFirst();
+            }
+        }
+
+        int getTotalWakeupsInWindow(String packageName, int userId) {
+            final LongArrayQueue history = mPackageHistory.get(Pair.create(packageName, userId));
+            return (history == null) ? 0 : history.size();
+        }
+
+        long getLastWakeupForPackage(String packageName, int userId, int positionFromEnd) {
+            final LongArrayQueue history = mPackageHistory.get(Pair.create(packageName, userId));
+            if (history == null) {
+                return 0;
+            }
+            final int i = history.size() - positionFromEnd;
+            return (i < 0) ? 0 : history.get(i);
+        }
+
+        void dump(PrintWriter pw, String prefix, long nowElapsed) {
+            dump(new IndentingPrintWriter(pw, "  ").setIndent(prefix), nowElapsed);
+        }
+
+        void dump(IndentingPrintWriter pw, long nowElapsed) {
+            pw.println("App Alarm history:");
+            pw.increaseIndent();
+            for (int i = 0; i < mPackageHistory.size(); i++) {
+                final Pair<String, Integer> packageUser = mPackageHistory.keyAt(i);
+                final LongArrayQueue timestamps = mPackageHistory.valueAt(i);
+                pw.print(packageUser.first);
+                pw.print(", u");
+                pw.print(packageUser.second);
+                pw.print(": ");
+                // limit dumping to a max of 100 values
+                final int lastIdx = Math.max(0, timestamps.size() - 100);
+                for (int j = timestamps.size() - 1; j >= lastIdx; j--) {
+                    TimeUtils.formatDuration(timestamps.get(j), nowElapsed, pw);
+                    pw.print(", ");
+                }
+                pw.println();
+            }
+            pw.decreaseIndent();
+        }
+    }
 
     /**
      * All times are in milliseconds. These constants are kept synchronized with the system
@@ -302,6 +390,17 @@
                 = "allow_while_idle_whitelist_duration";
         @VisibleForTesting
         static final String KEY_LISTENER_TIMEOUT = "listener_timeout";
+        @VisibleForTesting
+        static final String KEY_APP_STANDBY_QUOTAS_ENABLED = "app_standby_quotas_enabled";
+        private static final String KEY_APP_STANDBY_WINDOW = "app_standby_window";
+        @VisibleForTesting
+        final String[] KEYS_APP_STANDBY_QUOTAS = {
+                "standby_active_quota",
+                "standby_working_quota",
+                "standby_frequent_quota",
+                "standby_rare_quota",
+                "standby_never_quota",
+        };
 
         // Keys for specifying throttling delay based on app standby bucketing
         private final String[] KEYS_APP_STANDBY_DELAY = {
@@ -319,6 +418,18 @@
         private static final long DEFAULT_ALLOW_WHILE_IDLE_LONG_TIME = 9*60*1000;
         private static final long DEFAULT_ALLOW_WHILE_IDLE_WHITELIST_DURATION = 10*1000;
         private static final long DEFAULT_LISTENER_TIMEOUT = 5 * 1000;
+        private static final boolean DEFAULT_APP_STANDBY_QUOTAS_ENABLED = true;
+        private static final long DEFAULT_APP_STANDBY_WINDOW = 60 * 60 * 1000;  // 1 hr
+        /**
+         * Max number of times an app can receive alarms in {@link #APP_STANDBY_WINDOW}
+         */
+        private final int[] DEFAULT_APP_STANDBY_QUOTAS = {
+                720,    // Active
+                10,     // Working
+                2,      // Frequent
+                1,      // Rare
+                0       // Never
+        };
         private final long[] DEFAULT_APP_STANDBY_DELAYS = {
                 0,                       // Active
                 6 * 60_000,              // Working
@@ -348,8 +459,11 @@
 
         // Direct alarm listener callback timeout
         public long LISTENER_TIMEOUT = DEFAULT_LISTENER_TIMEOUT;
+        public boolean APP_STANDBY_QUOTAS_ENABLED = DEFAULT_APP_STANDBY_QUOTAS_ENABLED;
 
+        public long APP_STANDBY_WINDOW = DEFAULT_APP_STANDBY_WINDOW;
         public long[] APP_STANDBY_MIN_DELAYS = new long[DEFAULT_APP_STANDBY_DELAYS.length];
+        public int[] APP_STANDBY_QUOTAS = new int[DEFAULT_APP_STANDBY_QUOTAS.length];
 
         private ContentResolver mResolver;
         private final KeyValueListParser mParser = new KeyValueListParser(',');
@@ -409,48 +523,90 @@
                         DEFAULT_APP_STANDBY_DELAYS[ACTIVE_INDEX]);
                 for (int i = WORKING_INDEX; i < KEYS_APP_STANDBY_DELAY.length; i++) {
                     APP_STANDBY_MIN_DELAYS[i] = mParser.getDurationMillis(KEYS_APP_STANDBY_DELAY[i],
-                            Math.max(APP_STANDBY_MIN_DELAYS[i-1], DEFAULT_APP_STANDBY_DELAYS[i]));
+                            Math.max(APP_STANDBY_MIN_DELAYS[i - 1], DEFAULT_APP_STANDBY_DELAYS[i]));
+                }
+
+                APP_STANDBY_QUOTAS_ENABLED = mParser.getBoolean(KEY_APP_STANDBY_QUOTAS_ENABLED,
+                        DEFAULT_APP_STANDBY_QUOTAS_ENABLED);
+
+                APP_STANDBY_WINDOW = mParser.getLong(KEY_APP_STANDBY_WINDOW,
+                        DEFAULT_APP_STANDBY_WINDOW);
+                if (APP_STANDBY_WINDOW > DEFAULT_APP_STANDBY_WINDOW) {
+                    Slog.w(TAG, "Cannot exceed the app_standby_window size of "
+                            + DEFAULT_APP_STANDBY_WINDOW);
+                    APP_STANDBY_WINDOW = DEFAULT_APP_STANDBY_WINDOW;
+                } else if (APP_STANDBY_WINDOW < DEFAULT_APP_STANDBY_WINDOW) {
+                    // Not recommended outside of testing.
+                    Slog.w(TAG, "Using a non-default app_standby_window of " + APP_STANDBY_WINDOW);
+                }
+
+                APP_STANDBY_QUOTAS[ACTIVE_INDEX] = mParser.getInt(
+                        KEYS_APP_STANDBY_QUOTAS[ACTIVE_INDEX],
+                        DEFAULT_APP_STANDBY_QUOTAS[ACTIVE_INDEX]);
+                for (int i = WORKING_INDEX; i < KEYS_APP_STANDBY_QUOTAS.length; i++) {
+                    APP_STANDBY_QUOTAS[i] = mParser.getInt(KEYS_APP_STANDBY_QUOTAS[i],
+                            Math.min(APP_STANDBY_QUOTAS[i - 1], DEFAULT_APP_STANDBY_QUOTAS[i]));
                 }
                 updateAllowWhileIdleWhitelistDurationLocked();
             }
         }
 
-        void dump(PrintWriter pw) {
-            pw.println("  Settings:");
+        void dump(PrintWriter pw, String prefix) {
+            dump(new IndentingPrintWriter(pw, "  ").setIndent(prefix));
+        }
 
-            pw.print("    "); pw.print(KEY_MIN_FUTURITY); pw.print("=");
+        void dump(IndentingPrintWriter pw) {
+            pw.println("Settings:");
+
+            pw.increaseIndent();
+
+            pw.print(KEY_MIN_FUTURITY); pw.print("=");
             TimeUtils.formatDuration(MIN_FUTURITY, pw);
             pw.println();
 
-            pw.print("    "); pw.print(KEY_MIN_INTERVAL); pw.print("=");
+            pw.print(KEY_MIN_INTERVAL); pw.print("=");
             TimeUtils.formatDuration(MIN_INTERVAL, pw);
             pw.println();
 
-            pw.print("    "); pw.print(KEY_MAX_INTERVAL); pw.print("=");
+            pw.print(KEY_MAX_INTERVAL); pw.print("=");
             TimeUtils.formatDuration(MAX_INTERVAL, pw);
             pw.println();
 
-            pw.print("    "); pw.print(KEY_LISTENER_TIMEOUT); pw.print("=");
+            pw.print(KEY_LISTENER_TIMEOUT); pw.print("=");
             TimeUtils.formatDuration(LISTENER_TIMEOUT, pw);
             pw.println();
 
-            pw.print("    "); pw.print(KEY_ALLOW_WHILE_IDLE_SHORT_TIME); pw.print("=");
+            pw.print(KEY_ALLOW_WHILE_IDLE_SHORT_TIME); pw.print("=");
             TimeUtils.formatDuration(ALLOW_WHILE_IDLE_SHORT_TIME, pw);
             pw.println();
 
-            pw.print("    "); pw.print(KEY_ALLOW_WHILE_IDLE_LONG_TIME); pw.print("=");
+            pw.print(KEY_ALLOW_WHILE_IDLE_LONG_TIME); pw.print("=");
             TimeUtils.formatDuration(ALLOW_WHILE_IDLE_LONG_TIME, pw);
             pw.println();
 
-            pw.print("    "); pw.print(KEY_ALLOW_WHILE_IDLE_WHITELIST_DURATION); pw.print("=");
+            pw.print(KEY_ALLOW_WHILE_IDLE_WHITELIST_DURATION); pw.print("=");
             TimeUtils.formatDuration(ALLOW_WHILE_IDLE_WHITELIST_DURATION, pw);
             pw.println();
 
             for (int i = 0; i < KEYS_APP_STANDBY_DELAY.length; i++) {
-                pw.print("    "); pw.print(KEYS_APP_STANDBY_DELAY[i]); pw.print("=");
+                pw.print(KEYS_APP_STANDBY_DELAY[i]); pw.print("=");
                 TimeUtils.formatDuration(APP_STANDBY_MIN_DELAYS[i], pw);
                 pw.println();
             }
+
+            pw.print(KEY_APP_STANDBY_QUOTAS_ENABLED); pw.print("=");
+            pw.println(APP_STANDBY_QUOTAS_ENABLED);
+
+            pw.print(KEY_APP_STANDBY_WINDOW); pw.print("=");
+            TimeUtils.formatDuration(APP_STANDBY_WINDOW, pw);
+            pw.println();
+
+            for (int i = 0; i < KEYS_APP_STANDBY_QUOTAS.length; i++) {
+                pw.print(KEYS_APP_STANDBY_QUOTAS[i]); pw.print("=");
+                pw.println(APP_STANDBY_QUOTAS[i]);
+            }
+
+            pw.decreaseIndent();
         }
 
         void dumpProto(ProtoOutputStream proto, long fieldId) {
@@ -925,7 +1081,7 @@
                 if (targetPackages != null && !targetPackages.contains(packageUser)) {
                     continue;
                 }
-                if (adjustDeliveryTimeBasedOnStandbyBucketLocked(alarm)) {
+                if (adjustDeliveryTimeBasedOnBucketLocked(alarm)) {
                     batch.remove(alarm);
                     rescheduledAlarms.add(alarm);
                 }
@@ -1300,6 +1456,7 @@
         synchronized (mLock) {
             mHandler = new AlarmHandler();
             mConstants = new Constants(mHandler);
+            mAppWakeupHistory = new AppWakeupHistory(Constants.DEFAULT_APP_STANDBY_WINDOW);
 
             mNextWakeup = mNextNonWakeup = 0;
 
@@ -1583,6 +1740,27 @@
     }
 
     /**
+     * Returns the maximum alarms that an app in the specified bucket can receive in a rolling time
+     * window given by {@link Constants#APP_STANDBY_WINDOW}
+     */
+    @VisibleForTesting
+    int getQuotaForBucketLocked(int bucket) {
+        final int index;
+        if (bucket <= UsageStatsManager.STANDBY_BUCKET_ACTIVE) {
+            index = ACTIVE_INDEX;
+        } else if (bucket <= UsageStatsManager.STANDBY_BUCKET_WORKING_SET) {
+            index = WORKING_INDEX;
+        } else if (bucket <= UsageStatsManager.STANDBY_BUCKET_FREQUENT) {
+            index = FREQUENT_INDEX;
+        } else if (bucket < UsageStatsManager.STANDBY_BUCKET_NEVER) {
+            index = RARE_INDEX;
+        } else {
+            index = NEVER_INDEX;
+        }
+        return mConstants.APP_STANDBY_QUOTAS[index];
+    }
+
+    /**
      * Return the minimum time that should elapse before an app in the specified bucket
      * can receive alarms again
      */
@@ -1608,7 +1786,7 @@
      * @param alarm The alarm to adjust
      * @return true if the alarm delivery time was updated.
      */
-    private boolean adjustDeliveryTimeBasedOnStandbyBucketLocked(Alarm alarm) {
+    private boolean adjustDeliveryTimeBasedOnBucketLocked(Alarm alarm) {
         if (isExemptFromAppStandby(alarm)) {
             return false;
         }
@@ -1629,18 +1807,49 @@
         final int standbyBucket = mUsageStatsManagerInternal.getAppStandbyBucket(
                 sourcePackage, sourceUserId, mInjector.getElapsedRealtime());
 
-        final Pair<String, Integer> packageUser = Pair.create(sourcePackage, sourceUserId);
-        final long lastElapsed = mLastAlarmDeliveredForPackage.getOrDefault(packageUser, 0L);
-        if (lastElapsed > 0) {
-            final long minElapsed = lastElapsed + getMinDelayForBucketLocked(standbyBucket);
-            if (alarm.expectedWhenElapsed < minElapsed) {
-                alarm.whenElapsed = alarm.maxWhenElapsed = minElapsed;
-            } else {
-                // app is now eligible to run alarms at the originally requested window.
+        if (mConstants.APP_STANDBY_QUOTAS_ENABLED) {
+            // Quota deferring implementation:
+            final int wakeupsInWindow = mAppWakeupHistory.getTotalWakeupsInWindow(sourcePackage,
+                    sourceUserId);
+            final int quotaForBucket = getQuotaForBucketLocked(standbyBucket);
+            boolean deferred = false;
+            if (wakeupsInWindow >= quotaForBucket) {
+                final long minElapsed;
+                if (quotaForBucket <= 0) {
+                    // Just keep deferring for a day till the quota changes
+                    minElapsed = mInjector.getElapsedRealtime() + MILLIS_IN_DAY;
+                } else {
+                    // Suppose the quota for window was q, and the qth last delivery time for this
+                    // package was t(q) then the next delivery must be after t(q) + <window_size>
+                    final long t = mAppWakeupHistory.getLastWakeupForPackage(sourcePackage,
+                            sourceUserId, quotaForBucket);
+                    minElapsed = t + 1 + mConstants.APP_STANDBY_WINDOW;
+                }
+                if (alarm.expectedWhenElapsed < minElapsed) {
+                    alarm.whenElapsed = alarm.maxWhenElapsed = minElapsed;
+                    deferred = true;
+                }
+            }
+            if (!deferred) {
                 // Restore original requirements in case they were changed earlier.
                 alarm.whenElapsed = alarm.expectedWhenElapsed;
                 alarm.maxWhenElapsed = alarm.expectedMaxWhenElapsed;
             }
+        } else {
+            // Minimum delay deferring implementation:
+            final long lastElapsed = mAppWakeupHistory.getLastWakeupForPackage(sourcePackage,
+                    sourceUserId, 1);
+            if (lastElapsed > 0) {
+                final long minElapsed = lastElapsed + getMinDelayForBucketLocked(standbyBucket);
+                if (alarm.expectedWhenElapsed < minElapsed) {
+                    alarm.whenElapsed = alarm.maxWhenElapsed = minElapsed;
+                } else {
+                    // app is now eligible to run alarms at the originally requested window.
+                    // Restore original requirements in case they were changed earlier.
+                    alarm.whenElapsed = alarm.expectedWhenElapsed;
+                    alarm.maxWhenElapsed = alarm.expectedMaxWhenElapsed;
+                }
+            }
         }
         return (oldWhenElapsed != alarm.whenElapsed || oldMaxWhenElapsed != alarm.maxWhenElapsed);
     }
@@ -1696,7 +1905,7 @@
                 mAllowWhileIdleDispatches.add(ent);
             }
         }
-        adjustDeliveryTimeBasedOnStandbyBucketLocked(a);
+        adjustDeliveryTimeBasedOnBucketLocked(a);
         insertAndBatchAlarmLocked(a);
 
         if (a.alarmClock != null) {
@@ -1915,7 +2124,7 @@
     void dumpImpl(PrintWriter pw) {
         synchronized (mLock) {
             pw.println("Current Alarm Manager state:");
-            mConstants.dump(pw);
+            mConstants.dump(pw, "  ");
             pw.println();
 
             if (mAppStateTracker != null) {
@@ -2065,14 +2274,7 @@
                 pw.println("    none");
             }
 
-            pw.println("  mLastAlarmDeliveredForPackage:");
-            for (int i = 0; i < mLastAlarmDeliveredForPackage.size(); i++) {
-                Pair<String, Integer> packageUser = mLastAlarmDeliveredForPackage.keyAt(i);
-                pw.print("    Package " + packageUser.first + ", User " + packageUser.second + ":");
-                TimeUtils.formatDuration(mLastAlarmDeliveredForPackage.valueAt(i), nowELAPSED, pw);
-                pw.println();
-            }
-            pw.println();
+            mAppWakeupHistory.dump(pw, "  ", nowELAPSED);
 
             if (mPendingIdleUntil != null || mPendingWhileIdleAlarms.size() > 0) {
                 pw.println();
@@ -3862,6 +4064,7 @@
             obtainMessage(REMOVE_FOR_STOPPED, uid, 0).sendToTarget();
         }
 
+        @Override
         public void handleMessage(Message msg) {
             switch (msg.what) {
                 case ALARM_EVENT: {
@@ -4030,64 +4233,57 @@
         public void onReceive(Context context, Intent intent) {
             final int uid = intent.getIntExtra(Intent.EXTRA_UID, -1);
             synchronized (mLock) {
-                String action = intent.getAction();
                 String pkgList[] = null;
-                if (Intent.ACTION_QUERY_PACKAGE_RESTART.equals(action)) {
-                    pkgList = intent.getStringArrayExtra(Intent.EXTRA_PACKAGES);
-                    for (String packageName : pkgList) {
-                        if (lookForPackageLocked(packageName)) {
-                            setResultCode(Activity.RESULT_OK);
-                            return;
-                        }
-                    }
-                    return;
-                } else if (Intent.ACTION_EXTERNAL_APPLICATIONS_UNAVAILABLE.equals(action)) {
-                    pkgList = intent.getStringArrayExtra(Intent.EXTRA_CHANGED_PACKAGE_LIST);
-                } else if (Intent.ACTION_USER_STOPPED.equals(action)) {
-                    int userHandle = intent.getIntExtra(Intent.EXTRA_USER_HANDLE, -1);
-                    if (userHandle >= 0) {
-                        removeUserLocked(userHandle);
-                        for (int i = mLastAlarmDeliveredForPackage.size() - 1; i >= 0; i--) {
-                            final Pair<String, Integer> packageUser =
-                                    mLastAlarmDeliveredForPackage.keyAt(i);
-                            if (packageUser.second == userHandle) {
-                                mLastAlarmDeliveredForPackage.removeAt(i);
+                switch (intent.getAction()) {
+                    case Intent.ACTION_QUERY_PACKAGE_RESTART:
+                        pkgList = intent.getStringArrayExtra(Intent.EXTRA_PACKAGES);
+                        for (String packageName : pkgList) {
+                            if (lookForPackageLocked(packageName)) {
+                                setResultCode(Activity.RESULT_OK);
+                                return;
                             }
                         }
-                    }
-                } else if (Intent.ACTION_UID_REMOVED.equals(action)) {
-                    if (uid >= 0) {
-                        mLastAllowWhileIdleDispatch.delete(uid);
-                        mUseAllowWhileIdleShortTime.delete(uid);
-                    }
-                } else {
-                    if (Intent.ACTION_PACKAGE_REMOVED.equals(action)
-                            && intent.getBooleanExtra(Intent.EXTRA_REPLACING, false)) {
-                        // This package is being updated; don't kill its alarms.
                         return;
-                    }
-                    Uri data = intent.getData();
-                    if (data != null) {
-                        String pkg = data.getSchemeSpecificPart();
-                        if (pkg != null) {
-                            pkgList = new String[]{pkg};
+                    case Intent.ACTION_EXTERNAL_APPLICATIONS_UNAVAILABLE:
+                        pkgList = intent.getStringArrayExtra(Intent.EXTRA_CHANGED_PACKAGE_LIST);
+                        break;
+                    case Intent.ACTION_USER_STOPPED:
+                        final int userHandle = intent.getIntExtra(Intent.EXTRA_USER_HANDLE, -1);
+                        if (userHandle >= 0) {
+                            removeUserLocked(userHandle);
+                            mAppWakeupHistory.removeForUser(userHandle);
                         }
-                    }
+                        return;
+                    case Intent.ACTION_UID_REMOVED:
+                        if (uid >= 0) {
+                            mLastAllowWhileIdleDispatch.delete(uid);
+                            mUseAllowWhileIdleShortTime.delete(uid);
+                        }
+                        return;
+                    case Intent.ACTION_PACKAGE_REMOVED:
+                        if (intent.getBooleanExtra(Intent.EXTRA_REPLACING, false)) {
+                            // This package is being updated; don't kill its alarms.
+                            return;
+                        }
+                        // Intentional fall-through.
+                    case Intent.ACTION_PACKAGE_RESTARTED:
+                        final Uri data = intent.getData();
+                        if (data != null) {
+                            final String pkg = data.getSchemeSpecificPart();
+                            if (pkg != null) {
+                                pkgList = new String[]{pkg};
+                            }
+                        }
+                        break;
                 }
                 if (pkgList != null && (pkgList.length > 0)) {
-                    for (int i = mLastAlarmDeliveredForPackage.size() - 1; i >= 0; i--) {
-                        Pair<String, Integer> packageUser = mLastAlarmDeliveredForPackage.keyAt(i);
-                        if (ArrayUtils.contains(pkgList, packageUser.first)
-                                && packageUser.second == UserHandle.getUserId(uid)) {
-                            mLastAlarmDeliveredForPackage.removeAt(i);
-                        }
-                    }
                     for (String pkg : pkgList) {
                         if (uid >= 0) {
-                            // package-removed case
+                            // package-removed and package-restarted case
+                            mAppWakeupHistory.removeForPackage(pkg, UserHandle.getUserId(uid));
                             removeLocked(uid);
                         } else {
-                            // external-applications-unavailable etc case
+                            // external-applications-unavailable case
                             removeLocked(pkg);
                         }
                         mPriorities.remove(pkg);
@@ -4131,7 +4327,8 @@
     /**
      * Tracking of app assignments to standby buckets
      */
-    final class AppStandbyTracker extends UsageStatsManagerInternal.AppIdleStateChangeListener {
+    private final class AppStandbyTracker extends
+            UsageStatsManagerInternal.AppIdleStateChangeListener {
         @Override
         public void onAppIdleStateChanged(final String packageName, final @UserIdInt int userId,
                 boolean idle, int bucket, int reason) {
@@ -4474,7 +4671,8 @@
             if (!isExemptFromAppStandby(alarm)) {
                 final Pair<String, Integer> packageUser = Pair.create(alarm.sourcePackage,
                         UserHandle.getUserId(alarm.creatorUid));
-                mLastAlarmDeliveredForPackage.put(packageUser, nowELAPSED);
+                mAppWakeupHistory.recordAlarmForPackage(alarm.sourcePackage,
+                        UserHandle.getUserId(alarm.creatorUid), nowELAPSED);
             }
 
             final BroadcastStats bs = inflight.mBroadcastStats;
diff --git a/services/core/java/com/android/server/BluetoothManagerService.java b/services/core/java/com/android/server/BluetoothManagerService.java
index a333381..c4bc52c 100644
--- a/services/core/java/com/android/server/BluetoothManagerService.java
+++ b/services/core/java/com/android/server/BluetoothManagerService.java
@@ -208,6 +208,8 @@
     private int mErrorRecoveryRetryCounter;
     private final int mSystemUiUid;
 
+    private boolean mIsHearingAidProfileSupported;
+
     // Save a ProfileServiceConnections object for each of the bound
     // bluetooth profile services
     private final Map<Integer, ProfileServiceConnections> mProfileServices = new HashMap<>();
@@ -391,13 +393,19 @@
         mCallbacks = new RemoteCallbackList<IBluetoothManagerCallback>();
         mStateChangeCallbacks = new RemoteCallbackList<IBluetoothStateChangeCallback>();
 
+        mIsHearingAidProfileSupported = context.getResources()
+                .getBoolean(com.android.internal.R.bool.config_hearing_aid_profile_supported);
+
         // TODO: We need a more generic way to initialize the persist keys of FeatureFlagUtils
-        boolean isHearingAidEnabled;
         String value = SystemProperties.get(FeatureFlagUtils.PERSIST_PREFIX + FeatureFlagUtils.HEARING_AID_SETTINGS);
         if (!TextUtils.isEmpty(value)) {
-            isHearingAidEnabled = Boolean.parseBoolean(value);
+            boolean isHearingAidEnabled = Boolean.parseBoolean(value);
             Log.v(TAG, "set feature flag HEARING_AID_SETTINGS to " + isHearingAidEnabled);
             FeatureFlagUtils.setEnabled(context, FeatureFlagUtils.HEARING_AID_SETTINGS, isHearingAidEnabled);
+            if (isHearingAidEnabled && !mIsHearingAidProfileSupported) {
+                // Overwrite to enable support by FeatureFlag
+                mIsHearingAidProfileSupported = true;
+            }
         }
 
         IntentFilter filter = new IntentFilter();
@@ -679,6 +687,11 @@
         return false;
     }
 
+    @Override
+    public boolean isHearingAidProfileSupported() {
+        return mIsHearingAidProfileSupported;
+    }
+
     // Monitor change of BLE scan only mode settings.
     private void registerForBleScanModeChange() {
         ContentObserver contentObserver = new ContentObserver(null) {
diff --git a/services/core/java/com/android/server/ConnectivityService.java b/services/core/java/com/android/server/ConnectivityService.java
index aa74600..14e2354 100644
--- a/services/core/java/com/android/server/ConnectivityService.java
+++ b/services/core/java/com/android/server/ConnectivityService.java
@@ -73,6 +73,7 @@
 import android.net.LinkProperties;
 import android.net.LinkProperties.CompareResult;
 import android.net.MatchAllNetworkSpecifier;
+import android.net.NattSocketKeepalive;
 import android.net.Network;
 import android.net.NetworkAgent;
 import android.net.NetworkCapabilities;
@@ -97,10 +98,10 @@
 import android.net.metrics.IpConnectivityLog;
 import android.net.metrics.NetworkEvent;
 import android.net.netlink.InetDiagMessage;
+import android.net.shared.NetdService;
 import android.net.shared.NetworkMonitorUtils;
 import android.net.shared.PrivateDnsConfig;
 import android.net.util.MultinetworkPolicyTracker;
-import android.net.shared.NetdService;
 import android.os.Binder;
 import android.os.Build;
 import android.os.Bundle;
@@ -237,6 +238,9 @@
     // connect anyway?" dialog after the user selects a network that doesn't validate.
     private static final int PROMPT_UNVALIDATED_DELAY_MS = 8 * 1000;
 
+    // How long to dismiss network notification.
+    private static final int TIMEOUT_NOTIFICATION_DELAY_MS = 20 * 1000;
+
     // Default to 30s linger time-out. Modifiable only for testing.
     private static final String LINGER_DELAY_PROPERTY = "persist.netmon.linger";
     private static final int DEFAULT_LINGER_DELAY_MS = 30_000;
@@ -473,6 +477,11 @@
     public static final int EVENT_PROVISIONING_NOTIFICATION = 43;
 
     /**
+     * This event can handle dismissing notification by given network id.
+     */
+    public static final int EVENT_TIMEOUT_NOTIFICATION = 44;
+
+    /**
      * Argument for {@link #EVENT_PROVISIONING_NOTIFICATION} to indicate that the notification
      * should be shown.
      */
@@ -506,7 +515,8 @@
 
     // A helper object to track the current default HTTP proxy. ConnectivityService needs to tell
     // the world when it changes.
-    private final ProxyTracker mProxyTracker;
+    @VisibleForTesting
+    protected final ProxyTracker mProxyTracker;
 
     final private SettingsObserver mSettingsObserver;
 
@@ -815,7 +825,7 @@
         mPolicyManagerInternal = checkNotNull(
                 LocalServices.getService(NetworkPolicyManagerInternal.class),
                 "missing NetworkPolicyManagerInternal");
-        mProxyTracker = new ProxyTracker(context, mHandler, EVENT_PROXY_HAS_CHANGED);
+        mProxyTracker = makeProxyTracker();
 
         mNetd = NetdService.getInstance();
         mKeyStore = KeyStore.getInstance();
@@ -981,6 +991,11 @@
                 deps);
     }
 
+    @VisibleForTesting
+    protected ProxyTracker makeProxyTracker() {
+        return new ProxyTracker(mContext, mHandler, EVENT_PROXY_HAS_CHANGED);
+    }
+
     private static NetworkCapabilities createDefaultNetworkCapabilitiesForUid(int uid) {
         final NetworkCapabilities netCap = new NetworkCapabilities();
         netCap.addCapability(NET_CAPABILITY_INTERNET);
@@ -2476,6 +2491,11 @@
                     final boolean valid = (msg.arg1 == NETWORK_TEST_RESULT_VALID);
                     final boolean wasValidated = nai.lastValidated;
                     final boolean wasDefault = isDefaultNetwork(nai);
+                    if (nai.everCaptivePortalDetected && !nai.captivePortalLoginNotified
+                            && valid) {
+                        nai.captivePortalLoginNotified = true;
+                        showNetworkNotification(nai, NotificationType.LOGGED_IN);
+                    }
 
                     final String redirectUrl = (msg.obj instanceof String) ? (String) msg.obj : "";
 
@@ -2496,7 +2516,15 @@
                         updateCapabilities(oldScore, nai, nai.networkCapabilities);
                         // If score has changed, rebroadcast to NetworkFactories. b/17726566
                         if (oldScore != nai.getCurrentScore()) sendUpdatedScoreToFactories(nai);
-                        if (valid) handleFreshlyValidatedNetwork(nai);
+                        if (valid) {
+                            handleFreshlyValidatedNetwork(nai);
+                            // Clear NO_INTERNET and LOST_INTERNET notifications if network becomes
+                            // valid.
+                            mNotifier.clearNotification(nai.network.netId,
+                                    NotificationType.NO_INTERNET);
+                            mNotifier.clearNotification(nai.network.netId,
+                                    NotificationType.LOST_INTERNET);
+                        }
                     }
                     updateInetCondition(nai);
                     // Let the NetworkAgent know the state of its network
@@ -2520,6 +2548,9 @@
                         final int oldScore = nai.getCurrentScore();
                         nai.lastCaptivePortalDetected = visible;
                         nai.everCaptivePortalDetected |= visible;
+                        if (visible) {
+                            nai.captivePortalLoginNotified = false;
+                        }
                         if (nai.lastCaptivePortalDetected &&
                             Settings.Global.CAPTIVE_PORTAL_MODE_AVOID == getCaptivePortalMode()) {
                             if (DBG) log("Avoiding captive portal network: " + nai.name());
@@ -2531,7 +2562,10 @@
                         updateCapabilities(oldScore, nai, nai.networkCapabilities);
                     }
                     if (!visible) {
-                        mNotifier.clearNotification(netId);
+                        // Only clear SIGN_IN and NETWORK_SWITCH notifications here, or else other
+                        // notifications belong to the same network may be cleared unexpected.
+                        mNotifier.clearNotification(netId, NotificationType.SIGN_IN);
+                        mNotifier.clearNotification(netId, NotificationType.NETWORK_SWITCH);
                     } else {
                         if (nai == null) {
                             loge("EVENT_PROVISIONING_NOTIFICATION from unknown NetworkMonitor");
@@ -3237,9 +3271,15 @@
         pw.decreaseIndent();
     }
 
-    private void showValidationNotification(NetworkAgentInfo nai, NotificationType type) {
+    private void showNetworkNotification(NetworkAgentInfo nai, NotificationType type) {
         final String action;
         switch (type) {
+            case LOGGED_IN:
+                action = Settings.ACTION_WIFI_SETTINGS;
+                mHandler.removeMessages(EVENT_TIMEOUT_NOTIFICATION);
+                mHandler.sendMessageDelayed(mHandler.obtainMessage(EVENT_TIMEOUT_NOTIFICATION,
+                        nai.network.netId, 0), TIMEOUT_NOTIFICATION_DELAY_MS);
+                break;
             case NO_INTERNET:
                 action = ConnectivityManager.ACTION_PROMPT_UNVALIDATED;
                 break;
@@ -3252,10 +3292,12 @@
         }
 
         Intent intent = new Intent(action);
-        intent.setData(Uri.fromParts("netId", Integer.toString(nai.network.netId), null));
-        intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
-        intent.setClassName("com.android.settings",
-                "com.android.settings.wifi.WifiNoInternetDialog");
+        if (type != NotificationType.LOGGED_IN) {
+            intent.setData(Uri.fromParts("netId", Integer.toString(nai.network.netId), null));
+            intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
+            intent.setClassName("com.android.settings",
+                    "com.android.settings.wifi.WifiNoInternetDialog");
+        }
 
         PendingIntent pendingIntent = PendingIntent.getActivityAsUser(
                 mContext, 0, intent, PendingIntent.FLAG_CANCEL_CURRENT, null, UserHandle.CURRENT);
@@ -3273,7 +3315,7 @@
                 !nai.networkMisc.explicitlySelected || nai.networkMisc.acceptUnvalidated) {
             return;
         }
-        showValidationNotification(nai, NotificationType.NO_INTERNET);
+        showNetworkNotification(nai, NotificationType.NO_INTERNET);
     }
 
     private void handleNetworkUnvalidated(NetworkAgentInfo nai) {
@@ -3282,7 +3324,7 @@
 
         if (nc.hasTransport(NetworkCapabilities.TRANSPORT_WIFI) &&
             mMultinetworkPolicyTracker.shouldNotifyWifiUnvalidated()) {
-            showValidationNotification(nai, NotificationType.LOST_INTERNET);
+            showNetworkNotification(nai, NotificationType.LOST_INTERNET);
         }
     }
 
@@ -3428,6 +3470,9 @@
                 case EVENT_DATA_SAVER_CHANGED:
                     handleRestrictBackgroundChanged(toBool(msg.arg1));
                     break;
+                case EVENT_TIMEOUT_NOTIFICATION:
+                    mNotifier.clearNotification(msg.arg1, NotificationType.LOGGED_IN);
+                    break;
             }
         }
     }
@@ -3685,20 +3730,46 @@
         }
     }
 
+    /**
+     * Returns information about the proxy a certain network is using. If given a null network, it
+     * it will return the proxy for the bound network for the caller app or the default proxy if
+     * none.
+     *
+     * @param network the network we want to get the proxy information for.
+     * @return Proxy information if a network has a proxy configured, or otherwise null.
+     */
     @Override
     public ProxyInfo getProxyForNetwork(Network network) {
-        if (network == null) return mProxyTracker.getDefaultProxy();
         final ProxyInfo globalProxy = mProxyTracker.getGlobalProxy();
         if (globalProxy != null) return globalProxy;
-        if (!NetworkUtils.queryUserAccess(Binder.getCallingUid(), network.netId)) return null;
-        // Don't call getLinkProperties() as it requires ACCESS_NETWORK_STATE permission, which
-        // caller may not have.
+        if (network == null) {
+            // Get the network associated with the calling UID.
+            final Network activeNetwork = getActiveNetworkForUidInternal(Binder.getCallingUid(),
+                    true);
+            if (activeNetwork == null) {
+                return null;
+            }
+            return getLinkPropertiesProxyInfo(activeNetwork);
+        } else if (queryUserAccess(Binder.getCallingUid(), network.netId)) {
+            // Don't call getLinkProperties() as it requires ACCESS_NETWORK_STATE permission, which
+            // caller may not have.
+            return getLinkPropertiesProxyInfo(network);
+        }
+        // No proxy info available if the calling UID does not have network access.
+        return null;
+    }
+
+    @VisibleForTesting
+    protected boolean queryUserAccess(int uid, int netId) {
+        return NetworkUtils.queryUserAccess(uid, netId);
+    }
+
+    private ProxyInfo getLinkPropertiesProxyInfo(Network network) {
         final NetworkAgentInfo nai = getNetworkAgentInfoForNetwork(network);
         if (nai == null) return null;
         synchronized (nai) {
-            final ProxyInfo proxyInfo = nai.linkProperties.getHttpProxy();
-            if (proxyInfo == null) return null;
-            return new ProxyInfo(proxyInfo);
+            final ProxyInfo linkHttpProxy = nai.linkProperties.getHttpProxy();
+            return linkHttpProxy == null ? null : new ProxyInfo(linkHttpProxy);
         }
     }
 
@@ -3722,11 +3793,10 @@
         mProxyTracker.setDefaultProxy(proxy);
     }
 
-    // If the proxy has changed from oldLp to newLp, resend proxy broadcast with default proxy.
-    // This method gets called when any network changes proxy, but the broadcast only ever contains
-    // the default proxy (even if it hasn't changed).
-    // TODO: Deprecate the broadcast extras as they aren't necessarily applicable in a multi-network
-    // world where an app might be bound to a non-default network.
+    // If the proxy has changed from oldLp to newLp, resend proxy broadcast. This method gets called
+    // when any network changes proxy.
+    // TODO: Remove usage of broadcast extras as they are deprecated and not applicable in a
+    // multi-network world where an app might be bound to a non-default network.
     private void updateProxy(LinkProperties newLp, LinkProperties oldLp) {
         ProxyInfo newProxyInfo = newLp == null ? null : newLp.getHttpProxy();
         ProxyInfo oldProxyInfo = oldLp == null ? null : oldLp.getHttpProxy();
@@ -5893,12 +5963,6 @@
             }
             scheduleUnvalidatedPrompt(networkAgent);
 
-            if (networkAgent.isVPN()) {
-                // Temporarily disable the default proxy (not global).
-                mProxyTracker.setDefaultProxyEnabled(false);
-                // TODO: support proxy per network.
-            }
-
             // Whether a particular NetworkRequest listen should cause signal strength thresholds to
             // be communicated to a particular NetworkAgent depends only on the network's immutable,
             // capabilities, so it only needs to be done once on initial connect, not every time the
@@ -5917,10 +5981,16 @@
         } else if (state == NetworkInfo.State.DISCONNECTED) {
             networkAgent.asyncChannel.disconnect();
             if (networkAgent.isVPN()) {
-                mProxyTracker.setDefaultProxyEnabled(true);
                 updateUids(networkAgent, networkAgent.networkCapabilities, null);
             }
             disconnectAndDestroyNetwork(networkAgent);
+            if (networkAgent.isVPN()) {
+                // As the active or bound network changes for apps, broadcast the default proxy, as
+                // apps may need to update their proxy data. This is called after disconnecting from
+                // VPN to make sure we do not broadcast the old proxy data.
+                // TODO(b/122649188): send the broadcast only to VPN users.
+                mProxyTracker.sendProxyBroadcast();
+            }
         } else if ((oldInfo != null && oldInfo.getState() == NetworkInfo.State.SUSPENDED) ||
                 state == NetworkInfo.State.SUSPENDED) {
             // going into or coming out of SUSPEND: re-score and notify
@@ -6185,6 +6255,17 @@
     }
 
     @Override
+    public void startNattKeepaliveWithFd(Network network, FileDescriptor fd, int resourceId,
+            int intervalSeconds, Messenger messenger, IBinder binder, String srcAddr,
+            String dstAddr) {
+        enforceKeepalivePermission();
+        mKeepaliveTracker.startNattKeepalive(
+                getNetworkAgentInfoForNetwork(network), fd, resourceId,
+                intervalSeconds, messenger, binder,
+                srcAddr, dstAddr, NattSocketKeepalive.NATT_PORT);
+    }
+
+    @Override
     public void stopKeepalive(Network network, int slot) {
         mHandler.sendMessage(mHandler.obtainMessage(
                 NetworkAgent.CMD_STOP_PACKET_KEEPALIVE, slot, PacketKeepalive.SUCCESS, network));
diff --git a/services/core/java/com/android/server/LocationManagerService.java b/services/core/java/com/android/server/LocationManagerService.java
index 8dcc1d5..d2c6354 100644
--- a/services/core/java/com/android/server/LocationManagerService.java
+++ b/services/core/java/com/android/server/LocationManagerService.java
@@ -220,6 +220,8 @@
 
     private final ArraySet<String> mBackgroundThrottlePackageWhitelist = new ArraySet<>();
 
+    private final ArraySet<String> mIgnoreSettingsPackageWhitelist = new ArraySet<>();
+
     @GuardedBy("mLock")
     private final ArrayMap<IBinder, Identity> mGnssMeasurementsListeners = new ArrayMap<>();
 
@@ -353,6 +355,18 @@
                         }
                     }
                 }, UserHandle.USER_ALL);
+        mContext.getContentResolver().registerContentObserver(
+                Settings.Global.getUriFor(
+                        Settings.Global.LOCATION_IGNORE_SETTINGS_PACKAGE_WHITELIST),
+                true,
+                new ContentObserver(mHandler) {
+                    @Override
+                    public void onChange(boolean selfChange) {
+                        synchronized (mLock) {
+                            onIgnoreSettingsWhitelistChangedLocked();
+                        }
+                    }
+                }, UserHandle.USER_ALL);
 
         new PackageMonitor() {
             @Override
@@ -550,6 +564,25 @@
         }
     }
 
+    @GuardedBy("lock")
+    private void onIgnoreSettingsWhitelistChangedLocked() {
+        String setting = Settings.Global.getString(
+                mContext.getContentResolver(),
+                Settings.Global.LOCATION_IGNORE_SETTINGS_PACKAGE_WHITELIST);
+        if (setting == null) {
+            setting = "";
+        }
+
+        mIgnoreSettingsPackageWhitelist.clear();
+        mIgnoreSettingsPackageWhitelist.addAll(
+                SystemConfig.getInstance().getAllowIgnoreLocationSettings());
+        mIgnoreSettingsPackageWhitelist.addAll(Arrays.asList(setting.split(",")));
+
+        for (LocationProvider p : mProviders) {
+            applyRequirementsLocked(p);
+        }
+    }
+
     @GuardedBy("mLock")
     private void onUserProfilesChangedLocked() {
         mCurrentUserProfiles = mUserManager.getProfileIdsWithDisabled(mCurrentUserId);
@@ -1299,8 +1332,7 @@
                     if (provider == null) {
                         continue;
                     }
-                    if (!provider.isUseableLocked()
-                            && !updateRecord.mRealRequest.isLocationSettingsIgnored()) {
+                    if (!provider.isUseableLocked() && !isSettingsExemptLocked(updateRecord)) {
                         continue;
                     }
 
@@ -1988,7 +2020,7 @@
                 }
 
                 // requests that ignore location settings will never provider notifications
-                if (record.mRealRequest.isLocationSettingsIgnored()) {
+                if (isSettingsExemptLocked(record)) {
                     continue;
                 }
 
@@ -2052,8 +2084,7 @@
                         record.mReceiver.mAllowedResolutionLevel)) {
                     continue;
                 }
-                if (!provider.isUseableLocked()
-                        && !record.mRealRequest.isLocationSettingsIgnored()) {
+                if (!provider.isUseableLocked() && !isSettingsExemptLocked(record)) {
                     continue;
                 }
 
@@ -2163,6 +2194,25 @@
         return false;
     }
 
+    @GuardedBy("mLock")
+    private boolean isSettingsExemptLocked(UpdateRecord record) {
+        if (!record.mRealRequest.isLocationSettingsIgnored()) {
+            return false;
+        }
+
+        if (mBackgroundThrottlePackageWhitelist.contains(record.mReceiver.mIdentity.mPackageName)) {
+            return true;
+        }
+
+        for (LocationProvider provider : mProviders) {
+            if (record.mReceiver.mIdentity.mPackageName.equals(provider.getPackageLocked())) {
+                return true;
+            }
+        }
+
+        return false;
+    }
+
     private class UpdateRecord {
         final String mProvider;
         private final LocationRequest mRealRequest;  // original request from client
@@ -2306,7 +2356,7 @@
         }
         // make getFastestInterval() the minimum of interval and fastest interval
         if (sanitizedRequest.getFastestInterval() > sanitizedRequest.getInterval()) {
-            request.setFastestInterval(request.getInterval());
+            sanitizedRequest.setFastestInterval(request.getInterval());
         }
         return sanitizedRequest;
     }
@@ -2418,7 +2468,7 @@
             oldRecord.disposeLocked(false);
         }
 
-        if (!provider.isUseableLocked() && !request.isLocationSettingsIgnored()) {
+        if (!provider.isUseableLocked() && !isSettingsExemptLocked(record)) {
             // Notify the listener that updates are currently disabled - but only if the request
             // does not ignore location settings
             receiver.callProviderEnabledLocked(name, false);
@@ -3056,7 +3106,7 @@
             Receiver receiver = r.mReceiver;
             boolean receiverDead = false;
 
-            if (!provider.isUseableLocked() && !r.mRealRequest.isLocationSettingsIgnored()) {
+            if (!provider.isUseableLocked() && !isSettingsExemptLocked(r)) {
                 continue;
             }
 
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..c064453 100644
--- a/services/core/java/com/android/server/NetworkManagementService.java
+++ b/services/core/java/com/android/server/NetworkManagementService.java
@@ -46,9 +46,7 @@
 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.InterfaceConfiguration;
@@ -63,6 +61,7 @@
 import android.net.TetherStatsParcel;
 import android.net.UidRange;
 import android.net.shared.NetdService;
+import android.net.shared.NetworkObserverRegistry;
 import android.os.BatteryStats;
 import android.os.Binder;
 import android.os.Handler;
@@ -207,16 +206,13 @@
 
     private INetd mNetdService;
 
-    private final NetdUnsolicitedEventListener mNetdUnsolicitedEventListener;
+    private NMSNetworkObserverRegistry mNetworkObserverRegistry;
 
     private IBatteryStats mBatteryStats;
 
     private final Thread mThread;
     private CountDownLatch mConnectedSignal = new CountDownLatch(1);
 
-    private final RemoteCallbackList<INetworkManagementEventObserver> mObservers =
-            new RemoteCallbackList<>();
-
     private final NetworkStatsFactory mStatsFactory = new NetworkStatsFactory();
 
     @GuardedBy("mTetheringStatsProviders")
@@ -326,8 +322,6 @@
 
         mDaemonHandler = new Handler(FgThread.get().getLooper());
 
-        mNetdUnsolicitedEventListener = new NetdUnsolicitedEventListener();
-
         // Add ourself to the Watchdog monitors.
         Watchdog.getInstance().addMonitor(this);
 
@@ -346,7 +340,7 @@
         mFgHandler = null;
         mThread = null;
         mServices = null;
-        mNetdUnsolicitedEventListener = null;
+        mNetworkObserverRegistry = null;
     }
 
     static NetworkManagementService create(Context context, String socket, SystemServices services)
@@ -394,14 +388,12 @@
 
     @Override
     public void registerObserver(INetworkManagementEventObserver observer) {
-        mContext.enforceCallingOrSelfPermission(CONNECTIVITY_INTERNAL, TAG);
-        mObservers.register(observer);
+        mNetworkObserverRegistry.registerObserver(observer);
     }
 
     @Override
     public void unregisterObserver(INetworkManagementEventObserver observer) {
-        mContext.enforceCallingOrSelfPermission(CONNECTIVITY_INTERNAL, TAG);
-        mObservers.unregister(observer);
+        mNetworkObserverRegistry.unregisterObserver(observer);
     }
 
     @FunctionalInterface
@@ -409,127 +401,101 @@
         public void sendCallback(INetworkManagementEventObserver o) throws RemoteException;
     }
 
-    private void invokeForAllObservers(NetworkManagementEventCallback eventCallback) {
-        final int length = mObservers.beginBroadcast();
-        try {
-            for (int i = 0; i < length; i++) {
-                try {
-                    eventCallback.sendCallback(mObservers.getBroadcastItem(i));
-                } catch (RemoteException | RuntimeException e) {
-                }
-            }
-        } finally {
-            mObservers.finishBroadcast();
+    private class NMSNetworkObserverRegistry extends NetworkObserverRegistry {
+        NMSNetworkObserverRegistry(Context context, Handler handler, INetd netd)
+                throws RemoteException {
+            super(context, handler, netd);
         }
-    }
 
-    /**
-     * Notify our observers of an interface status change
-     */
-    private void notifyInterfaceStatusChanged(String iface, boolean up) {
-        invokeForAllObservers(o -> o.interfaceStatusChanged(iface, up));
-    }
+        /**
+         * Notify our observers of a change in the data activity state of the interface
+         */
+        @Override
+        public 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;
 
-    /**
-     * Notify our observers of an interface link state change
-     * (typically, an Ethernet cable has been plugged-in or unplugged).
-     */
-    private void notifyInterfaceLinkStateChanged(String iface, boolean up) {
-        invokeForAllObservers(o -> o.interfaceLinkStateChanged(iface, up));
-    }
-
-    /**
-     * Notify our observers of an interface addition.
-     */
-    private void notifyInterfaceAdded(String iface) {
-        invokeForAllObservers(o -> o.interfaceAdded(iface));
-    }
-
-    /**
-     * Notify our observers of an interface removal.
-     */
-    private void notifyInterfaceRemoved(String iface) {
-        // netd already clears out quota and alerts for removed ifaces; update
-        // our sanity-checking state.
-        mActiveAlerts.remove(iface);
-        mActiveQuotas.remove(iface);
-        invokeForAllObservers(o -> o.interfaceRemoved(iface));
-    }
-
-    /**
-     * Notify our observers of a limit reached.
-     */
-    private void notifyLimitReached(String limitName, String iface) {
-        invokeForAllObservers(o -> o.limitReached(limitName, iface));
-    }
-
-    /**
-     * Notify our observers of a change in the data activity state of the interface
-     */
-    private void notifyInterfaceClassActivity(int type, int powerState, long tsNanos,
-            int uid, boolean fromRadio) {
-        final boolean isMobile = ConnectivityManager.isNetworkTypeMobile(type);
-        if (isMobile) {
-            if (!fromRadio) {
-                if (mMobileActivityFromRadio) {
-                    // If this call is not coming from a report from the radio itself, but we
-                    // have previously received reports from the radio, then we will take the
-                    // power state to just be whatever the radio last reported.
-                    powerState = mLastPowerStateFromRadio;
+            if (isMobile) {
+                if (!fromRadio) {
+                    if (mMobileActivityFromRadio) {
+                        // If this call is not coming from a report from the radio itself, but we
+                        // have previously received reports from the radio, then we will take the
+                        // power state to just be whatever the radio last reported.
+                        powerState = mLastPowerStateFromRadio;
+                    }
+                } else {
+                    mMobileActivityFromRadio = true;
                 }
-            } else {
-                mMobileActivityFromRadio = true;
-            }
-            if (mLastPowerStateFromRadio != powerState) {
-                mLastPowerStateFromRadio = powerState;
-                try {
-                    getBatteryStats().noteMobileRadioPowerState(powerState, tsNanos, uid);
-                } catch (RemoteException e) {
+                if (mLastPowerStateFromRadio != powerState) {
+                    mLastPowerStateFromRadio = powerState;
+                    try {
+                        getBatteryStats().noteMobileRadioPowerState(powerState, tsNanos, uid);
+                    } catch (RemoteException e) {
+                    }
                 }
                 StatsLog.write_non_chained(StatsLog.MOBILE_RADIO_POWER_STATE_CHANGED, uid, null,
                         powerState);
             }
-        }
 
-        if (ConnectivityManager.isNetworkTypeWifi(type)) {
-            if (mLastPowerStateFromWifi != powerState) {
-                mLastPowerStateFromWifi = powerState;
-                try {
-                    getBatteryStats().noteWifiRadioPowerState(powerState, tsNanos, uid);
-                } catch (RemoteException e) {
+            if (ConnectivityManager.isNetworkTypeWifi(type)) {
+                if (mLastPowerStateFromWifi != powerState) {
+                    mLastPowerStateFromWifi = powerState;
+                    try {
+                        getBatteryStats().noteWifiRadioPowerState(powerState, tsNanos, uid);
+                    } catch (RemoteException e) {
+                    }
                 }
                 StatsLog.write_non_chained(StatsLog.WIFI_RADIO_POWER_STATE_CHANGED, uid, null,
                         powerState);
             }
-        }
 
-        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
-            // have previously seen change reports from the radio.  In that case only
-            // the radio is the authority for the current state.
-            final boolean active = isActive;
-            invokeForAllObservers(o -> o.interfaceClassDataActivityChanged(
-                    Integer.toString(type), active, tsNanos));
-        }
-
-        boolean report = false;
-        synchronized (mIdleTimerLock) {
-            if (mActiveIdleTimers.isEmpty()) {
-                // If there are no idle timers, we are not monitoring activity, so we
-                // are always considered active.
-                isActive = true;
+            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
+                // have previously seen change reports from the radio.  In that case only
+                // the radio is the authority for the current state.
+                final boolean active = isActive;
+                super.notifyInterfaceClassActivity(type, isActive, tsNanos, uid, fromRadio);
             }
-            if (mNetworkActive != isActive) {
-                mNetworkActive = isActive;
-                report = isActive;
+
+            boolean report = false;
+            synchronized (mIdleTimerLock) {
+                if (mActiveIdleTimers.isEmpty()) {
+                    // If there are no idle timers, we are not monitoring activity, so we
+                    // are always considered active.
+                    isActive = true;
+                }
+                if (mNetworkActive != isActive) {
+                    mNetworkActive = isActive;
+                    report = isActive;
+                }
+            }
+            if (report) {
+                reportNetworkActive();
             }
         }
-        if (report) {
-            reportNetworkActive();
+
+        /**
+         * Notify our observers of an interface removal.
+         */
+        @Override
+        public void notifyInterfaceRemoved(String iface) {
+            // netd already clears out quota and alerts for removed ifaces; update
+            // our sanity-checking state.
+            mActiveAlerts.remove(iface);
+            mActiveQuotas.remove(iface);
+            super.notifyInterfaceRemoved(iface);
+        }
+
+        @Override
+        public void onStrictCleartextDetected(int uid, String hex) throws RemoteException {
+            // Don't need to post to mDaemonHandler because the only thing
+            // that notifyCleartextNetwork does is post to a handler
+            ActivityManager.getService().notifyCleartextNetwork(uid,
+                    HexDump.hexStringToByteArray(hex));
         }
     }
 
@@ -558,7 +524,8 @@
                 return;
             }
             // No current code examines the interface parameter in a global alert. Just pass null.
-            mDaemonHandler.post(() -> notifyLimitReached(LIMIT_GLOBAL_ALERT, null));
+            mDaemonHandler.post(() -> mNetworkObserverRegistry.notifyLimitReached(
+                    LIMIT_GLOBAL_ALERT, null));
         }
     }
 
@@ -590,10 +557,11 @@
     private void connectNativeNetdService() {
         mNetdService = mServices.getNetd();
         try {
-            mNetdService.registerUnsolicitedEventListener(mNetdUnsolicitedEventListener);
-            if (DBG) Slog.d(TAG, "Register unsolicited event listener");
+            mNetworkObserverRegistry = new NMSNetworkObserverRegistry(
+                    mContext, mDaemonHandler, mNetdService);
+            if (DBG) Slog.d(TAG, "Registered NetworkObserverRegistry");
         } catch (RemoteException | ServiceSpecificException e) {
-            Slog.e(TAG, "Failed to set Netd unsolicited event listener " + e);
+            Slog.wtf(TAG, "Failed to register NetworkObserverRegistry: " + e);
         }
     }
 
@@ -697,120 +665,6 @@
 
     }
 
-    /**
-     * Notify our observers of a new or updated interface address.
-     */
-    private void notifyAddressUpdated(String iface, LinkAddress address) {
-        invokeForAllObservers(o -> o.addressUpdated(iface, address));
-    }
-
-    /**
-     * Notify our observers of a deleted interface address.
-     */
-    private void notifyAddressRemoved(String iface, LinkAddress address) {
-        invokeForAllObservers(o -> o.addressRemoved(iface, address));
-    }
-
-    /**
-     * Notify our observers of DNS server information received.
-     */
-    private void notifyInterfaceDnsServerInfo(String iface, long lifetime, String[] addresses) {
-        invokeForAllObservers(o -> o.interfaceDnsServerInfo(iface, lifetime, addresses));
-    }
-
-    /**
-     * Notify our observers of a route change.
-     */
-    private void notifyRouteChange(boolean updated, RouteInfo route) {
-        if (updated) {
-            invokeForAllObservers(o -> o.routeUpdated(route));
-        } else {
-            invokeForAllObservers(o -> o.routeRemoved(route));
-        }
-    }
-
-    private class NetdUnsolicitedEventListener extends INetdUnsolicitedEventListener.Stub {
-        @Override
-        public void onInterfaceClassActivityChanged(boolean isActive,
-                int label, long timestamp, int uid) throws RemoteException {
-            final long timestampNanos;
-            if (timestamp <= 0) {
-                timestampNanos = SystemClock.elapsedRealtimeNanos();
-            } else {
-                timestampNanos = timestamp;
-            }
-            mDaemonHandler.post(() -> notifyInterfaceClassActivity(label,
-                    isActive ? DataConnectionRealTimeInfo.DC_POWER_STATE_HIGH
-                    : DataConnectionRealTimeInfo.DC_POWER_STATE_LOW,
-                    timestampNanos, uid, false));
-        }
-
-        @Override
-        public void onQuotaLimitReached(String alertName, String ifName)
-                throws RemoteException {
-            mDaemonHandler.post(() -> notifyLimitReached(alertName, ifName));
-        }
-
-        @Override
-        public void onInterfaceDnsServerInfo(String ifName,
-                long lifetime, String[] servers) throws RemoteException {
-            mDaemonHandler.post(() -> notifyInterfaceDnsServerInfo(ifName, lifetime, servers));
-        }
-
-        @Override
-        public void onInterfaceAddressUpdated(String addr,
-                String ifName, int flags, int scope) throws RemoteException {
-            final LinkAddress address = new LinkAddress(addr, flags, scope);
-            mDaemonHandler.post(() -> notifyAddressUpdated(ifName, address));
-        }
-
-        @Override
-        public void onInterfaceAddressRemoved(String addr,
-                String ifName, int flags, int scope) throws RemoteException {
-            final LinkAddress address = new LinkAddress(addr, flags, scope);
-            mDaemonHandler.post(() -> notifyAddressRemoved(ifName, address));
-        }
-
-        @Override
-        public void onInterfaceAdded(String ifName) throws RemoteException {
-            mDaemonHandler.post(() -> notifyInterfaceAdded(ifName));
-        }
-
-        @Override
-        public void onInterfaceRemoved(String ifName) throws RemoteException {
-            mDaemonHandler.post(() -> notifyInterfaceRemoved(ifName));
-        }
-
-        @Override
-        public void onInterfaceChanged(String ifName, boolean up)
-                throws RemoteException {
-            mDaemonHandler.post(() -> notifyInterfaceStatusChanged(ifName, up));
-        }
-
-        @Override
-        public void onInterfaceLinkStateChanged(String ifName, boolean up)
-                throws RemoteException {
-            mDaemonHandler.post(() -> notifyInterfaceLinkStateChanged(ifName, up));
-        }
-
-        @Override
-        public void onRouteChanged(boolean updated,
-                String route, String gateway, String ifName) throws RemoteException {
-            final RouteInfo processRoute = new RouteInfo(new IpPrefix(route),
-                    ("".equals(gateway)) ? null : InetAddresses.parseNumericAddress(gateway),
-                    ifName);
-            mDaemonHandler.post(() -> notifyRouteChange(updated, processRoute));
-        }
-
-        @Override
-        public void onStrictCleartextDetected(int uid, String hex) throws RemoteException {
-            // Don't need to post to mDaemonHandler because the only thing
-            // that notifyCleartextNetwork does is post to a handler
-            ActivityManager.getService().notifyCleartextNetwork(uid,
-                    HexDump.hexStringToByteArray(hex));
-        }
-    }
-
     //
     // Netd Callback handling
     //
@@ -859,16 +713,18 @@
                         throw new IllegalStateException(errorMessage);
                     }
                     if (cooked[2].equals("added")) {
-                        notifyInterfaceAdded(cooked[3]);
+                        mNetworkObserverRegistry.notifyInterfaceAdded(cooked[3]);
                         return true;
                     } else if (cooked[2].equals("removed")) {
-                        notifyInterfaceRemoved(cooked[3]);
+                        mNetworkObserverRegistry.notifyInterfaceRemoved(cooked[3]);
                         return true;
                     } else if (cooked[2].equals("changed") && cooked.length == 5) {
-                        notifyInterfaceStatusChanged(cooked[3], cooked[4].equals("up"));
+                        mNetworkObserverRegistry.notifyInterfaceStatusChanged(
+                                cooked[3], cooked[4].equals("up"));
                         return true;
                     } else if (cooked[2].equals("linkstate") && cooked.length == 5) {
-                        notifyInterfaceLinkStateChanged(cooked[3], cooked[4].equals("up"));
+                        mNetworkObserverRegistry.notifyInterfaceLinkStateChanged(
+                                cooked[3], cooked[4].equals("up"));
                         return true;
                     }
                     throw new IllegalStateException(errorMessage);
@@ -882,7 +738,7 @@
                         throw new IllegalStateException(errorMessage);
                     }
                     if (cooked[2].equals("alert")) {
-                        notifyLimitReached(cooked[3], cooked[4]);
+                        mNetworkObserverRegistry.notifyLimitReached(cooked[3], cooked[4]);
                         return true;
                     }
                     throw new IllegalStateException(errorMessage);
@@ -908,9 +764,8 @@
                         timestampNanos = SystemClock.elapsedRealtimeNanos();
                     }
                     boolean isActive = cooked[2].equals("active");
-                    notifyInterfaceClassActivity(Integer.parseInt(cooked[3]),
-                            isActive ? DataConnectionRealTimeInfo.DC_POWER_STATE_HIGH
-                            : DataConnectionRealTimeInfo.DC_POWER_STATE_LOW,
+                    mNetworkObserverRegistry.notifyInterfaceClassActivity(
+                            Integer.parseInt(cooked[3]), isActive,
                             timestampNanos, processUid, false);
                     return true;
                     // break;
@@ -937,9 +792,9 @@
                     }
 
                     if (cooked[2].equals("updated")) {
-                        notifyAddressUpdated(iface, address);
+                        mNetworkObserverRegistry.notifyAddressUpdated(iface, address);
                     } else {
-                        notifyAddressRemoved(iface, address);
+                        mNetworkObserverRegistry.notifyAddressRemoved(iface, address);
                     }
                     return true;
                     // break;
@@ -959,7 +814,8 @@
                             throw new IllegalStateException(errorMessage);
                         }
                         String[] servers = cooked[5].split(",");
-                        notifyInterfaceDnsServerInfo(cooked[3], lifetime, servers);
+                        mNetworkObserverRegistry.notifyInterfaceDnsServerInfo(
+                                cooked[3], lifetime, servers);
                     }
                     return true;
                     // break;
@@ -998,7 +854,8 @@
                             InetAddress gateway = null;
                             if (via != null) gateway = InetAddress.parseNumericAddress(via);
                             RouteInfo route = new RouteInfo(new IpPrefix(cooked[3]), gateway, dev);
-                            notifyRouteChange(cooked[2].equals("updated"), route);
+                            mNetworkObserverRegistry.notifyRouteChange(
+                                    cooked[2].equals("updated"), route);
                             return true;
                         } catch (IllegalArgumentException e) {}
                     }
@@ -1461,9 +1318,8 @@
             if (ConnectivityManager.isNetworkTypeMobile(type)) {
                 mNetworkActive = false;
             }
-            mDaemonHandler.post(() -> notifyInterfaceClassActivity(type,
-                    DataConnectionRealTimeInfo.DC_POWER_STATE_HIGH,
-                    SystemClock.elapsedRealtimeNanos(), -1, false));
+            mDaemonHandler.post(() -> mNetworkObserverRegistry.notifyInterfaceClassActivity(
+                    type, true /* isActive */, SystemClock.elapsedRealtimeNanos(), -1, false));
         }
     }
 
@@ -1486,9 +1342,9 @@
                 throw new IllegalStateException(e);
             }
             mActiveIdleTimers.remove(iface);
-            mDaemonHandler.post(() -> notifyInterfaceClassActivity(params.type,
-                    DataConnectionRealTimeInfo.DC_POWER_STATE_LOW,
-                    SystemClock.elapsedRealtimeNanos(), -1, false));
+            mDaemonHandler.post(() -> mNetworkObserverRegistry.notifyInterfaceClassActivity(
+                    params.type, false /* isActive */, 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..cec825f 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,14 @@
                     refreshIsolatedStorageSettings();
                 }
             });
+        // For now, simply clone property when it changes
+        DeviceConfig.addOnPropertyChangedListener(DeviceConfig.Storage.NAMESPACE,
+                mContext.getMainExecutor(), (namespace, name, value) -> {
+                    if (DeviceConfig.Storage.ISOLATED_STORAGE_ENABLED.equals(name)) {
+                        Settings.Global.putString(mResolver,
+                                Settings.Global.ISOLATED_STORAGE_REMOTE, value);
+                    }
+                });
         refreshIsolatedStorageSettings();
     }
 
@@ -1523,6 +1533,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/TEST_MAPPING b/services/core/java/com/android/server/TEST_MAPPING
index 16b12f1..1870f8d 100644
--- a/services/core/java/com/android/server/TEST_MAPPING
+++ b/services/core/java/com/android/server/TEST_MAPPING
@@ -2,6 +2,7 @@
     "presubmit": [
         {
             "name": "FrameworksMockingServicesTests",
+            "file_patterns": ["AlarmManagerService\\.java"],
             "options": [
                 {
                   "include-annotation": "android.platform.test.annotations.Presubmit"
diff --git a/services/core/java/com/android/server/TelephonyRegistry.java b/services/core/java/com/android/server/TelephonyRegistry.java
index aa23890..e543617 100644
--- a/services/core/java/com/android/server/TelephonyRegistry.java
+++ b/services/core/java/com/android/server/TelephonyRegistry.java
@@ -2115,6 +2115,16 @@
                     android.Manifest.permission.READ_PRECISE_PHONE_STATE, null);
         }
 
+        if ((events & PhoneStateListener.LISTEN_RADIO_POWER_STATE_CHANGED) != 0) {
+            mContext.enforceCallingOrSelfPermission(
+                    android.Manifest.permission.READ_PRIVILEGED_PHONE_STATE, null);
+        }
+
+        if ((events & PhoneStateListener.LISTEN_VOICE_ACTIVATION_STATE) != 0) {
+            mContext.enforceCallingOrSelfPermission(
+                    android.Manifest.permission.READ_PRIVILEGED_PHONE_STATE, null);
+        }
+
         return true;
     }
 
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/accounts/AccountManagerService.java b/services/core/java/com/android/server/accounts/AccountManagerService.java
index e879efd..c826df0 100644
--- a/services/core/java/com/android/server/accounts/AccountManagerService.java
+++ b/services/core/java/com/android/server/accounts/AccountManagerService.java
@@ -5202,7 +5202,7 @@
                     Process.SYSTEM_UID, null /* packageName */, false);
             fout.println("Accounts: " + accounts.length);
             for (Account account : accounts) {
-                fout.println("  " + account.toSafeString());
+                fout.println("  " + account.toString());
             }
 
             // Add debug information.
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/ActivityManagerConstants.java b/services/core/java/com/android/server/am/ActivityManagerConstants.java
index dd2b33a..d025d73 100644
--- a/services/core/java/com/android/server/am/ActivityManagerConstants.java
+++ b/services/core/java/com/android/server/am/ActivityManagerConstants.java
@@ -16,13 +16,19 @@
 
 package com.android.server.am;
 
+import static android.provider.DeviceConfig.ActivityManager.KEY_MAX_CACHED_PROCESSES;
+
 import static com.android.server.am.ActivityManagerDebugConfig.DEBUG_POWER_QUICK;
 
+import android.app.ActivityThread;
 import android.content.ContentResolver;
 import android.database.ContentObserver;
 import android.net.Uri;
 import android.os.Handler;
+import android.provider.DeviceConfig;
+import android.provider.DeviceConfig.OnPropertyChangedListener;
 import android.provider.Settings;
+import android.text.TextUtils;
 import android.util.KeyValueListParser;
 import android.util.Slog;
 
@@ -34,7 +40,6 @@
 final class ActivityManagerConstants extends ContentObserver {
 
     // Key names stored in the settings value.
-    private static final String KEY_MAX_CACHED_PROCESSES = "max_cached_processes";
     private static final String KEY_BACKGROUND_SETTLE_TIME = "background_settle_time";
     private static final String KEY_FGSERVICE_MIN_SHOWN_TIME
             = "fgservice_min_shown_time";
@@ -69,13 +74,6 @@
     static final String KEY_PROCESS_START_ASYNC = "process_start_async";
     static final String KEY_MEMORY_INFO_THROTTLE_TIME = "memory_info_throttle_time";
     static final String KEY_TOP_TO_FGS_GRACE_DURATION = "top_to_fgs_grace_duration";
-    static final String KEY_USE_COMPACTION = "use_compaction";
-    static final String KEY_COMPACT_ACTION_1 = "compact_action_1";
-    static final String KEY_COMPACT_ACTION_2 = "compact_action_2";
-    static final String KEY_COMPACT_THROTTLE_1 = "compact_throttle_1";
-    static final String KEY_COMPACT_THROTTLE_2 = "compact_throttle_2";
-    static final String KEY_COMPACT_THROTTLE_3 = "compact_throttle_3";
-    static final String KEY_COMPACT_THROTTLE_4 = "compact_throttle_4";
 
     private static final int DEFAULT_MAX_CACHED_PROCESSES = 32;
     private static final long DEFAULT_BACKGROUND_SETTLE_TIME = 60*1000;
@@ -106,13 +104,6 @@
     private static final boolean DEFAULT_PROCESS_START_ASYNC = true;
     private static final long DEFAULT_MEMORY_INFO_THROTTLE_TIME = 5*60*1000;
     private static final long DEFAULT_TOP_TO_FGS_GRACE_DURATION = 15 * 1000;
-    private static final boolean DEFAULT_USE_COMPACTION = false;
-    public static final int DEFAULT_COMPACT_ACTION_1 = 1;
-    public static final int DEFAULT_COMPACT_ACTION_2 = 3;
-    public static final long DEFAULT_COMPACT_THROTTLE_1 = 5000;
-    public static final long DEFAULT_COMPACT_THROTTLE_2 = 10000;
-    public static final long DEFAULT_COMPACT_THROTTLE_3 = 500;
-    public static final long DEFAULT_COMPACT_THROTTLE_4 = 10000;
 
     // Maximum number of cached processes we will allow.
     public int MAX_CACHED_PROCESSES = DEFAULT_MAX_CACHED_PROCESSES;
@@ -232,23 +223,6 @@
     // this long.
     public long TOP_TO_FGS_GRACE_DURATION = DEFAULT_TOP_TO_FGS_GRACE_DURATION;
 
-    // Use compaction for background apps.
-    public boolean USE_COMPACTION = DEFAULT_USE_COMPACTION;
-
-    // Action for compactAppSome.
-    public int COMPACT_ACTION_1 = DEFAULT_COMPACT_ACTION_1;
-    // Action for compactAppFull;
-    public int COMPACT_ACTION_2 = DEFAULT_COMPACT_ACTION_2;
-
-    // How long we'll skip second compactAppSome after first compactAppSome
-    public long COMPACT_THROTTLE_1 = DEFAULT_COMPACT_THROTTLE_1;
-    // How long we'll skip compactAppSome after compactAppFull
-    public long COMPACT_THROTTLE_2 = DEFAULT_COMPACT_THROTTLE_2;
-    // How long we'll skip compactAppFull after compactAppSome
-    public long COMPACT_THROTTLE_3 = DEFAULT_COMPACT_THROTTLE_3;
-    // How long we'll skip second compactAppFull after first compactAppFull
-    public long COMPACT_THROTTLE_4 = DEFAULT_COMPACT_THROTTLE_4;
-
     // Indicates whether the activity starts logging is enabled.
     // Controlled by Settings.Global.ACTIVITY_STARTS_LOGGING_ENABLED
     volatile boolean mFlagActivityStartsLoggingEnabled;
@@ -295,10 +269,19 @@
                 Settings.Global.getUriFor(
                         Settings.Global.BACKGROUND_ACTIVITY_STARTS_ENABLED);
 
+    private final OnPropertyChangedListener mOnDeviceConfigChangedListener =
+            new OnPropertyChangedListener() {
+                @Override
+                public void onPropertyChanged(String namespace, String name, String value) {
+                    if (KEY_MAX_CACHED_PROCESSES.equals(name)) {
+                        updateMaxCachedProcesses();
+                    }
+                }
+            };
+
     public ActivityManagerConstants(ActivityManagerService service, Handler handler) {
         super(handler);
         mService = service;
-        updateMaxCachedProcesses();
     }
 
     public void start(ContentResolver resolver) {
@@ -309,6 +292,11 @@
         updateConstants();
         updateActivityStartsLoggingEnabled();
         updateBackgroundActivityStartsEnabled();
+        DeviceConfig.addOnPropertyChangedListener(DeviceConfig.ActivityManager.NAMESPACE,
+                ActivityThread.currentApplication().getMainExecutor(),
+                mOnDeviceConfigChangedListener);
+        updateMaxCachedProcesses();
+
     }
 
     public void setOverrideMaxCachedProcesses(int value) {
@@ -347,8 +335,6 @@
                 // with defaults.
                 Slog.e("ActivityManagerConstants", "Bad activity manager config settings", e);
             }
-            MAX_CACHED_PROCESSES = mParser.getInt(KEY_MAX_CACHED_PROCESSES,
-                    DEFAULT_MAX_CACHED_PROCESSES);
             BACKGROUND_SETTLE_TIME = mParser.getLong(KEY_BACKGROUND_SETTLE_TIME,
                     DEFAULT_BACKGROUND_SETTLE_TIME);
             FGSERVICE_MIN_SHOWN_TIME = mParser.getLong(KEY_FGSERVICE_MIN_SHOWN_TIME,
@@ -406,13 +392,9 @@
                     DEFAULT_MEMORY_INFO_THROTTLE_TIME);
             TOP_TO_FGS_GRACE_DURATION = mParser.getDurationMillis(KEY_TOP_TO_FGS_GRACE_DURATION,
                     DEFAULT_TOP_TO_FGS_GRACE_DURATION);
-            USE_COMPACTION = mParser.getBoolean(KEY_USE_COMPACTION, DEFAULT_USE_COMPACTION);
-            COMPACT_ACTION_1 = mParser.getInt(KEY_COMPACT_ACTION_1, DEFAULT_COMPACT_ACTION_1);
-            COMPACT_ACTION_2 = mParser.getInt(KEY_COMPACT_ACTION_2, DEFAULT_COMPACT_ACTION_2);
-            COMPACT_THROTTLE_1 = mParser.getLong(KEY_COMPACT_THROTTLE_1, DEFAULT_COMPACT_THROTTLE_1);
-            COMPACT_THROTTLE_2 = mParser.getLong(KEY_COMPACT_THROTTLE_2, DEFAULT_COMPACT_THROTTLE_2);
-            COMPACT_THROTTLE_3 = mParser.getLong(KEY_COMPACT_THROTTLE_3, DEFAULT_COMPACT_THROTTLE_3);
-            COMPACT_THROTTLE_4 = mParser.getLong(KEY_COMPACT_THROTTLE_4, DEFAULT_COMPACT_THROTTLE_4);
+
+            // For new flags that are intended for server-side experiments, please use the new
+            // DeviceConfig package.
 
             updateMaxCachedProcesses();
         }
@@ -429,8 +411,19 @@
     }
 
     private void updateMaxCachedProcesses() {
-        CUR_MAX_CACHED_PROCESSES = mOverrideMaxCachedProcesses < 0
-                ? MAX_CACHED_PROCESSES : mOverrideMaxCachedProcesses;
+        String maxCachedProcessesFlag = DeviceConfig.getProperty(
+                DeviceConfig.ActivityManager.NAMESPACE, KEY_MAX_CACHED_PROCESSES);
+        try {
+            CUR_MAX_CACHED_PROCESSES = mOverrideMaxCachedProcesses < 0
+                    ? (TextUtils.isEmpty(maxCachedProcessesFlag)
+                    ? DEFAULT_MAX_CACHED_PROCESSES : Integer.parseInt(maxCachedProcessesFlag))
+                    : mOverrideMaxCachedProcesses;
+        } catch (NumberFormatException e) {
+            // Bad flag value from Phenotype, revert to default.
+            Slog.e("ActivityManagerConstants",
+                    "Unable to parse flag for max_cached_processes: " + maxCachedProcessesFlag, e);
+            CUR_MAX_CACHED_PROCESSES = DEFAULT_MAX_CACHED_PROCESSES;
+        }
         CUR_MAX_EMPTY_PROCESSES = computeEmptyProcessLimit(CUR_MAX_CACHED_PROCESSES);
 
         // Note the trim levels do NOT depend on the override process limit, we want
@@ -503,8 +496,6 @@
         pw.println(MEMORY_INFO_THROTTLE_TIME);
         pw.print("  "); pw.print(KEY_TOP_TO_FGS_GRACE_DURATION); pw.print("=");
         pw.println(TOP_TO_FGS_GRACE_DURATION);
-        pw.print("  "); pw.print(KEY_USE_COMPACTION); pw.print("=");
-        pw.println(USE_COMPACTION);
 
         pw.println();
         if (mOverrideMaxCachedProcesses >= 0) {
diff --git a/services/core/java/com/android/server/am/ActivityManagerService.java b/services/core/java/com/android/server/am/ActivityManagerService.java
index b24290f..eb643b6 100644
--- a/services/core/java/com/android/server/am/ActivityManagerService.java
+++ b/services/core/java/com/android/server/am/ActivityManagerService.java
@@ -1607,19 +1607,8 @@
                 }
             } break;
             case UPDATE_HTTP_PROXY_MSG: {
-                ProxyInfo proxy = (ProxyInfo)msg.obj;
-                String host = "";
-                String port = "";
-                String exclList = "";
-                Uri pacFileUrl = Uri.EMPTY;
-                if (proxy != null) {
-                    host = proxy.getHost();
-                    port = Integer.toString(proxy.getPort());
-                    exclList = proxy.getExclusionListAsString();
-                    pacFileUrl = proxy.getPacFileUrl();
-                }
                 synchronized (ActivityManagerService.this) {
-                    mProcessList.setAllHttpProxyLocked(host, port, exclList, pacFileUrl);
+                    mProcessList.setAllHttpProxyLocked();
                 }
             } break;
             case PROC_START_TIMEOUT_MSG: {
@@ -7228,6 +7217,7 @@
         mActivityTaskManager.installSystemProviders();
         mDevelopmentSettingsObserver = new DevelopmentSettingsObserver();
         SettingsToPropertiesMapper.start(mContext.getContentResolver());
+        mOomAdjuster.initSettings();
 
         // Now that the settings provider is published we can consider sending
         // in a rescue party.
@@ -9340,6 +9330,7 @@
 
         synchronized(this) {
             mConstants.dump(pw);
+            mOomAdjuster.dumpAppCompactorSettings(pw);
             pw.println();
             if (dumpAll) {
                 pw.println("-------------------------------------------------------------------------------");
@@ -9739,6 +9730,7 @@
             } else if ("settings".equals(cmd)) {
                 synchronized (this) {
                     mConstants.dump(pw);
+                    mOomAdjuster.dumpAppCompactorSettings(pw);
                 }
             } else if ("services".equals(cmd) || "s".equals(cmd)) {
                 if (dumpClient) {
@@ -14656,8 +14648,7 @@
                     mHandler.sendEmptyMessage(CLEAR_DNS_CACHE_MSG);
                     break;
                 case Proxy.PROXY_CHANGE_ACTION:
-                    ProxyInfo proxy = intent.getParcelableExtra(Proxy.EXTRA_PROXY_INFO);
-                    mHandler.sendMessage(mHandler.obtainMessage(UPDATE_HTTP_PROXY_MSG, proxy));
+                    mHandler.sendMessage(mHandler.obtainMessage(UPDATE_HTTP_PROXY_MSG));
                     break;
                 case android.hardware.Camera.ACTION_NEW_PICTURE:
                 case android.hardware.Camera.ACTION_NEW_VIDEO:
diff --git a/services/core/java/com/android/server/am/AppCompactor.java b/services/core/java/com/android/server/am/AppCompactor.java
index fd402cc..bb55ec31 100644
--- a/services/core/java/com/android/server/am/AppCompactor.java
+++ b/services/core/java/com/android/server/am/AppCompactor.java
@@ -16,34 +16,63 @@
 
 package com.android.server.am;
 
-import com.android.internal.annotations.GuardedBy;
+import static android.os.Process.THREAD_PRIORITY_FOREGROUND;
+import static android.provider.DeviceConfig.ActivityManager.KEY_COMPACT_ACTION_1;
+import static android.provider.DeviceConfig.ActivityManager.KEY_COMPACT_ACTION_2;
+import static android.provider.DeviceConfig.ActivityManager.KEY_COMPACT_THROTTLE_1;
+import static android.provider.DeviceConfig.ActivityManager.KEY_COMPACT_THROTTLE_2;
+import static android.provider.DeviceConfig.ActivityManager.KEY_COMPACT_THROTTLE_3;
+import static android.provider.DeviceConfig.ActivityManager.KEY_COMPACT_THROTTLE_4;
+import static android.provider.DeviceConfig.ActivityManager.KEY_USE_COMPACTION;
 
 import android.app.ActivityManager;
-
+import android.app.ActivityThread;
 import android.os.Handler;
-import android.os.Looper;
 import android.os.Message;
 import android.os.Process;
 import android.os.SystemClock;
 import android.os.Trace;
-
+import android.provider.DeviceConfig;
+import android.provider.DeviceConfig.OnPropertyChangedListener;
+import android.text.TextUtils;
 import android.util.EventLog;
 import android.util.StatsLog;
 
-import static android.os.Process.THREAD_PRIORITY_FOREGROUND;
-
+import com.android.internal.annotations.GuardedBy;
+import com.android.internal.annotations.VisibleForTesting;
 import com.android.server.ServiceThread;
 
 import java.io.FileOutputStream;
-import java.io.IOException;
+import java.io.PrintWriter;
 import java.util.ArrayList;
 
 public final class AppCompactor {
-    /**
-     * Processes to compact.
-     */
-    final ArrayList<ProcessRecord> mPendingCompactionProcesses = new ArrayList<ProcessRecord>();
 
+    // Phenotype sends int configurations and we map them to the strings we'll use on device,
+    // preventing a weird string value entering the kernel.
+    private static final int COMPACT_ACTION_FILE_FLAG = 1;
+    private static final int COMPACT_ACTION_ANON_FLAG = 2;
+    private static final int COMPACT_ACTION_FULL_FLAG = 3;
+    private static final String COMPACT_ACTION_FILE = "file";
+    private static final String COMPACT_ACTION_ANON = "anon";
+    private static final String COMPACT_ACTION_FULL = "all";
+
+    // Defaults for phenotype flags.
+    @VisibleForTesting static final Boolean DEFAULT_USE_COMPACTION = false;
+    @VisibleForTesting static final int DEFAULT_COMPACT_ACTION_1 = COMPACT_ACTION_FILE_FLAG;
+    @VisibleForTesting static final int DEFAULT_COMPACT_ACTION_2 = COMPACT_ACTION_FULL_FLAG;
+    @VisibleForTesting static final long DEFAULT_COMPACT_THROTTLE_1 = 5_000;
+    @VisibleForTesting static final long DEFAULT_COMPACT_THROTTLE_2 = 10_000;
+    @VisibleForTesting static final long DEFAULT_COMPACT_THROTTLE_3 = 500;
+    @VisibleForTesting static final long DEFAULT_COMPACT_THROTTLE_4 = 10_000;
+
+    @VisibleForTesting
+    interface PropertyChangedCallbackForTest {
+        void onPropertyChanged();
+    }
+    private PropertyChangedCallbackForTest mTestCallback;
+
+    // Handler constants.
     static final int COMPACT_PROCESS_SOME = 1;
     static final int COMPACT_PROCESS_FULL = 2;
     static final int COMPACT_PROCESS_MSG = 1;
@@ -57,70 +86,106 @@
      */
     final ServiceThread mCompactionThread;
 
-    final private Handler mCompactionHandler;
+    private final ArrayList<ProcessRecord> mPendingCompactionProcesses =
+            new ArrayList<ProcessRecord>();
+    private final ActivityManagerService mAm;
+    private final OnPropertyChangedListener mOnFlagsChangedListener =
+            new OnPropertyChangedListener() {
+                @Override
+                public void onPropertyChanged(String namespace, String name, String value) {
+                    synchronized (mPhenotypeFlagLock) {
+                        if (KEY_USE_COMPACTION.equals(name)) {
+                            updateUseCompaction();
+                        } else if (KEY_COMPACT_ACTION_1.equals(name)
+                                || KEY_COMPACT_ACTION_2.equals(name)) {
+                            updateCompactionActions();
+                        } else if (KEY_COMPACT_THROTTLE_1.equals(name)
+                                || KEY_COMPACT_THROTTLE_2.equals(name)
+                                || KEY_COMPACT_THROTTLE_3.equals(name)
+                                || KEY_COMPACT_THROTTLE_4.equals(name)) {
+                            updateCompactionThrottles();
+                        }
+                    }
+                    if (mTestCallback != null) {
+                        mTestCallback.onPropertyChanged();
+                    }
+                }
+            };
 
-    final private ActivityManagerService mAm;
-    final private ActivityManagerConstants mConstants;
+    private final Object mPhenotypeFlagLock = new Object();
 
-    final private String COMPACT_ACTION_FILE = "file";
-    final private String COMPACT_ACTION_ANON = "anon";
-    final private String COMPACT_ACTION_FULL = "all";
+    // Configured by phenotype. Updates from the server take effect immediately.
+    @GuardedBy("mPhenotypeFlagLock")
+    @VisibleForTesting String mCompactActionSome =
+            compactActionIntToString(DEFAULT_COMPACT_ACTION_1);
+    @GuardedBy("mPhenotypeFlagLock")
+    @VisibleForTesting String mCompactActionFull =
+            compactActionIntToString(DEFAULT_COMPACT_ACTION_2);
+    @GuardedBy("mPhenotypeFlagLock")
+    @VisibleForTesting long mCompactThrottleSomeSome = DEFAULT_COMPACT_THROTTLE_1;
+    @GuardedBy("mPhenotypeFlagLock")
+    @VisibleForTesting long mCompactThrottleSomeFull = DEFAULT_COMPACT_THROTTLE_2;
+    @GuardedBy("mPhenotypeFlagLock")
+    @VisibleForTesting long mCompactThrottleFullSome = DEFAULT_COMPACT_THROTTLE_3;
+    @GuardedBy("mPhenotypeFlagLock")
+    @VisibleForTesting long mCompactThrottleFullFull = DEFAULT_COMPACT_THROTTLE_4;
+    @GuardedBy("mPhenotypeFlagLock")
+    private boolean mUseCompaction = DEFAULT_USE_COMPACTION;
 
-    final private String compactActionSome;
-    final private String compactActionFull;
-
-    final private long throttleSomeSome;
-    final private long throttleSomeFull;
-    final private long throttleFullSome;
-    final private long throttleFullFull;
+    // Handler on which compaction runs.
+    private Handler mCompactionHandler;
 
     public AppCompactor(ActivityManagerService am) {
         mAm = am;
-        mConstants = am.mConstants;
-
         mCompactionThread = new ServiceThread("CompactionThread",
                 THREAD_PRIORITY_FOREGROUND, true);
-        mCompactionThread.start();
-        mCompactionHandler = new MemCompactionHandler(this);
-
-        switch(mConstants.COMPACT_ACTION_1) {
-            case 1:
-                compactActionSome = COMPACT_ACTION_FILE;
-                break;
-            case 2:
-                compactActionSome = COMPACT_ACTION_ANON;
-                break;
-            case 3:
-                compactActionSome = COMPACT_ACTION_FULL;
-                break;
-            default:
-                compactActionSome = COMPACT_ACTION_FILE;
-                break;
-        }
-
-        switch(mConstants.COMPACT_ACTION_2) {
-            case 1:
-                compactActionFull = COMPACT_ACTION_FILE;
-                break;
-            case 2:
-                compactActionFull = COMPACT_ACTION_ANON;
-                break;
-            case 3:
-                compactActionFull = COMPACT_ACTION_FULL;
-                break;
-            default:
-                compactActionFull = COMPACT_ACTION_FULL;
-                break;
-        }
-
-        throttleSomeSome = mConstants.COMPACT_THROTTLE_1;
-        throttleSomeFull = mConstants.COMPACT_THROTTLE_2;
-        throttleFullSome = mConstants.COMPACT_THROTTLE_3;
-        throttleFullFull = mConstants.COMPACT_THROTTLE_4;
     }
 
-    // Must be called while holding AMS lock.
-    final void compactAppSome(ProcessRecord app) {
+    @VisibleForTesting
+    AppCompactor(ActivityManagerService am, PropertyChangedCallbackForTest callback) {
+        this(am);
+        mTestCallback = callback;
+    }
+
+    /**
+     * Reads phenotype config to determine whether app compaction is enabled or not and
+     * starts the background thread if necessary.
+     */
+    public void init() {
+        DeviceConfig.addOnPropertyChangedListener(DeviceConfig.ActivityManager.NAMESPACE,
+                ActivityThread.currentApplication().getMainExecutor(), mOnFlagsChangedListener);
+        synchronized (mPhenotypeFlagLock) {
+            updateUseCompaction();
+            updateCompactionActions();
+            updateCompactionThrottles();
+        }
+    }
+
+    /**
+     * Returns whether compaction is enabled.
+     */
+    public boolean useCompaction() {
+        synchronized (mPhenotypeFlagLock) {
+            return mUseCompaction;
+        }
+    }
+
+    @GuardedBy("mAm")
+    void dump(PrintWriter pw) {
+        pw.println("AppCompactor settings");
+        synchronized (mPhenotypeFlagLock) {
+            pw.println("  " + KEY_USE_COMPACTION + "=" + mUseCompaction);
+            pw.println("  " + KEY_COMPACT_ACTION_1 + "=" + mCompactActionSome);
+            pw.println("  " + KEY_COMPACT_ACTION_2 + "=" + mCompactActionFull);
+            pw.println("  " + KEY_COMPACT_THROTTLE_1 + "=" + mCompactThrottleSomeSome);
+            pw.println("  " + KEY_COMPACT_THROTTLE_2 + "=" + mCompactThrottleSomeFull);
+            pw.println("  " + KEY_COMPACT_THROTTLE_3 + "=" + mCompactThrottleFullSome);
+            pw.println("  " + KEY_COMPACT_THROTTLE_4 + "=" + mCompactThrottleFullFull);
+        }
+    }
+
+    @GuardedBy("mAm")
+    void compactAppSome(ProcessRecord app) {
         app.reqCompactAction = COMPACT_PROCESS_SOME;
         mPendingCompactionProcesses.add(app);
         mCompactionHandler.sendMessage(
@@ -128,8 +193,8 @@
                 COMPACT_PROCESS_MSG, app.curAdj, app.setProcState));
     }
 
-    // Must be called while holding AMS lock.
-    final void compactAppFull(ProcessRecord app) {
+    @GuardedBy("mAm")
+    void compactAppFull(ProcessRecord app) {
         app.reqCompactAction = COMPACT_PROCESS_FULL;
         mPendingCompactionProcesses.add(app);
         mCompactionHandler.sendMessage(
@@ -137,97 +202,205 @@
                 COMPACT_PROCESS_MSG, app.curAdj, app.setProcState));
 
     }
-    final class MemCompactionHandler extends Handler {
-        AppCompactor mAc;
 
-        private MemCompactionHandler(AppCompactor ac) {
-            super(ac.mCompactionThread.getLooper());
-            mAc = ac;
+    /**
+     * Reads the flag value from DeviceConfig to determine whether app compaction
+     * should be enabled, and starts/stops the compaction thread as needed.
+     */
+    @GuardedBy("mPhenotypeFlagLock")
+    private void updateUseCompaction() {
+        String useCompactionFlag =
+                DeviceConfig.getProperty(DeviceConfig.ActivityManager.NAMESPACE,
+                    KEY_USE_COMPACTION);
+        mUseCompaction = TextUtils.isEmpty(useCompactionFlag)
+                ? DEFAULT_USE_COMPACTION : Boolean.parseBoolean(useCompactionFlag);
+        if (mUseCompaction && !mCompactionThread.isAlive()) {
+            mCompactionThread.start();
+            mCompactionHandler = new MemCompactionHandler();
+        }
+    }
+
+    @GuardedBy("mPhenotypeFlagLock")
+    private void updateCompactionActions() {
+        String compactAction1Flag =
+                DeviceConfig.getProperty(DeviceConfig.ActivityManager.NAMESPACE,
+                        KEY_COMPACT_ACTION_1);
+        String compactAction2Flag =
+                DeviceConfig.getProperty(DeviceConfig.ActivityManager.NAMESPACE,
+                        KEY_COMPACT_ACTION_2);
+
+        int compactAction1 = DEFAULT_COMPACT_ACTION_1;
+        try {
+            compactAction1 = TextUtils.isEmpty(compactAction1Flag)
+                    ? DEFAULT_COMPACT_ACTION_1 : Integer.parseInt(compactAction1Flag);
+        } catch (NumberFormatException e) {
+          // Do nothing, leave default.
+        }
+
+        int compactAction2 = DEFAULT_COMPACT_ACTION_2;
+        try {
+            compactAction2 = TextUtils.isEmpty(compactAction2Flag)
+                    ? DEFAULT_COMPACT_ACTION_2 : Integer.parseInt(compactAction2Flag);
+        } catch (NumberFormatException e) {
+            // Do nothing, leave default.
+        }
+
+        mCompactActionSome = compactActionIntToString(compactAction1);
+        mCompactActionFull = compactActionIntToString(compactAction2);
+    }
+
+    @GuardedBy("mPhenotypeFlagLock")
+    private void updateCompactionThrottles() {
+        boolean useThrottleDefaults = false;
+        String throttleSomeSomeFlag =
+                DeviceConfig.getProperty(DeviceConfig.ActivityManager.NAMESPACE,
+                    KEY_COMPACT_THROTTLE_1);
+        String throttleSomeFullFlag =
+                DeviceConfig.getProperty(DeviceConfig.ActivityManager.NAMESPACE,
+                    KEY_COMPACT_THROTTLE_2);
+        String throttleFullSomeFlag =
+                DeviceConfig.getProperty(DeviceConfig.ActivityManager.NAMESPACE,
+                    KEY_COMPACT_THROTTLE_3);
+        String throttleFullFullFlag =
+                DeviceConfig.getProperty(DeviceConfig.ActivityManager.NAMESPACE,
+                    KEY_COMPACT_THROTTLE_4);
+
+        if (TextUtils.isEmpty(throttleSomeSomeFlag) || TextUtils.isEmpty(throttleSomeFullFlag)
+                || TextUtils.isEmpty(throttleFullSomeFlag)
+                || TextUtils.isEmpty(throttleFullFullFlag)) {
+            // Set defaults for all if any are not set.
+            useThrottleDefaults = true;
+        } else {
+            try {
+                mCompactThrottleSomeSome = Integer.parseInt(throttleSomeSomeFlag);
+                mCompactThrottleSomeFull = Integer.parseInt(throttleSomeFullFlag);
+                mCompactThrottleFullSome = Integer.parseInt(throttleFullSomeFlag);
+                mCompactThrottleFullFull = Integer.parseInt(throttleFullFullFlag);
+            } catch (NumberFormatException e) {
+                useThrottleDefaults = true;
+            }
+        }
+
+        if (useThrottleDefaults) {
+            mCompactThrottleSomeSome = DEFAULT_COMPACT_THROTTLE_1;
+            mCompactThrottleSomeFull = DEFAULT_COMPACT_THROTTLE_2;
+            mCompactThrottleFullSome = DEFAULT_COMPACT_THROTTLE_3;
+            mCompactThrottleFullFull = DEFAULT_COMPACT_THROTTLE_4;
+        }
+    }
+
+    @VisibleForTesting
+    static String compactActionIntToString(int action) {
+        switch(action) {
+            case COMPACT_ACTION_FILE_FLAG:
+                return COMPACT_ACTION_FILE;
+            case COMPACT_ACTION_ANON_FLAG:
+                return COMPACT_ACTION_ANON;
+            case COMPACT_ACTION_FULL_FLAG:
+                return COMPACT_ACTION_FULL;
+            default:
+                return COMPACT_ACTION_FILE;
+        }
+    }
+
+    private final class MemCompactionHandler extends Handler {
+        private MemCompactionHandler() {
+            super(mCompactionThread.getLooper());
         }
 
         @Override
         public void handleMessage(Message msg) {
             switch (msg.what) {
-            case COMPACT_PROCESS_MSG: {
-                long start = SystemClock.uptimeMillis();
-                ProcessRecord proc;
-                int pid;
-                String action;
-                final String name;
-                int pendingAction, lastCompactAction;
-                long lastCompactTime;
-                synchronized(mAc.mAm) {
-                    proc = mAc.mPendingCompactionProcesses.remove(0);
+                case COMPACT_PROCESS_MSG: {
+                    long start = SystemClock.uptimeMillis();
+                    ProcessRecord proc;
+                    int pid;
+                    String action;
+                    final String name;
+                    int pendingAction, lastCompactAction;
+                    long lastCompactTime;
+                    synchronized (mAm) {
+                        proc = mPendingCompactionProcesses.remove(0);
 
-                    // don't compact if the process has returned to perceptible
-                    if (proc.setAdj <= ProcessList.PERCEPTIBLE_APP_ADJ) {
+                        // don't compact if the process has returned to perceptible
+                        if (proc.setAdj <= ProcessList.PERCEPTIBLE_APP_ADJ) {
+                            return;
+                        }
+
+                        pid = proc.pid;
+                        name = proc.processName;
+                        pendingAction = proc.reqCompactAction;
+                        lastCompactAction = proc.lastCompactAction;
+                        lastCompactTime = proc.lastCompactTime;
+                    }
+
+                    if (pid == 0) {
+                        // not a real process, either one being launched or one being killed
                         return;
                     }
 
-                    pid = proc.pid;
-                    name = proc.processName;
-                    pendingAction = proc.reqCompactAction;
-                    lastCompactAction = proc.lastCompactAction;
-                    lastCompactTime = proc.lastCompactTime;
-                }
-                if (pid == 0) {
-                    // not a real process, either one being launched or one being killed
-                    return;
-                }
+                    // basic throttling
+                    // use the Phenotype flag knobs to determine whether current/prevous
+                    // compaction combo should be throtted or not
 
-                // basic throttling
-                // use the ActivityManagerConstants knobs to determine whether current/prevous
-                // compaction combo should be throtted or not
-                if (pendingAction == COMPACT_PROCESS_SOME) {
-                    if ((lastCompactAction == COMPACT_PROCESS_SOME && (start - lastCompactTime < throttleSomeSome)) ||
-                        (lastCompactAction == COMPACT_PROCESS_FULL && (start - lastCompactTime < throttleSomeFull))) {
-                        return;
-                    }
-                } else {
-                    if ((lastCompactAction == COMPACT_PROCESS_SOME && (start - lastCompactTime < throttleFullSome)) ||
-                        (lastCompactAction == COMPACT_PROCESS_FULL && (start - lastCompactTime < throttleFullFull))) {
-                        return;
-                    }
-                }
-
-                try {
-                    Trace.traceBegin(Trace.TRACE_TAG_ACTIVITY_MANAGER, "Compact " +
-                            ((pendingAction == COMPACT_PROCESS_SOME) ? "some" : "full") +
-                            ": " + name);
-                    long[] rssBefore = Process.getRss(pid);
-                    FileOutputStream fos = new FileOutputStream("/proc/" + pid + "/reclaim");
+                    // Note that we explicitly don't take mPhenotypeFlagLock here as the flags
+                    // should very seldom change, and taking the risk of using the wrong action is
+                    // preferable to taking the lock for every single compaction action.
                     if (pendingAction == COMPACT_PROCESS_SOME) {
-                        action = compactActionSome;
+                        if ((lastCompactAction == COMPACT_PROCESS_SOME
+                                  && (start - lastCompactTime < mCompactThrottleSomeSome))
+                                  || (lastCompactAction == COMPACT_PROCESS_FULL
+                                      && (start - lastCompactTime
+                                          < mCompactThrottleSomeFull))) {
+                            return;
+                        }
                     } else {
-                        action = compactActionFull;
+                        if ((lastCompactAction == COMPACT_PROCESS_SOME
+                                  && (start - lastCompactTime < mCompactThrottleFullSome))
+                                  || (lastCompactAction == COMPACT_PROCESS_FULL
+                                      && (start - lastCompactTime
+                                          < mCompactThrottleFullFull))) {
+                            return;
+                        }
                     }
-                    fos.write(action.getBytes());
-                    fos.close();
-                    long[] rssAfter = Process.getRss(pid);
-                    long end = SystemClock.uptimeMillis();
-                    long time = end - start;
-                    EventLog.writeEvent(EventLogTags.AM_COMPACT, pid, name, action,
-                            rssBefore[0], rssBefore[1], rssBefore[2], rssBefore[3],
-                            rssAfter[0], rssAfter[1], rssAfter[2], rssAfter[3], time,
-                            lastCompactAction, lastCompactTime, msg.arg1, msg.arg2);
-                    StatsLog.write(StatsLog.APP_COMPACTED, pid, name, pendingAction,
-                            rssBefore[0], rssBefore[1], rssBefore[2], rssBefore[3],
-                            rssAfter[0], rssAfter[1], rssAfter[2], rssAfter[3], time,
-                            lastCompactAction, lastCompactTime, msg.arg1,
-                            ActivityManager.processStateAmToProto(msg.arg2));
-                    synchronized(mAc.mAm) {
-                        proc.lastCompactTime = end;
-                        proc.lastCompactAction = pendingAction;
+
+                    if (pendingAction == COMPACT_PROCESS_SOME) {
+                        action = mCompactActionSome;
+                    } else {
+                        action = mCompactActionFull;
                     }
-                    Trace.traceEnd(Trace.TRACE_TAG_ACTIVITY_MANAGER);
-                } catch (Exception e) {
-                    // nothing to do, presumably the process died
-                    Trace.traceEnd(Trace.TRACE_TAG_ACTIVITY_MANAGER);
+
+                    try {
+                        Trace.traceBegin(Trace.TRACE_TAG_ACTIVITY_MANAGER, "Compact "
+                                + ((pendingAction == COMPACT_PROCESS_SOME) ? "some" : "full")
+                                + ": " + name);
+                        long[] rssBefore = Process.getRss(pid);
+                        FileOutputStream fos = new FileOutputStream("/proc/" + pid + "/reclaim");
+                        fos.write(action.getBytes());
+                        fos.close();
+                        long[] rssAfter = Process.getRss(pid);
+                        long end = SystemClock.uptimeMillis();
+                        long time = end - start;
+                        EventLog.writeEvent(EventLogTags.AM_COMPACT, pid, name, action,
+                                rssBefore[0], rssBefore[1], rssBefore[2], rssBefore[3],
+                                rssAfter[0], rssAfter[1], rssAfter[2], rssAfter[3], time,
+                                lastCompactAction, lastCompactTime, msg.arg1, msg.arg2);
+                        StatsLog.write(StatsLog.APP_COMPACTED, pid, name, pendingAction,
+                                rssBefore[0], rssBefore[1], rssBefore[2], rssBefore[3],
+                                rssAfter[0], rssAfter[1], rssAfter[2], rssAfter[3], time,
+                                lastCompactAction, lastCompactTime, msg.arg1,
+                                ActivityManager.processStateAmToProto(msg.arg2));
+                        synchronized (mAm) {
+                            proc.lastCompactTime = end;
+                            proc.lastCompactAction = pendingAction;
+                        }
+                        Trace.traceEnd(Trace.TRACE_TAG_ACTIVITY_MANAGER);
+                    } catch (Exception e) {
+                        // nothing to do, presumably the process died
+                        Trace.traceEnd(Trace.TRACE_TAG_ACTIVITY_MANAGER);
+                    }
                 }
             }
-            }
         }
     }
-
-
 }
diff --git a/services/core/java/com/android/server/am/BaseErrorDialog.java b/services/core/java/com/android/server/am/BaseErrorDialog.java
index aabb587..dc9a4bf 100644
--- a/services/core/java/com/android/server/am/BaseErrorDialog.java
+++ b/services/core/java/com/android/server/am/BaseErrorDialog.java
@@ -16,8 +16,6 @@
 
 package com.android.server.am;
 
-import com.android.internal.R;
-
 import android.app.AlertDialog;
 import android.content.Context;
 import android.os.Handler;
@@ -26,6 +24,8 @@
 import android.view.WindowManager;
 import android.widget.Button;
 
+import com.android.internal.R;
+
 public class BaseErrorDialog extends AlertDialog {
     private static final int ENABLE_BUTTONS = 0;
     private static final int DISABLE_BUTTONS = 1;
@@ -36,7 +36,7 @@
         super(context, com.android.internal.R.style.Theme_DeviceDefault_Dialog_AppError);
         context.assertRuntimeOverlayThemable();
 
-        getWindow().setType(WindowManager.LayoutParams.TYPE_SYSTEM_ALERT);
+        getWindow().setType(WindowManager.LayoutParams.TYPE_APPLICATION_OVERLAY);
         getWindow().setFlags(WindowManager.LayoutParams.FLAG_ALT_FOCUSABLE_IM,
                 WindowManager.LayoutParams.FLAG_ALT_FOCUSABLE_IM);
         WindowManager.LayoutParams attrs = getWindow().getAttributes();
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/BroadcastQueue.java b/services/core/java/com/android/server/am/BroadcastQueue.java
index 353749f..cdf6e0e 100644
--- a/services/core/java/com/android/server/am/BroadcastQueue.java
+++ b/services/core/java/com/android/server/am/BroadcastQueue.java
@@ -776,6 +776,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));
diff --git a/services/core/java/com/android/server/am/CoreSettingsObserver.java b/services/core/java/com/android/server/am/CoreSettingsObserver.java
index d3953b5..3d69aa8 100644
--- a/services/core/java/com/android/server/am/CoreSettingsObserver.java
+++ b/services/core/java/com/android/server/am/CoreSettingsObserver.java
@@ -61,6 +61,8 @@
                 Settings.Global.GLOBAL_SETTINGS_ANGLE_GL_DRIVER_SELECTION_PKGS, String.class);
         sGlobalSettingToTypeMap.put(
                 Settings.Global.GLOBAL_SETTINGS_ANGLE_GL_DRIVER_SELECTION_VALUES, String.class);
+        sGlobalSettingToTypeMap.put(
+                Settings.Global.GLOBAL_SETTINGS_ANGLE_WHITELIST, String.class);
         sGlobalSettingToTypeMap.put(Settings.Global.ENABLE_GPU_DEBUG_LAYERS, int.class);
         sGlobalSettingToTypeMap.put(Settings.Global.GPU_DEBUG_APP, String.class);
         sGlobalSettingToTypeMap.put(Settings.Global.GPU_DEBUG_LAYERS, String.class);
diff --git a/services/core/java/com/android/server/am/OomAdjuster.java b/services/core/java/com/android/server/am/OomAdjuster.java
index be910d4..1e03f6c 100644
--- a/services/core/java/com/android/server/am/OomAdjuster.java
+++ b/services/core/java/com/android/server/am/OomAdjuster.java
@@ -134,10 +134,11 @@
 
         mLocalPowerManager = LocalServices.getService(PowerManagerInternal.class);
         mConstants = mService.mConstants;
-        // mConstants can be null under test, which causes AppCompactor to crash
-        if (mConstants != null) {
-            mAppCompact = new AppCompactor(mService);
-        }
+        mAppCompact = new AppCompactor(mService);
+    }
+
+    void initSettings() {
+        mAppCompact.init();
     }
 
     /**
@@ -1679,7 +1680,7 @@
 
         if (app.curAdj != app.setAdj) {
             // don't compact during bootup
-            if (mConstants.USE_COMPACTION && mService.mBooted) {
+            if (mAppCompact.useCompaction() && mService.mBooted) {
                 // Perform a minor compaction when a perceptible app becomes the prev/home app
                 // Perform a major compaction when any app enters cached
                 // reminder: here, setAdj is previous state, curAdj is upcoming state
@@ -2104,4 +2105,8 @@
                 + " mNewNumServiceProcs=" + mNewNumServiceProcs);
     }
 
+    @GuardedBy("mService")
+    void dumpAppCompactorSettings(PrintWriter pw) {
+        mAppCompact.dump(pw);
+    }
 }
diff --git a/services/core/java/com/android/server/am/PendingIntentRecord.java b/services/core/java/com/android/server/am/PendingIntentRecord.java
index 55cca95..0b27a8a 100644
--- a/services/core/java/com/android/server/am/PendingIntentRecord.java
+++ b/services/core/java/com/android/server/am/PendingIntentRecord.java
@@ -379,6 +379,10 @@
             if (userId == UserHandle.USER_CURRENT) {
                 userId = controller.mUserController.getCurrentOrTargetUserId();
             }
+            // temporarily allow receivers and services to open activities from background if the
+            // PendingIntent.send() caller was foreground at the time of sendInner() call
+            final boolean allowTrampoline = uid != callingUid
+                    && controller.mAtmInternal.isUidForeground(callingUid);
 
             switch (key.type) {
                 case ActivityManager.INTENT_SENDER_ACTIVITY:
@@ -419,7 +423,8 @@
                                 uid, finalIntent, resolvedType, finishedReceiver, code, null, null,
                                 requiredPermission, options, (finishedReceiver != null),
                                 false, userId,
-                                mAllowBgActivityStartsForBroadcastSender.contains(whitelistToken));
+                                mAllowBgActivityStartsForBroadcastSender.contains(whitelistToken)
+                                || allowTrampoline);
                         if (sent == ActivityManager.BROADCAST_SUCCESS) {
                             sendFinish = false;
                         }
@@ -433,7 +438,8 @@
                         controller.mAmInternal.startServiceInPackage(uid, finalIntent, resolvedType,
                                 key.type == ActivityManager.INTENT_SENDER_FOREGROUND_SERVICE,
                                 key.packageName, userId,
-                                mAllowBgActivityStartsForServiceSender.contains(whitelistToken));
+                                mAllowBgActivityStartsForServiceSender.contains(whitelistToken)
+                                || allowTrampoline);
                     } catch (RuntimeException e) {
                         Slog.w(TAG, "Unable to send startService intent", e);
                     } catch (TransactionTooLargeException e) {
diff --git a/services/core/java/com/android/server/am/ProcessList.java b/services/core/java/com/android/server/am/ProcessList.java
index c1be387..003ddd1 100644
--- a/services/core/java/com/android/server/am/ProcessList.java
+++ b/services/core/java/com/android/server/am/ProcessList.java
@@ -145,6 +145,11 @@
     static final int CACHED_APP_MAX_ADJ = 999;
     static final int CACHED_APP_MIN_ADJ = 900;
 
+    // This is the oom_adj level that we allow to die first. This cannot be equal to
+    // CACHED_APP_MAX_ADJ unless processes are actively being assigned an oom_score_adj of
+    // CACHED_APP_MAX_ADJ.
+    static final int CACHED_APP_LMK_FIRST_ADJ = 950;
+
     // Number of levels we have available for different service connection group importance
     // levels.
     static final int CACHED_APP_IMPORTANCE_LEVELS = 5;
@@ -266,7 +271,7 @@
     // can't give it a different value for every possible kind of process.
     private final int[] mOomAdj = new int[] {
             FOREGROUND_APP_ADJ, VISIBLE_APP_ADJ, PERCEPTIBLE_APP_ADJ,
-            BACKUP_APP_ADJ, CACHED_APP_MIN_ADJ, CACHED_APP_MAX_ADJ
+            BACKUP_APP_ADJ, CACHED_APP_MIN_ADJ, CACHED_APP_LMK_FIRST_ADJ
     };
     // These are the low-end OOM level limits.  This is appropriate for an
     // HVGA or smaller phone with less than 512MB.  Values are in KB.
@@ -1497,7 +1502,7 @@
                 mService.mNativeDebuggingApp = null;
             }
 
-            if ((app.info.privateFlags & ApplicationInfo.PRIVATE_FLAG_PREFER_CODE_INTEGRITY) != 0
+            if (app.info.isCodeIntegrityPreferred()
                     || (app.info.isPrivilegedApp()
                         && DexManager.isPackageSelectedToRunOob(app.pkgList.mPkgList.keySet()))) {
                 runtimeFlags |= Zygote.ONLY_USE_SYSTEM_OAT_FILES;
@@ -1730,7 +1735,7 @@
                         app.processName, uid, uid, gids, runtimeFlags, mountExternal,
                         app.info.targetSdkVersion, seInfo, requiredAbi, instructionSet,
                         app.info.dataDir, null, app.info.packageName,
-                        packageNames, visibleVolIds,
+                        packageNames, visibleVolIds, /*useBlastulaPool=*/ false,
                         new String[] {PROC_START_SEQ_IDENT + app.startSeq});
             } else {
                 startResult = Process.start(entryPoint,
@@ -2379,14 +2384,14 @@
     }
 
     @GuardedBy("mService")
-    void setAllHttpProxyLocked(String host, String port, String exclList, Uri pacFileUrl) {
+    void setAllHttpProxyLocked() {
         for (int i = mLruProcesses.size() - 1; i >= 0; i--) {
             ProcessRecord r = mLruProcesses.get(i);
             // Don't dispatch to isolated processes as they can't access
             // ConnectivityManager and don't have network privileges anyway.
             if (r.thread != null && !r.isolated) {
                 try {
-                    r.thread.setHttpProxy(host, port, exclList, pacFileUrl);
+                    r.thread.updateHttpProxy();
                 } catch (RemoteException ex) {
                     Slog.w(TAG, "Failed to update http proxy for: " +
                             r.info.processName);
diff --git a/services/core/java/com/android/server/appop/AppOpsService.java b/services/core/java/com/android/server/appop/AppOpsService.java
index 7ede6dc..26d2d17 100644
--- a/services/core/java/com/android/server/appop/AppOpsService.java
+++ b/services/core/java/com/android/server/appop/AppOpsService.java
@@ -16,8 +16,8 @@
 
 package com.android.server.appop;
 
-import static android.app.AppOpsManager.OP_PLAY_AUDIO;
 import static android.app.AppOpsManager.OP_NONE;
+import static android.app.AppOpsManager.OP_PLAY_AUDIO;
 import static android.app.AppOpsManager.UID_STATE_BACKGROUND;
 import static android.app.AppOpsManager.UID_STATE_CACHED;
 import static android.app.AppOpsManager.UID_STATE_FOREGROUND;
@@ -94,9 +94,9 @@
 import com.android.internal.util.Preconditions;
 import com.android.internal.util.XmlUtils;
 import com.android.internal.util.function.pooled.PooledLambda;
-
 import com.android.server.LocalServices;
 import com.android.server.LockGuard;
+
 import libcore.util.EmptyArray;
 
 import org.xmlpull.v1.XmlPullParser;
@@ -1282,6 +1282,46 @@
         }
     }
 
+    /**
+     * Set all {@link #setMode (package) modes} for this uid to the default value.
+     *
+     * @param code The app-op
+     * @param uid The uid
+     */
+    private void setAllPkgModesToDefault(int code, int uid) {
+        synchronized (this) {
+            UidState uidState = getUidStateLocked(uid, false);
+            if (uidState == null) {
+                return;
+            }
+
+            ArrayMap<String, Ops> pkgOps = uidState.pkgOps;
+            if (pkgOps == null) {
+                return;
+            }
+
+            int numPkgs = pkgOps.size();
+            for (int pkgNum = 0; pkgNum < numPkgs; pkgNum++) {
+                Ops ops = pkgOps.valueAt(pkgNum);
+
+                Op op = ops.get(code);
+                if (op == null) {
+                    continue;
+                }
+
+                int defaultMode = AppOpsManager.opToDefaultMode(code);
+                if (op.mode != defaultMode) {
+                    Slog.w(TAG, "resetting app-op mode for " + AppOpsManager.opToName(code) + " of "
+                            + pkgOps.keyAt(pkgNum));
+
+                    op.mode = defaultMode;
+
+                    scheduleWriteLocked();
+                }
+            }
+        }
+    }
+
     @Override
     public void setMode(int code, int uid, String packageName, int mode) {
         setMode(code, uid, packageName, mode, true, false);
@@ -4387,5 +4427,10 @@
         public void setUidMode(int code, int uid, int mode) {
             AppOpsService.this.setUidMode(code, uid, mode);
         }
+
+        @Override
+        public void setAllPkgModesToDefault(int code, int uid) {
+            AppOpsService.this.setAllPkgModesToDefault(code, uid);
+        }
     }
 }
diff --git a/services/core/java/com/android/server/attention/AttentionManagerService.java b/services/core/java/com/android/server/attention/AttentionManagerService.java
index 27edbbf..b71a751 100644
--- a/services/core/java/com/android/server/attention/AttentionManagerService.java
+++ b/services/core/java/com/android/server/attention/AttentionManagerService.java
@@ -174,10 +174,11 @@
                         @Override
                         public void onSuccess(int requestCode, int result, long timestamp) {
                             callback.onSuccess(requestCode, result, timestamp);
-                            userState.mAttentionCheckCache = new AttentionCheckCache(
-                                    SystemClock.uptimeMillis(), result,
-                                    timestamp);
-
+                            synchronized (mLock) {
+                                userState.mAttentionCheckCache = new AttentionCheckCache(
+                                        SystemClock.uptimeMillis(), result,
+                                        timestamp);
+                            }
                             StatsLog.write(StatsLog.ATTENTION_MANAGER_SERVICE_RESULT_REPORTED,
                                     result);
                         }
diff --git a/services/core/java/com/android/server/audio/AudioDeviceBroker.java b/services/core/java/com/android/server/audio/AudioDeviceBroker.java
new file mode 100644
index 0000000..d652f93
--- /dev/null
+++ b/services/core/java/com/android/server/audio/AudioDeviceBroker.java
@@ -0,0 +1,807 @@
+/*
+ * Copyright 2019 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.android.server.audio;
+
+import android.annotation.NonNull;
+import android.bluetooth.BluetoothA2dp;
+import android.bluetooth.BluetoothDevice;
+import android.bluetooth.BluetoothProfile;
+import android.content.ContentResolver;
+import android.content.Context;
+import android.content.Intent;
+import android.media.AudioManager;
+import android.media.AudioRoutesInfo;
+import android.media.AudioSystem;
+import android.media.IAudioRoutesObserver;
+import android.os.Binder;
+import android.os.Handler;
+import android.os.IBinder;
+import android.os.Looper;
+import android.os.Message;
+import android.os.PowerManager;
+import android.os.SystemClock;
+import android.os.UserHandle;
+import android.util.Log;
+
+import com.android.internal.annotations.GuardedBy;
+
+import java.util.ArrayList;
+
+/** @hide */
+/*package*/ final class AudioDeviceBroker {
+
+    private static final String TAG = "AudioDeviceBroker";
+
+    private static final long BROKER_WAKELOCK_TIMEOUT_MS = 5000; //5s
+
+    /*package*/ static final  int BTA2DP_DOCK_TIMEOUT_MS = 8000;
+    // Timeout for connection to bluetooth headset service
+    /*package*/ static final int BT_HEADSET_CNCT_TIMEOUT_MS = 3000;
+
+    private final @NonNull AudioService mAudioService;
+    private final @NonNull Context mContext;
+
+    /** Forced device usage for communications sent to AudioSystem */
+    private int mForcedUseForComm;
+    /**
+     * Externally reported force device usage state returned by getters: always consistent
+     * with requests by setters */
+    private int mForcedUseForCommExt;
+
+    // Manages all connected devices, only ever accessed on the message loop
+    //### or make it synchronized
+    private final AudioDeviceInventory mDeviceInventory;
+    // Manages notifications to BT service
+    private final BtHelper mBtHelper;
+
+
+    //-------------------------------------------------------------------
+    private static final Object sLastDeviceConnectionMsgTimeLock = new Object();
+    private static long sLastDeviceConnectMsgTime = 0;
+
+    private final Object mBluetoothA2dpEnabledLock = new Object();
+    // Request to override default use of A2DP for media.
+    @GuardedBy("mBluetoothA2dpEnabledLock")
+    private boolean mBluetoothA2dpEnabled;
+
+    // lock always taken synchronized on mConnectedDevices
+    /*package*/  final Object mA2dpAvrcpLock = new Object();
+    // lock always taken synchronized on mConnectedDevices
+    /*package*/  final Object mHearingAidLock = new Object();
+
+    // lock always taken when accessing AudioService.mSetModeDeathHandlers
+    /*package*/ final Object mSetModeLock = new Object();
+
+    //-------------------------------------------------------------------
+    /*package*/ AudioDeviceBroker(@NonNull Context context, @NonNull AudioService service) {
+        mContext = context;
+        mAudioService = service;
+        setupMessaging(context);
+        mBtHelper = new BtHelper(this);
+        mDeviceInventory = new AudioDeviceInventory(this);
+
+        mForcedUseForComm = AudioSystem.FORCE_NONE;
+        mForcedUseForCommExt = mForcedUseForComm;
+
+    }
+
+    /*package*/ Context getContext() {
+        return mContext;
+    }
+
+    //---------------------------------------------------------------------
+    // Communication from AudioService
+    // All methods are asynchronous and never block
+    // All permission checks are done in AudioService, all incoming calls are considered "safe"
+    // All post* methods are asynchronous
+
+    /*package*/ void onSystemReady() {
+        mBtHelper.onSystemReady();
+    }
+
+    /*package*/ void onAudioServerDied() {
+        // Restore forced usage for communications and record
+        onSetForceUse(AudioSystem.FOR_COMMUNICATION, mForcedUseForComm, "onAudioServerDied");
+        onSetForceUse(AudioSystem.FOR_RECORD, mForcedUseForComm, "onAudioServerDied");
+        // restore devices
+        sendMsgNoDelay(MSG_RESTORE_DEVICES, SENDMSG_REPLACE);
+    }
+
+    /*package*/ void setForceUse_Async(int useCase, int config, String eventSource) {
+        sendIILMsgNoDelay(MSG_IIL_SET_FORCE_USE, SENDMSG_QUEUE,
+                useCase, config, eventSource);
+    }
+
+    /*package*/ void toggleHdmiIfConnected_Async() {
+        sendMsgNoDelay(MSG_TOGGLE_HDMI, SENDMSG_QUEUE);
+    }
+
+    /*package*/ void disconnectAllBluetoothProfiles() {
+        mBtHelper.disconnectAllBluetoothProfiles();
+    }
+
+    /**
+     * Handle BluetoothHeadset intents where the action is one of
+     *   {@link BluetoothHeadset#ACTION_ACTIVE_DEVICE_CHANGED} or
+     *   {@link BluetoothHeadset#ACTION_AUDIO_STATE_CHANGED}.
+     * @param intent
+     */
+    /*package*/ void receiveBtEvent(@NonNull Intent intent) {
+        mBtHelper.receiveBtEvent(intent);
+    }
+
+    /*package*/ void setBluetoothA2dpOn_Async(boolean on, String source) {
+        synchronized (mBluetoothA2dpEnabledLock) {
+            if (mBluetoothA2dpEnabled == on) {
+                return;
+            }
+            mBluetoothA2dpEnabled = on;
+            mBrokerHandler.removeMessages(MSG_IIL_SET_FORCE_BT_A2DP_USE);
+            sendIILMsgNoDelay(MSG_IIL_SET_FORCE_BT_A2DP_USE, SENDMSG_QUEUE,
+                    AudioSystem.FOR_MEDIA,
+                    mBluetoothA2dpEnabled ? AudioSystem.FORCE_NONE : AudioSystem.FORCE_NO_BT_A2DP,
+                    source);
+        }
+    }
+
+    /*package*/ void setSpeakerphoneOn(boolean on, String eventSource) {
+        if (on) {
+            if (mForcedUseForComm == AudioSystem.FORCE_BT_SCO) {
+                setForceUse_Async(AudioSystem.FOR_RECORD, AudioSystem.FORCE_NONE, eventSource);
+            }
+            mForcedUseForComm = AudioSystem.FORCE_SPEAKER;
+        } else if (mForcedUseForComm == AudioSystem.FORCE_SPEAKER) {
+            mForcedUseForComm = AudioSystem.FORCE_NONE;
+        }
+
+        mForcedUseForCommExt = mForcedUseForComm;
+        setForceUse_Async(AudioSystem.FOR_COMMUNICATION, mForcedUseForComm, eventSource);
+    }
+
+    /*package*/ boolean isSpeakerphoneOn() {
+        return (mForcedUseForCommExt == AudioSystem.FORCE_SPEAKER);
+    }
+
+    /*package*/ void setWiredDeviceConnectionState(int type,
+            @AudioService.ConnectionState int state, String address, String name,
+            String caller) {
+        //TODO move logging here just like in setBluetooth* methods
+        mDeviceInventory.setWiredDeviceConnectionState(type, state, address, name, caller);
+    }
+
+    /*package*/ int setBluetoothA2dpDeviceConnectionStateSuppressNoisyIntent(
+            @NonNull BluetoothDevice device, @AudioService.BtProfileConnectionState int state,
+            int profile, boolean suppressNoisyIntent, int a2dpVolume) {
+        AudioService.sDeviceLogger.log((new AudioEventLogger.StringEvent(
+                "setBluetoothA2dpDeviceConnectionStateSuppressNoisyIntent state=" + state
+                        // only querying address as this is the only readily available field
+                        // on the device
+                        + " addr=" + device.getAddress()
+                        + " prof=" + profile + " supprNoisy=" + suppressNoisyIntent
+                        + " vol=" + a2dpVolume)).printLog(TAG));
+        if (mBrokerHandler.hasMessages(MSG_IL_SET_A2DP_SINK_CONNECTION_STATE,
+                new BtHelper.BluetoothA2dpDeviceInfo(device))) {
+            AudioService.sDeviceLogger.log(new AudioEventLogger.StringEvent(
+                    "A2DP connection state ignored"));
+            return 0;
+        }
+        return mDeviceInventory.setBluetoothA2dpDeviceConnectionState(
+                device, state, profile, suppressNoisyIntent, AudioSystem.DEVICE_NONE, a2dpVolume);
+    }
+
+    /*package*/ int handleBluetoothA2dpActiveDeviceChange(
+            @NonNull BluetoothDevice device,
+            @AudioService.BtProfileConnectionState int state, int profile,
+            boolean suppressNoisyIntent, int a2dpVolume) {
+        return mDeviceInventory.handleBluetoothA2dpActiveDeviceChange(device, state, profile,
+                suppressNoisyIntent, a2dpVolume);
+    }
+
+    /*package*/ int setBluetoothHearingAidDeviceConnectionState(
+            @NonNull BluetoothDevice device, @AudioService.BtProfileConnectionState int state,
+            boolean suppressNoisyIntent, int musicDevice, @NonNull String eventSource) {
+        AudioService.sDeviceLogger.log((new AudioEventLogger.StringEvent(
+                "setHearingAidDeviceConnectionState state=" + state
+                        + " addr=" + device.getAddress()
+                        + " supprNoisy=" + suppressNoisyIntent
+                        + " src=" + eventSource)).printLog(TAG));
+        return mDeviceInventory.setBluetoothHearingAidDeviceConnectionState(
+                device, state, suppressNoisyIntent, musicDevice);
+    }
+
+    // never called by system components
+    /*package*/ void setBluetoothScoOnByApp(boolean on) {
+        mForcedUseForCommExt = on ? AudioSystem.FORCE_BT_SCO : AudioSystem.FORCE_NONE;
+    }
+
+    /*package*/ boolean isBluetoothScoOnForApp() {
+        return mForcedUseForCommExt == AudioSystem.FORCE_BT_SCO;
+    }
+
+    /*package*/ void setBluetoothScoOn(boolean on, String eventSource) {
+        //Log.i(TAG, "setBluetoothScoOnInt: " + on + " " + eventSource);
+        if (on) {
+            // do not accept SCO ON if SCO audio is not connected
+            if (!mBtHelper.isBluetoothScoOn()) {
+                mForcedUseForCommExt = AudioSystem.FORCE_BT_SCO;
+                return;
+            }
+            mForcedUseForComm = AudioSystem.FORCE_BT_SCO;
+        } else if (mForcedUseForComm == AudioSystem.FORCE_BT_SCO) {
+            mForcedUseForComm = AudioSystem.FORCE_NONE;
+        }
+        mForcedUseForCommExt = mForcedUseForComm;
+        AudioSystem.setParameters("BT_SCO=" + (on ? "on" : "off"));
+        sendIILMsgNoDelay(MSG_IIL_SET_FORCE_USE, SENDMSG_QUEUE,
+                AudioSystem.FOR_COMMUNICATION, mForcedUseForComm, eventSource);
+        sendIILMsgNoDelay(MSG_IIL_SET_FORCE_USE, SENDMSG_QUEUE,
+                AudioSystem.FOR_RECORD, mForcedUseForComm, eventSource);
+        // Un-mute ringtone stream volume
+        mAudioService.setUpdateRingerModeServiceInt();
+    }
+
+    /*package*/ AudioRoutesInfo startWatchingRoutes(IAudioRoutesObserver observer) {
+        return mDeviceInventory.startWatchingRoutes(observer);
+    }
+
+    /*package*/ AudioRoutesInfo getCurAudioRoutes() {
+        return mDeviceInventory.getCurAudioRoutes();
+    }
+
+    /*package*/ boolean isAvrcpAbsoluteVolumeSupported() {
+        synchronized (mA2dpAvrcpLock) {
+            return mBtHelper.isAvrcpAbsoluteVolumeSupported();
+        }
+    }
+
+    /*package*/ boolean isBluetoothA2dpOn() {
+        synchronized (mBluetoothA2dpEnabledLock) {
+            return mBluetoothA2dpEnabled;
+        }
+    }
+
+    /*package*/ void postSetAvrcpAbsoluteVolumeIndex(int index) {
+        sendIMsgNoDelay(MSG_I_SET_AVRCP_ABSOLUTE_VOLUME, SENDMSG_REPLACE, index);
+    }
+
+    /*package*/ void postSetHearingAidVolumeIndex(int index, int streamType) {
+        sendIIMsgNoDelay(MSG_II_SET_HEARING_AID_VOLUME, SENDMSG_REPLACE, index, streamType);
+    }
+
+    /*package*/ void postDisconnectBluetoothSco(int exceptPid) {
+        sendIMsgNoDelay(MSG_I_DISCONNECT_BT_SCO, SENDMSG_REPLACE, exceptPid);
+    }
+
+    /*package*/ void postBluetoothA2dpDeviceConfigChange(@NonNull BluetoothDevice device) {
+        sendLMsgNoDelay(MSG_L_A2DP_DEVICE_CONFIG_CHANGE, SENDMSG_QUEUE, device);
+    }
+
+    /*package*/ void startBluetoothScoForClient_Sync(IBinder cb, int scoAudioMode,
+                @NonNull String eventSource) {
+        mBtHelper.startBluetoothScoForClient(cb, scoAudioMode, eventSource);
+    }
+
+    /*package*/ void stopBluetoothScoForClient_Sync(IBinder cb, @NonNull String eventSource) {
+        mBtHelper.stopBluetoothScoForClient(cb, eventSource);
+    }
+
+    //---------------------------------------------------------------------
+    // Communication with (to) AudioService
+    //TODO check whether the AudioService methods are candidates to move here
+    /*package*/ void postAccessoryPlugMediaUnmute(int device) {
+        mAudioService.postAccessoryPlugMediaUnmute(device);
+    }
+
+    /*package*/ AudioService.VolumeStreamState getStreamState(int streamType) {
+        return mAudioService.getStreamState(streamType);
+    }
+
+    /*package*/ ArrayList<AudioService.SetModeDeathHandler> getSetModeDeathHandlers() {
+        return mAudioService.mSetModeDeathHandlers;
+    }
+
+    /*package*/ int getDeviceForStream(int streamType) {
+        return mAudioService.getDeviceForStream(streamType);
+    }
+
+    /*package*/ void setDeviceVolume(AudioService.VolumeStreamState streamState, int device) {
+        mAudioService.setDeviceVolume(streamState, device);
+    }
+
+    /*packages*/ void observeDevicesForAllStreams() {
+        mAudioService.observeDevicesForAllStreams();
+    }
+
+    /*package*/ boolean isInCommunication() {
+        return mAudioService.isInCommunication();
+    }
+
+    /*package*/ boolean hasMediaDynamicPolicy() {
+        return mAudioService.hasMediaDynamicPolicy();
+    }
+
+    /*package*/ ContentResolver getContentResolver() {
+        return mAudioService.getContentResolver();
+    }
+
+    /*package*/ void checkMusicActive(int deviceType, String caller) {
+        mAudioService.checkMusicActive(deviceType, caller);
+    }
+
+    /*package*/ void checkVolumeCecOnHdmiConnection(int state, String caller) {
+        mAudioService.checkVolumeCecOnHdmiConnection(state, caller);
+    }
+
+    //---------------------------------------------------------------------
+    // Message handling on behalf of helper classes
+    /*package*/ void broadcastScoConnectionState(int state) {
+        sendIMsgNoDelay(MSG_I_BROADCAST_BT_CONNECTION_STATE, SENDMSG_QUEUE, state);
+    }
+
+    /*package*/ void broadcastBecomingNoisy() {
+        sendMsgNoDelay(MSG_BROADCAST_AUDIO_BECOMING_NOISY, SENDMSG_REPLACE);
+    }
+
+    //###TODO unify with handleSetA2dpSinkConnectionState
+    /*package*/ void postA2dpSinkConnection(int state,
+            @NonNull BtHelper.BluetoothA2dpDeviceInfo btDeviceInfo, int delay) {
+        sendILMsg(MSG_IL_SET_A2DP_SINK_CONNECTION_STATE, SENDMSG_QUEUE,
+                state, btDeviceInfo, delay);
+    }
+
+    //###TODO unify with handleSetA2dpSourceConnectionState
+    /*package*/ void postA2dpSourceConnection(int state,
+            @NonNull BtHelper.BluetoothA2dpDeviceInfo btDeviceInfo, int delay) {
+        sendILMsg(MSG_IL_SET_A2DP_SOURCE_CONNECTION_STATE, SENDMSG_QUEUE,
+                state, btDeviceInfo, delay);
+    }
+
+    /*package*/ void postSetWiredDeviceConnectionState(
+            AudioDeviceInventory.WiredDeviceConnectionState connectionState, int delay) {
+        sendLMsg(MSG_L_SET_WIRED_DEVICE_CONNECTION_STATE, SENDMSG_QUEUE, connectionState, delay);
+    }
+
+    /*package*/ void postSetHearingAidConnectionState(
+            @AudioService.BtProfileConnectionState int state,
+            @NonNull BluetoothDevice device, int delay) {
+        sendILMsg(MSG_IL_SET_HEARING_AID_CONNECTION_STATE, SENDMSG_QUEUE,
+                state,
+                device,
+                delay);
+    }
+
+    //---------------------------------------------------------------------
+    // Method forwarding between the helper classes (BtHelper, AudioDeviceInventory)
+    // only call from a "handle"* method or "on"* method
+
+    // Handles request to override default use of A2DP for media.
+    //@GuardedBy("mConnectedDevices")
+    /*package*/ void setBluetoothA2dpOnInt(boolean on, String source) {
+        // for logging only
+        final String eventSource = new StringBuilder("setBluetoothA2dpOn(").append(on)
+                .append(") from u/pid:").append(Binder.getCallingUid()).append("/")
+                .append(Binder.getCallingPid()).append(" src:").append(source).toString();
+
+        synchronized (mBluetoothA2dpEnabledLock) {
+            mBluetoothA2dpEnabled = on;
+            mBrokerHandler.removeMessages(MSG_IIL_SET_FORCE_BT_A2DP_USE);
+            onSetForceUse(
+                    AudioSystem.FOR_MEDIA,
+                    mBluetoothA2dpEnabled ? AudioSystem.FORCE_NONE : AudioSystem.FORCE_NO_BT_A2DP,
+                    eventSource);
+        }
+    }
+
+    /*package*/ boolean handleDeviceConnection(boolean connect, int device, String address,
+                                                       String deviceName) {
+        return mDeviceInventory.handleDeviceConnection(connect, device, address, deviceName);
+    }
+
+    /*package*/ void handleDisconnectA2dp() {
+        mDeviceInventory.disconnectA2dp();
+    }
+    /*package*/ void handleDisconnectA2dpSink() {
+        mDeviceInventory.disconnectA2dpSink();
+    }
+
+    /*package*/ void handleSetA2dpSinkConnectionState(@BluetoothProfile.BtProfileState int state,
+                @NonNull BtHelper.BluetoothA2dpDeviceInfo btDeviceInfo) {
+        final int intState = (state == BluetoothA2dp.STATE_CONNECTED) ? 1 : 0;
+        //### DOESN'T HONOR SYNC ON DEVICES -> make a synchronized version?
+        // might be ok here because called on BT thread? + sync happening in
+        //  checkSendBecomingNoisyIntent
+        final int delay = mDeviceInventory.checkSendBecomingNoisyIntent(
+                AudioSystem.DEVICE_OUT_BLUETOOTH_A2DP, intState,
+                AudioSystem.DEVICE_NONE);
+        final String addr = btDeviceInfo == null ? "null" : btDeviceInfo.getBtDevice().getAddress();
+
+        if (AudioService.DEBUG_DEVICES) {
+            Log.d(TAG, "handleSetA2dpSinkConnectionState btDevice= " + btDeviceInfo
+                    + " state= " + state
+                    + " is dock: " + btDeviceInfo.getBtDevice().isBluetoothDock());
+        }
+        sendILMsg(MSG_IL_SET_A2DP_SINK_CONNECTION_STATE, SENDMSG_QUEUE,
+                state, btDeviceInfo, delay);
+    }
+
+    /*package*/ void handleDisconnectHearingAid() {
+        mDeviceInventory.disconnectHearingAid();
+    }
+
+    /*package*/ void handleSetA2dpSourceConnectionState(@BluetoothProfile.BtProfileState int state,
+            @NonNull BtHelper.BluetoothA2dpDeviceInfo btDeviceInfo) {
+        final int intState = (state == BluetoothA2dp.STATE_CONNECTED) ? 1 : 0;
+        sendILMsgNoDelay(MSG_IL_SET_A2DP_SOURCE_CONNECTION_STATE, SENDMSG_QUEUE, state,
+                btDeviceInfo);
+    }
+
+    /*package*/ void handleFailureToConnectToBtHeadsetService(int delay) {
+        sendMsg(MSG_BT_HEADSET_CNCT_FAILED, SENDMSG_REPLACE, delay);
+    }
+
+    /*package*/ void handleCancelFailureToConnectToBtHeadsetService() {
+        mBrokerHandler.removeMessages(MSG_BT_HEADSET_CNCT_FAILED);
+    }
+
+    /*package*/ void postReportNewRoutes() {
+        sendMsgNoDelay(MSG_REPORT_NEW_ROUTES, SENDMSG_NOOP);
+    }
+
+    /*package*/ void cancelA2dpDockTimeout() {
+        mBrokerHandler.removeMessages(MSG_IL_BTA2DP_DOCK_TIMEOUT);
+    }
+
+    /*package*/ void postA2dpActiveDeviceChange(BtHelper.BluetoothA2dpDeviceInfo btDeviceInfo) {
+        sendLMsgNoDelay(MSG_L_A2DP_ACTIVE_DEVICE_CHANGE, SENDMSG_QUEUE, btDeviceInfo);
+    }
+
+    //###
+    // must be called synchronized on mConnectedDevices
+    /*package*/ boolean hasScheduledA2dpDockTimeout() {
+        return mBrokerHandler.hasMessages(MSG_IL_BTA2DP_DOCK_TIMEOUT);
+    }
+
+    //###
+    // must be called synchronized on mConnectedDevices
+    /*package*/  boolean hasScheduledA2dpSinkConnectionState(BluetoothDevice btDevice) {
+        return mBrokerHandler.hasMessages(MSG_IL_SET_A2DP_SINK_CONNECTION_STATE,
+                new BtHelper.BluetoothA2dpDeviceInfo(btDevice));
+    }
+
+    /*package*/ void setA2dpDockTimeout(String address, int a2dpCodec, int delayMs) {
+        sendILMsg(MSG_IL_BTA2DP_DOCK_TIMEOUT, SENDMSG_QUEUE, a2dpCodec, address, delayMs);
+    }
+
+    /*package*/ void setAvrcpAbsoluteVolumeSupported(boolean supported) {
+        synchronized (mA2dpAvrcpLock) {
+            mBtHelper.setAvrcpAbsoluteVolumeSupported(supported);
+        }
+    }
+
+    /*package*/ boolean getBluetoothA2dpEnabled() {
+        synchronized (mBluetoothA2dpEnabledLock) {
+            return mBluetoothA2dpEnabled;
+        }
+    }
+
+    /*package*/ int getA2dpCodec(@NonNull BluetoothDevice device) {
+        synchronized (mA2dpAvrcpLock) {
+            return mBtHelper.getA2dpCodec(device);
+        }
+    }
+
+    //---------------------------------------------------------------------
+    // Internal handling of messages
+    // These methods are ALL synchronous, in response to message handling in BrokerHandler
+    // Blocking in any of those will block the message queue
+
+    private void onSetForceUse(int useCase, int config, String eventSource) {
+        if (useCase == AudioSystem.FOR_MEDIA) {
+            postReportNewRoutes();
+        }
+        AudioService.sForceUseLogger.log(
+                new AudioServiceEvents.ForceUseEvent(useCase, config, eventSource));
+        AudioSystem.setForceUse(useCase, config);
+    }
+
+    private void onSendBecomingNoisyIntent() {
+        AudioService.sDeviceLogger.log((new AudioEventLogger.StringEvent(
+                "broadcast ACTION_AUDIO_BECOMING_NOISY")).printLog(TAG));
+        sendBroadcastToAll(new Intent(AudioManager.ACTION_AUDIO_BECOMING_NOISY));
+    }
+
+    //---------------------------------------------------------------------
+    // Message handling
+    private BrokerHandler mBrokerHandler;
+    private BrokerThread mBrokerThread;
+    private PowerManager.WakeLock mBrokerEventWakeLock;
+
+    private void setupMessaging(Context ctxt) {
+        final PowerManager pm = (PowerManager) ctxt.getSystemService(Context.POWER_SERVICE);
+        mBrokerEventWakeLock = pm.newWakeLock(PowerManager.PARTIAL_WAKE_LOCK,
+                "handleAudioDeviceEvent");
+        mBrokerThread = new BrokerThread();
+        mBrokerThread.start();
+        waitForBrokerHandlerCreation();
+    }
+
+    private void waitForBrokerHandlerCreation() {
+        synchronized (this) {
+            while (mBrokerHandler == null) {
+                try {
+                    wait();
+                } catch (InterruptedException e) {
+                    Log.e(TAG, "Interruption while waiting on BrokerHandler");
+                }
+            }
+        }
+    }
+
+    /** Class that handles the device broker's message queue */
+    private class BrokerThread extends Thread {
+        BrokerThread() {
+            super("AudioDeviceBroker");
+        }
+
+        @Override
+        public void run() {
+            // Set this thread up so the handler will work on it
+            Looper.prepare();
+
+            synchronized (AudioDeviceBroker.this) {
+                mBrokerHandler = new BrokerHandler();
+
+                // Notify that the handler has been created
+                AudioDeviceBroker.this.notify();
+            }
+
+            Looper.loop();
+        }
+    }
+
+    /** Class that handles the message queue */
+    private class BrokerHandler extends Handler {
+
+        @Override
+        public void handleMessage(Message msg) {
+            switch (msg.what) {
+                case MSG_RESTORE_DEVICES:
+                    mDeviceInventory.onRestoreDevices();
+                    synchronized (mBluetoothA2dpEnabledLock) {
+                        mBtHelper.onAudioServerDiedRestoreA2dp();
+                    }
+                    break;
+                case MSG_L_SET_WIRED_DEVICE_CONNECTION_STATE:
+                    mDeviceInventory.onSetWiredDeviceConnectionState(
+                            (AudioDeviceInventory.WiredDeviceConnectionState) msg.obj);
+                    break;
+                case MSG_I_BROADCAST_BT_CONNECTION_STATE:
+                    mBtHelper.onBroadcastScoConnectionState(msg.arg1);
+                    break;
+                case MSG_IIL_SET_FORCE_USE: // intented fall-through
+                case MSG_IIL_SET_FORCE_BT_A2DP_USE:
+                    onSetForceUse(msg.arg1, msg.arg2, (String) msg.obj);
+                    break;
+                case MSG_REPORT_NEW_ROUTES:
+                    mDeviceInventory.onReportNewRoutes();
+                    break;
+                case MSG_IL_SET_A2DP_SINK_CONNECTION_STATE:
+                    mDeviceInventory.onSetA2dpSinkConnectionState(
+                            (BtHelper.BluetoothA2dpDeviceInfo) msg.obj, msg.arg1, msg.arg2);
+                    break;
+                case MSG_IL_SET_A2DP_SOURCE_CONNECTION_STATE:
+                    mDeviceInventory.onSetA2dpSourceConnectionState(
+                            (BtHelper.BluetoothA2dpDeviceInfo) msg.obj, msg.arg1);
+                    break;
+                case MSG_IL_SET_HEARING_AID_CONNECTION_STATE:
+                    mDeviceInventory.onSetHearingAidConnectionState(
+                            (BluetoothDevice) msg.obj, msg.arg1);
+                    break;
+                case MSG_BT_HEADSET_CNCT_FAILED:
+                    mBtHelper.resetBluetoothSco();
+                    break;
+                case MSG_IL_BTA2DP_DOCK_TIMEOUT:
+                    // msg.obj  == address of BTA2DP device
+                    mDeviceInventory.onMakeA2dpDeviceUnavailableNow((String) msg.obj, msg.arg1);
+                    break;
+                case MSG_L_A2DP_DEVICE_CONFIG_CHANGE:
+                    final int a2dpCodec;
+                    final BluetoothDevice btDevice = (BluetoothDevice) msg.obj;
+                    synchronized (mA2dpAvrcpLock) {
+                        a2dpCodec = mBtHelper.getA2dpCodec(btDevice);
+                    }
+                    mDeviceInventory.onBluetoothA2dpDeviceConfigChange(
+                            new BtHelper.BluetoothA2dpDeviceInfo(btDevice, -1, a2dpCodec));
+                    break;
+                case MSG_BROADCAST_AUDIO_BECOMING_NOISY:
+                    onSendBecomingNoisyIntent();
+                    break;
+                case MSG_II_SET_HEARING_AID_VOLUME:
+                    mBtHelper.setHearingAidVolume(msg.arg1, msg.arg2);
+                    break;
+                case MSG_I_SET_AVRCP_ABSOLUTE_VOLUME:
+                    mBtHelper.setAvrcpAbsoluteVolumeIndex(msg.arg1);
+                    break;
+                case MSG_I_DISCONNECT_BT_SCO:
+                    mBtHelper.disconnectBluetoothSco(msg.arg1);
+                    break;
+                case MSG_TOGGLE_HDMI:
+                    mDeviceInventory.onToggleHdmi();
+                    break;
+                case MSG_L_A2DP_ACTIVE_DEVICE_CHANGE:
+                    mDeviceInventory.onBluetoothA2dpActiveDeviceChange(
+                            (BtHelper.BluetoothA2dpDeviceInfo) msg.obj);
+                    break;
+                default:
+                    Log.wtf(TAG, "Invalid message " + msg.what);
+            }
+            if (isMessageHandledUnderWakelock(msg.what)) {
+                try {
+                    mBrokerEventWakeLock.release();
+                } catch (Exception e) {
+                    Log.e(TAG, "Exception releasing wakelock", e);
+                }
+            }
+        }
+    }
+
+    // List of all messages. If a message has be handled under wakelock, add it to
+    //    the isMessageHandledUnderWakelock(int) method
+    // Naming of msg indicates arguments, using JNI argument grammar
+    // (e.g. II indicates two int args, IL indicates int and Obj arg)
+    private static final int MSG_RESTORE_DEVICES = 1;
+    private static final int MSG_L_SET_WIRED_DEVICE_CONNECTION_STATE = 2;
+    private static final int MSG_I_BROADCAST_BT_CONNECTION_STATE = 3;
+    private static final int MSG_IIL_SET_FORCE_USE = 4;
+    private static final int MSG_IIL_SET_FORCE_BT_A2DP_USE = 5;
+    private static final int MSG_IL_SET_A2DP_SINK_CONNECTION_STATE = 6;
+    private static final int MSG_IL_SET_A2DP_SOURCE_CONNECTION_STATE = 7;
+    private static final int MSG_IL_SET_HEARING_AID_CONNECTION_STATE = 8;
+    private static final int MSG_BT_HEADSET_CNCT_FAILED = 9;
+    private static final int MSG_IL_BTA2DP_DOCK_TIMEOUT = 10;
+    private static final int MSG_L_A2DP_DEVICE_CONFIG_CHANGE = 11;
+    private static final int MSG_BROADCAST_AUDIO_BECOMING_NOISY = 12;
+    private static final int MSG_REPORT_NEW_ROUTES = 13;
+    private static final int MSG_II_SET_HEARING_AID_VOLUME = 14;
+    private static final int MSG_I_SET_AVRCP_ABSOLUTE_VOLUME = 15;
+    private static final int MSG_I_DISCONNECT_BT_SCO = 16;
+    private static final int MSG_TOGGLE_HDMI = 17;
+    private static final int MSG_L_A2DP_ACTIVE_DEVICE_CHANGE = 18;
+
+
+    private static boolean isMessageHandledUnderWakelock(int msgId) {
+        switch(msgId) {
+            case MSG_L_SET_WIRED_DEVICE_CONNECTION_STATE:
+            case MSG_IL_SET_A2DP_SINK_CONNECTION_STATE:
+            case MSG_IL_SET_A2DP_SOURCE_CONNECTION_STATE:
+            case MSG_IL_SET_HEARING_AID_CONNECTION_STATE:
+            case MSG_IL_BTA2DP_DOCK_TIMEOUT:
+            case MSG_L_A2DP_DEVICE_CONFIG_CHANGE:
+            case MSG_TOGGLE_HDMI:
+            case MSG_L_A2DP_ACTIVE_DEVICE_CHANGE:
+                return true;
+            default:
+                return false;
+        }
+    }
+
+    // Message helper methods
+
+    // sendMsg() flags
+    /** If the msg is already queued, replace it with this one. */
+    private static final int SENDMSG_REPLACE = 0;
+    /** If the msg is already queued, ignore this one and leave the old. */
+    private static final int SENDMSG_NOOP = 1;
+    /** If the msg is already queued, queue this one and leave the old. */
+    private static final int SENDMSG_QUEUE = 2;
+
+    private void sendMsg(int msg, int existingMsgPolicy, int delay) {
+        sendIILMsg(msg, existingMsgPolicy, 0, 0, null, delay);
+    }
+
+    private void sendILMsg(int msg, int existingMsgPolicy, int arg, Object obj, int delay) {
+        sendIILMsg(msg, existingMsgPolicy, arg, 0, obj, delay);
+    }
+
+    private void sendLMsg(int msg, int existingMsgPolicy, Object obj, int delay) {
+        sendIILMsg(msg, existingMsgPolicy, 0, 0, obj, delay);
+    }
+
+    private void sendIMsg(int msg, int existingMsgPolicy, int arg, int delay) {
+        sendIILMsg(msg, existingMsgPolicy, arg, 0, null, delay);
+    }
+
+    private void sendMsgNoDelay(int msg, int existingMsgPolicy) {
+        sendIILMsg(msg, existingMsgPolicy, 0, 0, null, 0);
+    }
+
+    private void sendIMsgNoDelay(int msg, int existingMsgPolicy, int arg) {
+        sendIILMsg(msg, existingMsgPolicy, arg, 0, null, 0);
+    }
+
+    private void sendIIMsgNoDelay(int msg, int existingMsgPolicy, int arg1, int arg2) {
+        sendIILMsg(msg, existingMsgPolicy, arg1, arg2, null, 0);
+    }
+
+    private void sendILMsgNoDelay(int msg, int existingMsgPolicy, int arg, Object obj) {
+        sendIILMsg(msg, existingMsgPolicy, arg, 0, obj, 0);
+    }
+
+    private void sendLMsgNoDelay(int msg, int existingMsgPolicy, Object obj) {
+        sendIILMsg(msg, existingMsgPolicy, 0, 0, obj, 0);
+    }
+
+    private void sendIILMsgNoDelay(int msg, int existingMsgPolicy, int arg1, int arg2, Object obj) {
+        sendIILMsg(msg, existingMsgPolicy, arg1, arg2, obj, 0);
+    }
+
+    private void sendIILMsg(int msg, int existingMsgPolicy, int arg1, int arg2, Object obj,
+                            int delay) {
+        if (existingMsgPolicy == SENDMSG_REPLACE) {
+            mBrokerHandler.removeMessages(msg);
+        } else if (existingMsgPolicy == SENDMSG_NOOP && mBrokerHandler.hasMessages(msg)) {
+            return;
+        }
+
+        if (isMessageHandledUnderWakelock(msg)) {
+            final long identity = Binder.clearCallingIdentity();
+            try {
+                mBrokerEventWakeLock.acquire(BROKER_WAKELOCK_TIMEOUT_MS);
+            } catch (Exception e) {
+                Log.e(TAG, "Exception acquiring wakelock", e);
+            }
+            Binder.restoreCallingIdentity(identity);
+        }
+
+        synchronized (sLastDeviceConnectionMsgTimeLock) {
+            long time = SystemClock.uptimeMillis() + delay;
+
+            switch (msg) {
+                case MSG_IL_SET_A2DP_SOURCE_CONNECTION_STATE:
+                case MSG_IL_SET_A2DP_SINK_CONNECTION_STATE:
+                case MSG_IL_SET_HEARING_AID_CONNECTION_STATE:
+                case MSG_L_SET_WIRED_DEVICE_CONNECTION_STATE:
+                case MSG_IL_BTA2DP_DOCK_TIMEOUT:
+                case MSG_L_A2DP_DEVICE_CONFIG_CHANGE:
+                case MSG_L_A2DP_ACTIVE_DEVICE_CHANGE:
+                    if (sLastDeviceConnectMsgTime >= time) {
+                        // add a little delay to make sure messages are ordered as expected
+                        time = sLastDeviceConnectMsgTime + 30;
+                    }
+                    sLastDeviceConnectMsgTime = time;
+                    break;
+                default:
+                    break;
+            }
+
+            mBrokerHandler.sendMessageAtTime(mBrokerHandler.obtainMessage(msg, arg1, arg2, obj),
+                    time);
+        }
+    }
+
+    //-------------------------------------------------------------
+    // internal utilities
+    private void sendBroadcastToAll(Intent intent) {
+        intent.addFlags(Intent.FLAG_RECEIVER_REGISTERED_ONLY_BEFORE_BOOT);
+        intent.addFlags(Intent.FLAG_RECEIVER_FOREGROUND);
+        final long ident = Binder.clearCallingIdentity();
+        try {
+            mContext.sendBroadcastAsUser(intent, UserHandle.ALL);
+        } finally {
+            Binder.restoreCallingIdentity(ident);
+        }
+    }
+}
diff --git a/services/core/java/com/android/server/audio/AudioDeviceInventory.java b/services/core/java/com/android/server/audio/AudioDeviceInventory.java
new file mode 100644
index 0000000..eb76e6e0
--- /dev/null
+++ b/services/core/java/com/android/server/audio/AudioDeviceInventory.java
@@ -0,0 +1,1024 @@
+/*
+ * Copyright 2019 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.android.server.audio;
+
+import android.annotation.NonNull;
+import android.app.ActivityManager;
+import android.bluetooth.BluetoothA2dp;
+import android.bluetooth.BluetoothAdapter;
+import android.bluetooth.BluetoothDevice;
+import android.bluetooth.BluetoothHearingAid;
+import android.bluetooth.BluetoothProfile;
+import android.content.Intent;
+import android.media.AudioDevicePort;
+import android.media.AudioFormat;
+import android.media.AudioManager;
+import android.media.AudioPort;
+import android.media.AudioRoutesInfo;
+import android.media.AudioSystem;
+import android.media.IAudioRoutesObserver;
+import android.os.Binder;
+import android.os.RemoteCallbackList;
+import android.os.RemoteException;
+import android.os.UserHandle;
+import android.text.TextUtils;
+import android.util.ArrayMap;
+import android.util.ArraySet;
+import android.util.Log;
+import android.util.Slog;
+
+import com.android.internal.annotations.GuardedBy;
+
+import java.util.ArrayList;
+
+/**
+ * Class to manage the inventory of all connected devices.
+ * This class is thread-safe.
+ */
+public final class AudioDeviceInventory {
+
+    private static final String TAG = "AS.AudioDeviceInventory";
+
+    // Actual list of connected devices
+    // Key for map created from DeviceInfo.makeDeviceListKey()
+    private final ArrayMap<String, DeviceInfo> mConnectedDevices = new ArrayMap<>();
+
+    private final @NonNull AudioDeviceBroker mDeviceBroker;
+
+    AudioDeviceInventory(@NonNull AudioDeviceBroker broker) {
+        mDeviceBroker = broker;
+    }
+
+    // cache of the address of the last dock the device was connected to
+    private String mDockAddress;
+
+    // Monitoring of audio routes.  Protected by mAudioRoutes.
+    final AudioRoutesInfo mCurAudioRoutes = new AudioRoutesInfo();
+    final RemoteCallbackList<IAudioRoutesObserver> mRoutesObservers =
+            new RemoteCallbackList<IAudioRoutesObserver>();
+
+    //------------------------------------------------------------
+    /**
+     * Class to store info about connected devices.
+     * Use makeDeviceListKey() to make a unique key for this list.
+     */
+    private static class DeviceInfo {
+        final int mDeviceType;
+        final String mDeviceName;
+        final String mDeviceAddress;
+        int mDeviceCodecFormat;
+
+        DeviceInfo(int deviceType, String deviceName, String deviceAddress, int deviceCodecFormat) {
+            mDeviceType = deviceType;
+            mDeviceName = deviceName;
+            mDeviceAddress = deviceAddress;
+            mDeviceCodecFormat = deviceCodecFormat;
+        }
+
+        @Override
+        public String toString() {
+            return "[DeviceInfo: type:0x" + Integer.toHexString(mDeviceType)
+                    + " name:" + mDeviceName
+                    + " addr:" + mDeviceAddress
+                    + " codec: " + Integer.toHexString(mDeviceCodecFormat) + "]";
+        }
+
+        /**
+         * Generate a unique key for the mConnectedDevices List by composing the device "type"
+         * and the "address" associated with a specific instance of that device type
+         */
+        private static String makeDeviceListKey(int device, String deviceAddress) {
+            return "0x" + Integer.toHexString(device) + ":" + deviceAddress;
+        }
+    }
+
+    /**
+     * A class just for packaging up a set of connection parameters.
+     */
+    /*package*/ class WiredDeviceConnectionState {
+        public final int mType;
+        public final @AudioService.ConnectionState int mState;
+        public final String mAddress;
+        public final String mName;
+        public final String mCaller;
+
+        /*package*/ WiredDeviceConnectionState(int type, @AudioService.ConnectionState int state,
+                                               String address, String name, String caller) {
+            mType = type;
+            mState = state;
+            mAddress = address;
+            mName = name;
+            mCaller = caller;
+        }
+    }
+
+    //------------------------------------------------------------
+    // Message handling from AudioDeviceBroker
+
+    /**
+     * Restore previously connected devices. Use in case of audio server crash
+     * (see AudioService.onAudioServerDied() method)
+     */
+    /*package*/ void onRestoreDevices() {
+        synchronized (mConnectedDevices) {
+            for (int i = 0; i < mConnectedDevices.size(); i++) {
+                DeviceInfo di = mConnectedDevices.valueAt(i);
+                AudioSystem.setDeviceConnectionState(
+                        di.mDeviceType,
+                        AudioSystem.DEVICE_STATE_AVAILABLE,
+                        di.mDeviceAddress,
+                        di.mDeviceName,
+                        di.mDeviceCodecFormat);
+            }
+        }
+    }
+
+    /*package*/ void onSetA2dpSinkConnectionState(@NonNull BtHelper.BluetoothA2dpDeviceInfo btInfo,
+            @AudioService.BtProfileConnectionState int state, int a2dpVolume) {
+        final BluetoothDevice btDevice = btInfo.getBtDevice();
+        if (AudioService.DEBUG_DEVICES) {
+            Log.d(TAG, "onSetA2dpSinkConnectionState btDevice=" + btDevice + " state="
+                    + state + " is dock=" + btDevice.isBluetoothDock());
+        }
+        String address = btDevice.getAddress();
+        if (!BluetoothAdapter.checkBluetoothAddress(address)) {
+            address = "";
+        }
+        AudioService.sDeviceLogger.log(new AudioEventLogger.StringEvent(
+                "A2DP sink connected: device addr=" + address + " state=" + state));
+
+        final int a2dpCodec;
+        synchronized (mDeviceBroker.mA2dpAvrcpLock) {
+            a2dpCodec = btInfo.getCodec();
+        }
+
+        synchronized (mConnectedDevices) {
+            final String key = DeviceInfo.makeDeviceListKey(AudioSystem.DEVICE_OUT_BLUETOOTH_A2DP,
+                    btDevice.getAddress());
+            final DeviceInfo di = mConnectedDevices.get(key);
+            boolean isConnected = di != null;
+
+            if (isConnected && state != BluetoothProfile.STATE_CONNECTED) {
+                if (btDevice.isBluetoothDock()) {
+                    if (state == BluetoothProfile.STATE_DISCONNECTED) {
+                        // introduction of a delay for transient disconnections of docks when
+                        // power is rapidly turned off/on, this message will be canceled if
+                        // we reconnect the dock under a preset delay
+                        makeA2dpDeviceUnavailableLater(address,
+                                AudioDeviceBroker.BTA2DP_DOCK_TIMEOUT_MS);
+                        // the next time isConnected is evaluated, it will be false for the dock
+                    }
+                } else {
+                    makeA2dpDeviceUnavailableNow(address, di.mDeviceCodecFormat);
+                }
+            } else if (!isConnected && state == BluetoothProfile.STATE_CONNECTED) {
+                if (btDevice.isBluetoothDock()) {
+                    // this could be a reconnection after a transient disconnection
+                    mDeviceBroker.cancelA2dpDockTimeout();
+                    mDockAddress = address;
+                } else {
+                    // this could be a connection of another A2DP device before the timeout of
+                    // a dock: cancel the dock timeout, and make the dock unavailable now
+                    if (mDeviceBroker.hasScheduledA2dpDockTimeout() && mDockAddress != null) {
+                        mDeviceBroker.cancelA2dpDockTimeout();
+                        makeA2dpDeviceUnavailableNow(mDockAddress,
+                                AudioSystem.AUDIO_FORMAT_DEFAULT);
+                    }
+                }
+                if (a2dpVolume != -1) {
+                    AudioService.VolumeStreamState streamState =
+                            mDeviceBroker.getStreamState(AudioSystem.STREAM_MUSIC);
+                    // Convert index to internal representation in VolumeStreamState
+                    a2dpVolume = a2dpVolume * 10;
+                    streamState.setIndex(a2dpVolume, AudioSystem.DEVICE_OUT_BLUETOOTH_A2DP,
+                            "onSetA2dpSinkConnectionState");
+                    mDeviceBroker.setDeviceVolume(
+                            streamState, AudioSystem.DEVICE_OUT_BLUETOOTH_A2DP);
+                }
+                makeA2dpDeviceAvailable(address, btDevice.getName(),
+                        "onSetA2dpSinkConnectionState", a2dpCodec);
+            }
+        }
+    }
+
+    /*package*/ void onSetA2dpSourceConnectionState(
+            @NonNull BtHelper.BluetoothA2dpDeviceInfo btInfo, int state) {
+        final BluetoothDevice btDevice = btInfo.getBtDevice();
+        if (AudioService.DEBUG_DEVICES) {
+            Log.d(TAG, "onSetA2dpSourceConnectionState btDevice=" + btDevice + " state="
+                    + state);
+        }
+        String address = btDevice.getAddress();
+        if (!BluetoothAdapter.checkBluetoothAddress(address)) {
+            address = "";
+        }
+
+        synchronized (mConnectedDevices) {
+            final String key = DeviceInfo.makeDeviceListKey(
+                    AudioSystem.DEVICE_IN_BLUETOOTH_A2DP, address);
+            final DeviceInfo di = mConnectedDevices.get(key);
+            boolean isConnected = di != null;
+
+            if (isConnected && state != BluetoothProfile.STATE_CONNECTED) {
+                makeA2dpSrcUnavailable(address);
+            } else if (!isConnected && state == BluetoothProfile.STATE_CONNECTED) {
+                makeA2dpSrcAvailable(address);
+            }
+        }
+    }
+
+    /*package*/ void onSetHearingAidConnectionState(BluetoothDevice btDevice,
+                @AudioService.BtProfileConnectionState int state) {
+        String address = btDevice.getAddress();
+        if (!BluetoothAdapter.checkBluetoothAddress(address)) {
+            address = "";
+        }
+        AudioService.sDeviceLogger.log(new AudioEventLogger.StringEvent(
+                "onSetHearingAidConnectionState addr=" + address));
+
+        synchronized (mConnectedDevices) {
+            final String key = DeviceInfo.makeDeviceListKey(AudioSystem.DEVICE_OUT_HEARING_AID,
+                    btDevice.getAddress());
+            final DeviceInfo di = mConnectedDevices.get(key);
+            boolean isConnected = di != null;
+
+            if (isConnected && state != BluetoothProfile.STATE_CONNECTED) {
+                makeHearingAidDeviceUnavailable(address);
+            } else if (!isConnected && state == BluetoothProfile.STATE_CONNECTED) {
+                makeHearingAidDeviceAvailable(address, btDevice.getName(),
+                        "onSetHearingAidConnectionState");
+            }
+        }
+    }
+
+    /*package*/ void onBluetoothA2dpDeviceConfigChange(
+            @NonNull BtHelper.BluetoothA2dpDeviceInfo btInfo) {
+        final BluetoothDevice btDevice = btInfo.getBtDevice();
+        if (AudioService.DEBUG_DEVICES) {
+            Log.d(TAG, "onBluetoothA2dpDeviceConfigChange btDevice=" + btDevice);
+        }
+        if (btDevice == null) {
+            return;
+        }
+        String address = btDevice.getAddress();
+        if (!BluetoothAdapter.checkBluetoothAddress(address)) {
+            address = "";
+        }
+        AudioService.sDeviceLogger.log(new AudioEventLogger.StringEvent(
+                "onBluetoothA2dpDeviceConfigChange addr=" + address));
+
+        final int a2dpCodec = btInfo.getCodec();
+
+        synchronized (mConnectedDevices) {
+            if (mDeviceBroker.hasScheduledA2dpSinkConnectionState(btDevice)) {
+                AudioService.sDeviceLogger.log(new AudioEventLogger.StringEvent(
+                        "A2dp config change ignored"));
+                return;
+            }
+            final String key =
+                    DeviceInfo.makeDeviceListKey(AudioSystem.DEVICE_OUT_BLUETOOTH_A2DP, address);
+            final DeviceInfo di = mConnectedDevices.get(key);
+            if (di == null) {
+                Log.e(TAG, "invalid null DeviceInfo in onBluetoothA2dpDeviceConfigChange");
+                return;
+            }
+            // Device is connected
+            if (di.mDeviceCodecFormat != a2dpCodec) {
+                di.mDeviceCodecFormat = a2dpCodec;
+                mConnectedDevices.replace(key, di);
+            }
+            if (AudioService.DEBUG_DEVICES) {
+                Log.d(TAG, "onBluetoothA2dpDeviceConfigChange: codec="
+                        + di.mDeviceCodecFormat);
+            }
+            if (AudioSystem.handleDeviceConfigChange(AudioSystem.DEVICE_OUT_BLUETOOTH_A2DP, address,
+                    btDevice.getName(), di.mDeviceCodecFormat) != AudioSystem.AUDIO_STATUS_OK) {
+                // force A2DP device disconnection in case of error so that AudioService state
+                // is consistent with audio policy manager state
+                final int musicDevice = mDeviceBroker.getDeviceForStream(AudioSystem.STREAM_MUSIC);
+                setBluetoothA2dpDeviceConnectionState(
+                        btDevice, BluetoothA2dp.STATE_DISCONNECTED, BluetoothProfile.A2DP,
+                        false /* suppressNoisyIntent */, musicDevice,
+                        -1 /* a2dpVolume */);
+            }
+        }
+    }
+
+    /*package*/ void onBluetoothA2dpActiveDeviceChange(
+            @NonNull BtHelper.BluetoothA2dpDeviceInfo btInfo) {
+        final BluetoothDevice btDevice = btInfo.getBtDevice();
+        int a2dpVolume = btInfo.getVolume();
+        final int a2dpCodec = btInfo.getCodec();
+
+        if (AudioService.DEBUG_DEVICES) {
+            Log.d(TAG, "onBluetoothA2dpActiveDeviceChange btDevice=" + btDevice);
+        }
+        String address = btDevice.getAddress();
+        if (!BluetoothAdapter.checkBluetoothAddress(address)) {
+            address = "";
+        }
+        AudioService.sDeviceLogger.log(new AudioEventLogger.StringEvent(
+                "onBluetoothA2dpActiveDeviceChange addr=" + address));
+
+        synchronized (mConnectedDevices) {
+            //TODO original CL is not consistent between BluetoothDevice and BluetoothA2dpDeviceInfo
+            // for this type of message
+            if (mDeviceBroker.hasScheduledA2dpSinkConnectionState(btDevice)) {
+                AudioService.sDeviceLogger.log(new AudioEventLogger.StringEvent(
+                        "A2dp config change ignored"));
+                return;
+            }
+            final String key = DeviceInfo.makeDeviceListKey(
+                    AudioSystem.DEVICE_OUT_BLUETOOTH_A2DP, address);
+            final DeviceInfo di = mConnectedDevices.get(key);
+            if (di == null) {
+                return;
+            }
+
+            // Device is connected
+            if (a2dpVolume != -1) {
+                final AudioService.VolumeStreamState streamState =
+                        mDeviceBroker.getStreamState(AudioSystem.STREAM_MUSIC);
+                // Convert index to internal representation in VolumeStreamState
+                a2dpVolume = a2dpVolume * 10;
+                streamState.setIndex(a2dpVolume, AudioSystem.DEVICE_OUT_BLUETOOTH_A2DP,
+                        "onBluetoothA2dpActiveDeviceChange");
+                mDeviceBroker.setDeviceVolume(streamState, AudioSystem.DEVICE_OUT_BLUETOOTH_A2DP);
+            }
+
+            if (AudioSystem.handleDeviceConfigChange(AudioSystem.DEVICE_OUT_BLUETOOTH_A2DP, address,
+                    btDevice.getName(), a2dpCodec) != AudioSystem.AUDIO_STATUS_OK) {
+                int musicDevice = mDeviceBroker.getDeviceForStream(AudioSystem.STREAM_MUSIC);
+                // force A2DP device disconnection in case of error so that AudioService state is
+                // consistent with audio policy manager state
+                setBluetoothA2dpDeviceConnectionState(
+                        btDevice, BluetoothA2dp.STATE_DISCONNECTED, BluetoothProfile.A2DP,
+                        false /* suppressNoisyIntent */, musicDevice,
+                        -1 /* a2dpVolume */);
+            }
+        }
+    }
+
+    /*package*/ void onMakeA2dpDeviceUnavailableNow(String address, int a2dpCodec) {
+        synchronized (mConnectedDevices) {
+            makeA2dpDeviceUnavailableNow(address, a2dpCodec);
+        }
+    }
+
+    /*package*/ void onReportNewRoutes() {
+        int n = mRoutesObservers.beginBroadcast();
+        if (n > 0) {
+            AudioRoutesInfo routes;
+            synchronized (mCurAudioRoutes) {
+                routes = new AudioRoutesInfo(mCurAudioRoutes);
+            }
+            while (n > 0) {
+                n--;
+                IAudioRoutesObserver obs = mRoutesObservers.getBroadcastItem(n);
+                try {
+                    obs.dispatchAudioRoutesChanged(routes);
+                } catch (RemoteException e) { }
+            }
+        }
+        mRoutesObservers.finishBroadcast();
+        mDeviceBroker.observeDevicesForAllStreams();
+    }
+
+    private static final int DEVICE_OVERRIDE_A2DP_ROUTE_ON_PLUG =
+            AudioSystem.DEVICE_OUT_WIRED_HEADSET | AudioSystem.DEVICE_OUT_WIRED_HEADPHONE
+                    | AudioSystem.DEVICE_OUT_LINE | AudioSystem.DEVICE_OUT_ALL_USB;
+
+    /*package*/ void onSetWiredDeviceConnectionState(
+                            AudioDeviceInventory.WiredDeviceConnectionState wdcs) {
+        AudioService.sDeviceLogger.log(new AudioServiceEvents.WiredDevConnectEvent(wdcs));
+
+        synchronized (mConnectedDevices) {
+            if ((wdcs.mState == AudioService.CONNECTION_STATE_DISCONNECTED)
+                    && ((wdcs.mType & DEVICE_OVERRIDE_A2DP_ROUTE_ON_PLUG) != 0)) {
+                mDeviceBroker.setBluetoothA2dpOnInt(true,
+                        "onSetWiredDeviceConnectionState state DISCONNECTED");
+            }
+
+            if (!handleDeviceConnection(wdcs.mState == 1, wdcs.mType, wdcs.mAddress,
+                    wdcs.mName)) {
+                // change of connection state failed, bailout
+                return;
+            }
+            if (wdcs.mState != AudioService.CONNECTION_STATE_DISCONNECTED) {
+                if ((wdcs.mType & DEVICE_OVERRIDE_A2DP_ROUTE_ON_PLUG) != 0) {
+                    mDeviceBroker.setBluetoothA2dpOnInt(false,
+                            "onSetWiredDeviceConnectionState state not DISCONNECTED");
+                }
+                mDeviceBroker.checkMusicActive(wdcs.mType, wdcs.mCaller);
+            }
+            mDeviceBroker.checkVolumeCecOnHdmiConnection(wdcs.mState, wdcs.mCaller);
+            sendDeviceConnectionIntent(wdcs.mType, wdcs.mState, wdcs.mAddress, wdcs.mName);
+            updateAudioRoutes(wdcs.mType, wdcs.mState);
+        }
+    }
+
+    /*package*/ void onToggleHdmi() {
+        synchronized (mConnectedDevices) {
+            // Is HDMI connected?
+            final String key = DeviceInfo.makeDeviceListKey(AudioSystem.DEVICE_OUT_HDMI, "");
+            final DeviceInfo di = mConnectedDevices.get(key);
+            if (di == null) {
+                Log.e(TAG, "invalid null DeviceInfo in onToggleHdmi");
+                return;
+            }
+            // Toggle HDMI to retrigger broadcast with proper formats.
+            setWiredDeviceConnectionState(AudioSystem.DEVICE_OUT_HDMI,
+                    AudioSystem.DEVICE_STATE_UNAVAILABLE, "", "",
+                    "android"); // disconnect
+            setWiredDeviceConnectionState(AudioSystem.DEVICE_OUT_HDMI,
+                    AudioSystem.DEVICE_STATE_AVAILABLE, "", "",
+                    "android"); // reconnect
+        }
+    }
+    //------------------------------------------------------------
+    //
+
+    /**
+     * Implements the communication with AudioSystem to (dis)connect a device in the native layers
+     * @param connect true if connection
+     * @param device the device type
+     * @param address the address of the device
+     * @param deviceName human-readable name of device
+     * @return false if an error was reported by AudioSystem
+     */
+    /*package*/ boolean handleDeviceConnection(boolean connect, int device, String address,
+            String deviceName) {
+        if (AudioService.DEBUG_DEVICES) {
+            Slog.i(TAG, "handleDeviceConnection(" + connect + " dev:"
+                    + Integer.toHexString(device) + " address:" + address
+                    + " name:" + deviceName + ")");
+        }
+        synchronized (mConnectedDevices) {
+            final String deviceKey = DeviceInfo.makeDeviceListKey(device, address);
+            if (AudioService.DEBUG_DEVICES) {
+                Slog.i(TAG, "deviceKey:" + deviceKey);
+            }
+            DeviceInfo di = mConnectedDevices.get(deviceKey);
+            boolean isConnected = di != null;
+            if (AudioService.DEBUG_DEVICES) {
+                Slog.i(TAG, "deviceInfo:" + di + " is(already)Connected:" + isConnected);
+            }
+            if (connect && !isConnected) {
+                final int res = AudioSystem.setDeviceConnectionState(device,
+                        AudioSystem.DEVICE_STATE_AVAILABLE, address, deviceName,
+                        AudioSystem.AUDIO_FORMAT_DEFAULT);
+                if (res != AudioSystem.AUDIO_STATUS_OK) {
+                    Slog.e(TAG, "not connecting device 0x" + Integer.toHexString(device)
+                            + " due to command error " + res);
+                    return false;
+                }
+                mConnectedDevices.put(deviceKey, new DeviceInfo(
+                        device, deviceName, address, AudioSystem.AUDIO_FORMAT_DEFAULT));
+                mDeviceBroker.postAccessoryPlugMediaUnmute(device);
+                return true;
+            } else if (!connect && isConnected) {
+                AudioSystem.setDeviceConnectionState(device,
+                        AudioSystem.DEVICE_STATE_UNAVAILABLE, address, deviceName,
+                        AudioSystem.AUDIO_FORMAT_DEFAULT);
+                // always remove even if disconnection failed
+                mConnectedDevices.remove(deviceKey);
+                return true;
+            }
+            Log.w(TAG, "handleDeviceConnection() failed, deviceKey=" + deviceKey
+                    + ", deviceSpec=" + di + ", connect=" + connect);
+        }
+        return false;
+    }
+
+
+    /*package*/ void disconnectA2dp() {
+        synchronized (mConnectedDevices) {
+            synchronized (mDeviceBroker.mA2dpAvrcpLock) {
+                final ArraySet<String> toRemove = new ArraySet<>();
+                // Disconnect ALL DEVICE_OUT_BLUETOOTH_A2DP devices
+                mConnectedDevices.values().forEach(deviceInfo -> {
+                    if (deviceInfo.mDeviceType == AudioSystem.DEVICE_OUT_BLUETOOTH_A2DP) {
+                        toRemove.add(deviceInfo.mDeviceAddress);
+                    }
+                });
+                if (toRemove.size() > 0) {
+                    final int delay = checkSendBecomingNoisyIntentInt(
+                            AudioSystem.DEVICE_OUT_BLUETOOTH_A2DP,
+                            0, AudioSystem.DEVICE_NONE);
+                    toRemove.stream().forEach(deviceAddress ->
+                            makeA2dpDeviceUnavailableLater(deviceAddress, delay)
+                    );
+                }
+            }
+        }
+    }
+
+    /*package*/ void disconnectA2dpSink() {
+        synchronized (mConnectedDevices) {
+            final ArraySet<String> toRemove = new ArraySet<>();
+            // Disconnect ALL DEVICE_IN_BLUETOOTH_A2DP devices
+            mConnectedDevices.values().forEach(deviceInfo -> {
+                if (deviceInfo.mDeviceType == AudioSystem.DEVICE_IN_BLUETOOTH_A2DP) {
+                    toRemove.add(deviceInfo.mDeviceAddress);
+                }
+            });
+            toRemove.stream().forEach(deviceAddress -> makeA2dpSrcUnavailable(deviceAddress));
+        }
+    }
+
+    /*package*/ void disconnectHearingAid() {
+        synchronized (mConnectedDevices) {
+            synchronized (mDeviceBroker.mHearingAidLock) {
+                final ArraySet<String> toRemove = new ArraySet<>();
+                // Disconnect ALL DEVICE_OUT_HEARING_AID devices
+                mConnectedDevices.values().forEach(deviceInfo -> {
+                    if (deviceInfo.mDeviceType == AudioSystem.DEVICE_OUT_HEARING_AID) {
+                        toRemove.add(deviceInfo.mDeviceAddress);
+                    }
+                });
+                if (toRemove.size() > 0) {
+                    final int delay = checkSendBecomingNoisyIntentInt(
+                            AudioSystem.DEVICE_OUT_HEARING_AID, 0, AudioSystem.DEVICE_NONE);
+                    toRemove.stream().forEach(deviceAddress ->
+                            // TODO delay not used?
+                            makeHearingAidDeviceUnavailable(deviceAddress /*, delay*/)
+                    );
+                }
+            }
+        }
+    }
+
+    // must be called before removing the device from mConnectedDevices
+    // musicDevice argument is used when not AudioSystem.DEVICE_NONE instead of querying
+    // from AudioSystem
+    /*package*/ int checkSendBecomingNoisyIntent(int device, int state, int musicDevice) {
+        synchronized (mConnectedDevices) {
+            return checkSendBecomingNoisyIntentInt(device, state, musicDevice);
+        }
+    }
+
+    /*package*/ AudioRoutesInfo startWatchingRoutes(IAudioRoutesObserver observer) {
+        synchronized (mCurAudioRoutes) {
+            AudioRoutesInfo routes = new AudioRoutesInfo(mCurAudioRoutes);
+            mRoutesObservers.register(observer);
+            return routes;
+        }
+    }
+
+    /*package*/ AudioRoutesInfo getCurAudioRoutes() {
+        return mCurAudioRoutes;
+    }
+
+    /*package*/ int setBluetoothA2dpDeviceConnectionState(
+            @NonNull BluetoothDevice device, @AudioService.BtProfileConnectionState int state,
+            int profile, boolean suppressNoisyIntent, int musicDevice, int a2dpVolume) {
+        int delay;
+        if (profile != BluetoothProfile.A2DP && profile != BluetoothProfile.A2DP_SINK) {
+            throw new IllegalArgumentException("invalid profile " + profile);
+        }
+        synchronized (mConnectedDevices) {
+            if (profile == BluetoothProfile.A2DP && !suppressNoisyIntent) {
+                int intState = (state == BluetoothA2dp.STATE_CONNECTED) ? 1 : 0;
+                delay = checkSendBecomingNoisyIntentInt(AudioSystem.DEVICE_OUT_BLUETOOTH_A2DP,
+                        intState, musicDevice);
+            } else {
+                delay = 0;
+            }
+
+            final int a2dpCodec = mDeviceBroker.getA2dpCodec(device);
+
+            if (AudioService.DEBUG_DEVICES) {
+                Log.i(TAG, "setBluetoothA2dpDeviceConnectionState device: " + device
+                        + " state: " + state + " delay(ms): " + delay + "codec:" + a2dpCodec
+                        + " suppressNoisyIntent: " + suppressNoisyIntent);
+            }
+
+            final BtHelper.BluetoothA2dpDeviceInfo a2dpDeviceInfo =
+                    new BtHelper.BluetoothA2dpDeviceInfo(device, a2dpVolume, a2dpCodec);
+            if (profile == BluetoothProfile.A2DP) {
+                mDeviceBroker.postA2dpSinkConnection(state,
+                        a2dpDeviceInfo,
+                        delay);
+            } else { //profile == BluetoothProfile.A2DP_SINK
+                mDeviceBroker.postA2dpSourceConnection(state,
+                        a2dpDeviceInfo,
+                        delay);
+            }
+        }
+        return delay;
+    }
+
+    /*package*/ int handleBluetoothA2dpActiveDeviceChange(
+            @NonNull BluetoothDevice device,
+            @AudioService.BtProfileConnectionState int state, int profile,
+            boolean suppressNoisyIntent, int a2dpVolume) {
+        if (state == BluetoothProfile.STATE_DISCONNECTED) {
+            return setBluetoothA2dpDeviceConnectionState(device, state, profile,
+                    suppressNoisyIntent, AudioSystem.DEVICE_NONE, a2dpVolume);
+        }
+        // state == BluetoothProfile.STATE_CONNECTED
+        synchronized (mConnectedDevices) {
+            for (int i = 0; i < mConnectedDevices.size(); i++) {
+                final DeviceInfo deviceInfo = mConnectedDevices.valueAt(i);
+                if (deviceInfo.mDeviceType != AudioSystem.DEVICE_OUT_BLUETOOTH_A2DP) {
+                    continue;
+                }
+                // If A2DP device exists, this is either an active device change or
+                // device config change
+                final String existingDevicekey = mConnectedDevices.keyAt(i);
+                final String deviceName = device.getName();
+                final String address = device.getAddress();
+                final String newDeviceKey = DeviceInfo.makeDeviceListKey(
+                        AudioSystem.DEVICE_OUT_BLUETOOTH_A2DP, address);
+                int a2dpCodec = mDeviceBroker.getA2dpCodec(device);
+                // Device not equal to existing device, active device change
+                if (!TextUtils.equals(existingDevicekey, newDeviceKey)) {
+                    mConnectedDevices.remove(existingDevicekey);
+                    mConnectedDevices.put(newDeviceKey, new DeviceInfo(
+                            AudioSystem.DEVICE_OUT_BLUETOOTH_A2DP, deviceName,
+                            address, a2dpCodec));
+                    mDeviceBroker.postA2dpActiveDeviceChange(
+                            new BtHelper.BluetoothA2dpDeviceInfo(
+                                    device, a2dpVolume, a2dpCodec));
+                    return 0;
+                } else {
+                    // Device config change for existing device
+                    mDeviceBroker.postBluetoothA2dpDeviceConfigChange(device);
+                    return 0;
+                }
+            }
+        }
+        return 0;
+    }
+
+    /*package*/ int setWiredDeviceConnectionState(int type, @AudioService.ConnectionState int state,
+                                                  String address, String name, String caller) {
+        synchronized (mConnectedDevices) {
+            int delay = checkSendBecomingNoisyIntentInt(type, state, AudioSystem.DEVICE_NONE);
+            mDeviceBroker.postSetWiredDeviceConnectionState(
+                    new WiredDeviceConnectionState(type, state, address, name, caller),
+                    delay);
+            return delay;
+        }
+    }
+
+    /*package*/ int  setBluetoothHearingAidDeviceConnectionState(
+            @NonNull BluetoothDevice device, @AudioService.BtProfileConnectionState int state,
+            boolean suppressNoisyIntent, int musicDevice) {
+        int delay;
+        synchronized (mConnectedDevices) {
+            if (!suppressNoisyIntent) {
+                int intState = (state == BluetoothHearingAid.STATE_CONNECTED) ? 1 : 0;
+                delay = checkSendBecomingNoisyIntentInt(AudioSystem.DEVICE_OUT_HEARING_AID,
+                        intState, musicDevice);
+            } else {
+                delay = 0;
+            }
+            mDeviceBroker.postSetHearingAidConnectionState(state, device, delay);
+            return delay;
+        }
+    }
+
+
+    //-------------------------------------------------------------------
+    // Internal utilities
+
+    @GuardedBy("mConnectedDevices")
+    private void makeA2dpDeviceAvailable(String address, String name, String eventSource,
+            int a2dpCodec) {
+        // enable A2DP before notifying A2DP connection to avoid unnecessary processing in
+        // audio policy manager
+        AudioService.VolumeStreamState streamState =
+                mDeviceBroker.getStreamState(AudioSystem.STREAM_MUSIC);
+        mDeviceBroker.setBluetoothA2dpOnInt(true, eventSource);
+        AudioSystem.setDeviceConnectionState(AudioSystem.DEVICE_OUT_BLUETOOTH_A2DP,
+                AudioSystem.DEVICE_STATE_AVAILABLE, address, name, a2dpCodec);
+        // Reset A2DP suspend state each time a new sink is connected
+        AudioSystem.setParameters("A2dpSuspended=false");
+        mConnectedDevices.put(
+                DeviceInfo.makeDeviceListKey(AudioSystem.DEVICE_OUT_BLUETOOTH_A2DP, address),
+                new DeviceInfo(AudioSystem.DEVICE_OUT_BLUETOOTH_A2DP, name,
+                        address, a2dpCodec));
+        mDeviceBroker.postAccessoryPlugMediaUnmute(AudioSystem.DEVICE_OUT_BLUETOOTH_A2DP);
+        setCurrentAudioRouteNameIfPossible(name);
+    }
+
+    @GuardedBy("mConnectedDevices")
+    private void makeA2dpDeviceUnavailableNow(String address, int a2dpCodec) {
+        if (address == null) {
+            return;
+        }
+        mDeviceBroker.setAvrcpAbsoluteVolumeSupported(false);
+        AudioSystem.setDeviceConnectionState(AudioSystem.DEVICE_OUT_BLUETOOTH_A2DP,
+                AudioSystem.DEVICE_STATE_UNAVAILABLE, address, "", a2dpCodec);
+        mConnectedDevices.remove(
+                DeviceInfo.makeDeviceListKey(AudioSystem.DEVICE_OUT_BLUETOOTH_A2DP, address));
+        // Remove A2DP routes as well
+        setCurrentAudioRouteNameIfPossible(null);
+        if (mDockAddress == address) {
+            mDockAddress = null;
+        }
+    }
+
+    @GuardedBy("mConnectedDevices")
+    private void makeA2dpDeviceUnavailableLater(String address, int delayMs) {
+        // prevent any activity on the A2DP audio output to avoid unwanted
+        // reconnection of the sink.
+        AudioSystem.setParameters("A2dpSuspended=true");
+        // retrieve DeviceInfo before removing device
+        final String deviceKey =
+                DeviceInfo.makeDeviceListKey(AudioSystem.DEVICE_OUT_BLUETOOTH_A2DP, address);
+        final DeviceInfo deviceInfo = mConnectedDevices.get(deviceKey);
+        final int a2dpCodec = deviceInfo != null ? deviceInfo.mDeviceCodecFormat :
+                AudioSystem.AUDIO_FORMAT_DEFAULT;
+        // the device will be made unavailable later, so consider it disconnected right away
+        mConnectedDevices.remove(deviceKey);
+        // send the delayed message to make the device unavailable later
+        mDeviceBroker.setA2dpDockTimeout(address, a2dpCodec, delayMs);
+    }
+
+
+    @GuardedBy("mConnectedDevices")
+    private void makeA2dpSrcAvailable(String address) {
+        AudioSystem.setDeviceConnectionState(AudioSystem.DEVICE_IN_BLUETOOTH_A2DP,
+                AudioSystem.DEVICE_STATE_AVAILABLE, address, "",
+                AudioSystem.AUDIO_FORMAT_DEFAULT);
+        mConnectedDevices.put(
+                DeviceInfo.makeDeviceListKey(AudioSystem.DEVICE_IN_BLUETOOTH_A2DP, address),
+                new DeviceInfo(AudioSystem.DEVICE_IN_BLUETOOTH_A2DP, "",
+                        address, AudioSystem.AUDIO_FORMAT_DEFAULT));
+    }
+
+    @GuardedBy("mConnectedDevices")
+    private void makeA2dpSrcUnavailable(String address) {
+        AudioSystem.setDeviceConnectionState(AudioSystem.DEVICE_IN_BLUETOOTH_A2DP,
+                AudioSystem.DEVICE_STATE_UNAVAILABLE, address, "",
+                AudioSystem.AUDIO_FORMAT_DEFAULT);
+        mConnectedDevices.remove(
+                DeviceInfo.makeDeviceListKey(AudioSystem.DEVICE_IN_BLUETOOTH_A2DP, address));
+    }
+
+    @GuardedBy("mConnectedDevices")
+    private void makeHearingAidDeviceAvailable(String address, String name, String eventSource) {
+        final int hearingAidVolIndex = mDeviceBroker.getStreamState(AudioSystem.STREAM_MUSIC)
+                .getIndex(AudioSystem.DEVICE_OUT_HEARING_AID);
+        mDeviceBroker.postSetHearingAidVolumeIndex(hearingAidVolIndex, AudioSystem.STREAM_MUSIC);
+
+        AudioSystem.setDeviceConnectionState(AudioSystem.DEVICE_OUT_HEARING_AID,
+                AudioSystem.DEVICE_STATE_AVAILABLE, address, name,
+                AudioSystem.AUDIO_FORMAT_DEFAULT);
+        mConnectedDevices.put(
+                DeviceInfo.makeDeviceListKey(AudioSystem.DEVICE_OUT_HEARING_AID, address),
+                new DeviceInfo(AudioSystem.DEVICE_OUT_HEARING_AID, name,
+                        address, AudioSystem.AUDIO_FORMAT_DEFAULT));
+        mDeviceBroker.postAccessoryPlugMediaUnmute(AudioSystem.DEVICE_OUT_HEARING_AID);
+        mDeviceBroker.setDeviceVolume(
+                mDeviceBroker.getStreamState(AudioSystem.STREAM_MUSIC),
+                AudioSystem.DEVICE_OUT_HEARING_AID);
+        setCurrentAudioRouteNameIfPossible(name);
+    }
+
+    @GuardedBy("mConnectedDevices")
+    private void makeHearingAidDeviceUnavailable(String address) {
+        AudioSystem.setDeviceConnectionState(AudioSystem.DEVICE_OUT_HEARING_AID,
+                AudioSystem.DEVICE_STATE_UNAVAILABLE, address, "",
+                AudioSystem.AUDIO_FORMAT_DEFAULT);
+        mConnectedDevices.remove(
+                DeviceInfo.makeDeviceListKey(AudioSystem.DEVICE_OUT_HEARING_AID, address));
+        // Remove Hearing Aid routes as well
+        setCurrentAudioRouteNameIfPossible(null);
+    }
+
+    @GuardedBy("mConnectedDevices")
+    private void setCurrentAudioRouteNameIfPossible(String name) {
+        synchronized (mCurAudioRoutes) {
+            if (TextUtils.equals(mCurAudioRoutes.bluetoothName, name)) {
+                return;
+            }
+            if (name != null || !isCurrentDeviceConnected()) {
+                mCurAudioRoutes.bluetoothName = name;
+                mDeviceBroker.postReportNewRoutes();
+            }
+        }
+    }
+
+    @GuardedBy("mConnectedDevices")
+    private boolean isCurrentDeviceConnected() {
+        return mConnectedDevices.values().stream().anyMatch(deviceInfo ->
+            TextUtils.equals(deviceInfo.mDeviceName, mCurAudioRoutes.bluetoothName));
+    }
+
+    // Devices which removal triggers intent ACTION_AUDIO_BECOMING_NOISY. The intent is only
+    // sent if:
+    // - none of these devices are connected anymore after one is disconnected AND
+    // - the device being disconnected is actually used for music.
+    // Access synchronized on mConnectedDevices
+    private int mBecomingNoisyIntentDevices =
+            AudioSystem.DEVICE_OUT_WIRED_HEADSET | AudioSystem.DEVICE_OUT_WIRED_HEADPHONE
+                    | AudioSystem.DEVICE_OUT_ALL_A2DP | AudioSystem.DEVICE_OUT_HDMI
+                    | AudioSystem.DEVICE_OUT_ANLG_DOCK_HEADSET
+                    | AudioSystem.DEVICE_OUT_DGTL_DOCK_HEADSET
+                    | AudioSystem.DEVICE_OUT_ALL_USB | AudioSystem.DEVICE_OUT_LINE
+                    | AudioSystem.DEVICE_OUT_HEARING_AID;
+
+    // must be called before removing the device from mConnectedDevices
+    // musicDevice argument is used when not AudioSystem.DEVICE_NONE instead of querying
+    // from AudioSystem
+    @GuardedBy("mConnectedDevices")
+    private int checkSendBecomingNoisyIntentInt(int device, int state, int musicDevice) {
+        if (state != 0) {
+            return 0;
+        }
+        if ((device & mBecomingNoisyIntentDevices) == 0) {
+            return 0;
+        }
+        int delay = 0;
+        int devices = 0;
+        for (int i = 0; i < mConnectedDevices.size(); i++) {
+            int dev = mConnectedDevices.valueAt(i).mDeviceType;
+            if (((dev & AudioSystem.DEVICE_BIT_IN) == 0)
+                    && ((dev & mBecomingNoisyIntentDevices) != 0)) {
+                devices |= dev;
+            }
+        }
+        if (musicDevice == AudioSystem.DEVICE_NONE) {
+            musicDevice = mDeviceBroker.getDeviceForStream(AudioSystem.STREAM_MUSIC);
+        }
+        // ignore condition on device being actually used for music when in communication
+        // because music routing is altered in this case.
+        // also checks whether media routing if affected by a dynamic policy
+        if (((device == musicDevice) || mDeviceBroker.isInCommunication())
+                && (device == devices) && !mDeviceBroker.hasMediaDynamicPolicy()) {
+            mDeviceBroker.broadcastBecomingNoisy();
+            delay = 1000;
+        }
+
+        return delay;
+    }
+
+    // Intent "extra" data keys.
+    private static final String CONNECT_INTENT_KEY_PORT_NAME = "portName";
+    private static final String CONNECT_INTENT_KEY_STATE = "state";
+    private static final String CONNECT_INTENT_KEY_ADDRESS = "address";
+    private static final String CONNECT_INTENT_KEY_HAS_PLAYBACK = "hasPlayback";
+    private static final String CONNECT_INTENT_KEY_HAS_CAPTURE = "hasCapture";
+    private static final String CONNECT_INTENT_KEY_HAS_MIDI = "hasMIDI";
+    private static final String CONNECT_INTENT_KEY_DEVICE_CLASS = "class";
+
+    private void sendDeviceConnectionIntent(int device, int state, String address,
+                                            String deviceName) {
+        if (AudioService.DEBUG_DEVICES) {
+            Slog.i(TAG, "sendDeviceConnectionIntent(dev:0x" + Integer.toHexString(device)
+                    + " state:0x" + Integer.toHexString(state) + " address:" + address
+                    + " name:" + deviceName + ");");
+        }
+        Intent intent = new Intent();
+
+        switch(device) {
+            case AudioSystem.DEVICE_OUT_WIRED_HEADSET:
+                intent.setAction(Intent.ACTION_HEADSET_PLUG);
+                intent.putExtra("microphone", 1);
+                break;
+            case AudioSystem.DEVICE_OUT_WIRED_HEADPHONE:
+            case AudioSystem.DEVICE_OUT_LINE:
+                intent.setAction(Intent.ACTION_HEADSET_PLUG);
+                intent.putExtra("microphone", 0);
+                break;
+            case AudioSystem.DEVICE_OUT_USB_HEADSET:
+                intent.setAction(Intent.ACTION_HEADSET_PLUG);
+                intent.putExtra("microphone",
+                        AudioSystem.getDeviceConnectionState(AudioSystem.DEVICE_IN_USB_HEADSET, "")
+                                == AudioSystem.DEVICE_STATE_AVAILABLE ? 1 : 0);
+                break;
+            case AudioSystem.DEVICE_IN_USB_HEADSET:
+                if (AudioSystem.getDeviceConnectionState(AudioSystem.DEVICE_OUT_USB_HEADSET, "")
+                        == AudioSystem.DEVICE_STATE_AVAILABLE) {
+                    intent.setAction(Intent.ACTION_HEADSET_PLUG);
+                    intent.putExtra("microphone", 1);
+                } else {
+                    // do not send ACTION_HEADSET_PLUG when only the input side is seen as changing
+                    return;
+                }
+                break;
+            case AudioSystem.DEVICE_OUT_HDMI:
+            case AudioSystem.DEVICE_OUT_HDMI_ARC:
+                configureHdmiPlugIntent(intent, state);
+                break;
+        }
+
+        if (intent.getAction() == null) {
+            return;
+        }
+
+        intent.putExtra(CONNECT_INTENT_KEY_STATE, state);
+        intent.putExtra(CONNECT_INTENT_KEY_ADDRESS, address);
+        intent.putExtra(CONNECT_INTENT_KEY_PORT_NAME, deviceName);
+
+        intent.addFlags(Intent.FLAG_RECEIVER_REGISTERED_ONLY);
+
+        final long ident = Binder.clearCallingIdentity();
+        try {
+            ActivityManager.broadcastStickyIntent(intent, UserHandle.USER_ALL);
+        } finally {
+            Binder.restoreCallingIdentity(ident);
+        }
+    }
+
+    private void updateAudioRoutes(int device, int state) {
+        int connType = 0;
+
+        switch (device) {
+            case AudioSystem.DEVICE_OUT_WIRED_HEADSET:
+                connType = AudioRoutesInfo.MAIN_HEADSET;
+                break;
+            case AudioSystem.DEVICE_OUT_WIRED_HEADPHONE:
+            case AudioSystem.DEVICE_OUT_LINE:
+                connType = AudioRoutesInfo.MAIN_HEADPHONES;
+                break;
+            case AudioSystem.DEVICE_OUT_HDMI:
+            case AudioSystem.DEVICE_OUT_HDMI_ARC:
+                connType = AudioRoutesInfo.MAIN_HDMI;
+                break;
+            case AudioSystem.DEVICE_OUT_USB_DEVICE:
+            case AudioSystem.DEVICE_OUT_USB_HEADSET:
+                connType = AudioRoutesInfo.MAIN_USB;
+                break;
+        }
+
+        synchronized (mCurAudioRoutes) {
+            if (connType == 0) {
+                return;
+            }
+            int newConn = mCurAudioRoutes.mainType;
+            if (state != 0) {
+                newConn |= connType;
+            } else {
+                newConn &= ~connType;
+            }
+            if (newConn != mCurAudioRoutes.mainType) {
+                mCurAudioRoutes.mainType = newConn;
+                mDeviceBroker.postReportNewRoutes();
+            }
+        }
+    }
+
+    private void configureHdmiPlugIntent(Intent intent, @AudioService.ConnectionState int state) {
+        intent.setAction(AudioManager.ACTION_HDMI_AUDIO_PLUG);
+        intent.putExtra(AudioManager.EXTRA_AUDIO_PLUG_STATE, state);
+        if (state != AudioService.CONNECTION_STATE_CONNECTED) {
+            return;
+        }
+        ArrayList<AudioPort> ports = new ArrayList<AudioPort>();
+        int[] portGeneration = new int[1];
+        int status = AudioSystem.listAudioPorts(ports, portGeneration);
+        if (status != AudioManager.SUCCESS) {
+            Log.e(TAG, "listAudioPorts error " + status + " in configureHdmiPlugIntent");
+            return;
+        }
+        for (AudioPort port : ports) {
+            if (!(port instanceof AudioDevicePort)) {
+                continue;
+            }
+            final AudioDevicePort devicePort = (AudioDevicePort) port;
+            if (devicePort.type() != AudioManager.DEVICE_OUT_HDMI
+                    && devicePort.type() != AudioManager.DEVICE_OUT_HDMI_ARC) {
+                continue;
+            }
+            // found an HDMI port: format the list of supported encodings
+            int[] formats = AudioFormat.filterPublicFormats(devicePort.formats());
+            if (formats.length > 0) {
+                ArrayList<Integer> encodingList = new ArrayList(1);
+                for (int format : formats) {
+                    // a format in the list can be 0, skip it
+                    if (format != AudioFormat.ENCODING_INVALID) {
+                        encodingList.add(format);
+                    }
+                }
+                final int[] encodingArray = encodingList.stream().mapToInt(i -> i).toArray();
+                intent.putExtra(AudioManager.EXTRA_ENCODINGS, encodingArray);
+            }
+            // find the maximum supported number of channels
+            int maxChannels = 0;
+            for (int mask : devicePort.channelMasks()) {
+                int channelCount = AudioFormat.channelCountFromOutChannelMask(mask);
+                if (channelCount > maxChannels) {
+                    maxChannels = channelCount;
+                }
+            }
+            intent.putExtra(AudioManager.EXTRA_MAX_CHANNEL_COUNT, maxChannels);
+        }
+    }
+}
diff --git a/services/core/java/com/android/server/audio/AudioService.java b/services/core/java/com/android/server/audio/AudioService.java
index de389bc..df33bf2 100644
--- a/services/core/java/com/android/server/audio/AudioService.java
+++ b/services/core/java/com/android/server/audio/AudioService.java
@@ -27,6 +27,7 @@
 import static android.provider.Settings.Secure.VOLUME_HUSH_VIBRATE;
 
 import android.Manifest;
+import android.annotation.IntDef;
 import android.annotation.NonNull;
 import android.annotation.Nullable;
 import android.app.ActivityManager;
@@ -35,14 +36,9 @@
 import android.app.AppOpsManager;
 import android.app.IUidObserver;
 import android.app.NotificationManager;
-import android.bluetooth.BluetoothA2dp;
 import android.bluetooth.BluetoothAdapter;
-import android.bluetooth.BluetoothClass;
-import android.bluetooth.BluetoothCodecConfig;
-import android.bluetooth.BluetoothCodecStatus;
 import android.bluetooth.BluetoothDevice;
 import android.bluetooth.BluetoothHeadset;
-import android.bluetooth.BluetoothHearingAid;
 import android.bluetooth.BluetoothProfile;
 import android.content.BroadcastReceiver;
 import android.content.ComponentName;
@@ -65,14 +61,12 @@
 import android.hardware.hdmi.HdmiTvClient;
 import android.hardware.usb.UsbManager;
 import android.media.AudioAttributes;
-import android.media.AudioDevicePort;
 import android.media.AudioFocusInfo;
 import android.media.AudioFocusRequest;
 import android.media.AudioFormat;
 import android.media.AudioManager;
 import android.media.AudioManagerInternal;
 import android.media.AudioPlaybackConfiguration;
-import android.media.AudioPort;
 import android.media.AudioRecordingConfiguration;
 import android.media.AudioRoutesInfo;
 import android.media.AudioSystem;
@@ -104,7 +98,6 @@
 import android.os.Looper;
 import android.os.Message;
 import android.os.PowerManager;
-import android.os.RemoteCallbackList;
 import android.os.RemoteException;
 import android.os.SystemClock;
 import android.os.SystemProperties;
@@ -120,8 +113,6 @@
 import android.telecom.TelecomManager;
 import android.text.TextUtils;
 import android.util.AndroidRuntimeException;
-import android.util.ArrayMap;
-import android.util.ArraySet;
 import android.util.IntArray;
 import android.util.Log;
 import android.util.MathUtils;
@@ -137,10 +128,8 @@
 import com.android.server.EventLogTags;
 import com.android.server.LocalServices;
 import com.android.server.SystemService;
-import com.android.server.audio.AudioServiceEvents.ForceUseEvent;
 import com.android.server.audio.AudioServiceEvents.PhoneStateEvent;
 import com.android.server.audio.AudioServiceEvents.VolumeEvent;
-import com.android.server.audio.AudioServiceEvents.WiredDevConnectEvent;
 import com.android.server.pm.UserManagerService;
 import com.android.server.wm.ActivityTaskManagerInternal;
 
@@ -150,6 +139,8 @@
 import java.io.FileDescriptor;
 import java.io.IOException;
 import java.io.PrintWriter;
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
 import java.lang.reflect.Field;
 import java.util.ArrayList;
 import java.util.Collection;
@@ -175,19 +166,20 @@
         implements AccessibilityManager.TouchExplorationStateChangeListener,
             AccessibilityManager.AccessibilityServicesStateChangeListener {
 
-    private static final String TAG = "AudioService";
+    private static final String TAG = "AS.AudioService";
 
     /** Debug audio mode */
-    protected static final boolean DEBUG_MODE = Log.isLoggable(TAG + ".MOD", Log.DEBUG);
+    protected static final boolean DEBUG_MODE = false;
 
     /** Debug audio policy feature */
-    protected static final boolean DEBUG_AP = Log.isLoggable(TAG + ".AP", Log.DEBUG);
+    protected static final boolean DEBUG_AP = false;
 
     /** Debug volumes */
-    protected static final boolean DEBUG_VOL = Log.isLoggable(TAG + ".VOL", Log.DEBUG);
+    protected static final boolean DEBUG_VOL = false;
 
     /** debug calls to devices APIs */
-    protected static final boolean DEBUG_DEVICES = Log.isLoggable(TAG + ".DEVICES", Log.DEBUG);
+    protected static final boolean DEBUG_DEVICES = false;
+
     /** How long to delay before persisting a change in volume/ringer mode. */
     private static final int PERSIST_DELAY = 500;
 
@@ -213,11 +205,11 @@
         return mPlatformType == AudioSystem.PLATFORM_VOICE;
     }
 
-    private boolean isPlatformTelevision() {
+    /*package*/ boolean isPlatformTelevision() {
         return mPlatformType == AudioSystem.PLATFORM_TELEVISION;
     }
 
-    private boolean isPlatformAutomotive() {
+    /*package*/ boolean isPlatformAutomotive() {
         return mContext.getPackageManager().hasSystemFeature(PackageManager.FEATURE_AUTOMOTIVE);
     }
 
@@ -242,52 +234,40 @@
     private static final int MSG_SET_FORCE_USE = 8;
     private static final int MSG_BT_HEADSET_CNCT_FAILED = 9;
     private static final int MSG_SET_ALL_VOLUMES = 10;
-    private static final int MSG_REPORT_NEW_ROUTES = 12;
-    private static final int MSG_SET_FORCE_BT_A2DP_USE = 13;
-    private static final int MSG_CHECK_MUSIC_ACTIVE = 14;
-    private static final int MSG_BROADCAST_AUDIO_BECOMING_NOISY = 15;
-    private static final int MSG_CONFIGURE_SAFE_MEDIA_VOLUME = 16;
-    private static final int MSG_CONFIGURE_SAFE_MEDIA_VOLUME_FORCED = 17;
-    private static final int MSG_PERSIST_SAFE_VOLUME_STATE = 18;
-    private static final int MSG_BROADCAST_BT_CONNECTION_STATE = 19;
-    private static final int MSG_UNLOAD_SOUND_EFFECTS = 20;
-    private static final int MSG_SYSTEM_READY = 21;
-    private static final int MSG_PERSIST_MUSIC_ACTIVE_MS = 22;
-    private static final int MSG_UNMUTE_STREAM = 24;
-    private static final int MSG_DYN_POLICY_MIX_STATE_UPDATE = 25;
-    private static final int MSG_INDICATE_SYSTEM_READY = 26;
-    private static final int MSG_ACCESSORY_PLUG_MEDIA_UNMUTE = 27;
-    private static final int MSG_NOTIFY_VOL_EVENT = 28;
-    private static final int MSG_DISPATCH_AUDIO_SERVER_STATE = 29;
-    private static final int MSG_ENABLE_SURROUND_FORMATS = 30;
+    private static final int MSG_CHECK_MUSIC_ACTIVE = 11;
+    private static final int MSG_CONFIGURE_SAFE_MEDIA_VOLUME = 12;
+    private static final int MSG_CONFIGURE_SAFE_MEDIA_VOLUME_FORCED = 13;
+    private static final int MSG_PERSIST_SAFE_VOLUME_STATE = 14;
+    private static final int MSG_UNLOAD_SOUND_EFFECTS = 15;
+    private static final int MSG_SYSTEM_READY = 16;
+    private static final int MSG_PERSIST_MUSIC_ACTIVE_MS = 17;
+    private static final int MSG_UNMUTE_STREAM = 18;
+    private static final int MSG_DYN_POLICY_MIX_STATE_UPDATE = 19;
+    private static final int MSG_INDICATE_SYSTEM_READY = 20;
+    private static final int MSG_ACCESSORY_PLUG_MEDIA_UNMUTE = 21;
+    private static final int MSG_NOTIFY_VOL_EVENT = 22;
+    private static final int MSG_DISPATCH_AUDIO_SERVER_STATE = 23;
+    private static final int MSG_ENABLE_SURROUND_FORMATS = 24;
     // start of messages handled under wakelock
     //   these messages can only be queued, i.e. sent with queueMsgUnderWakeLock(),
     //   and not with sendMsg(..., ..., SENDMSG_QUEUE, ...)
-    private static final int MSG_SET_WIRED_DEVICE_CONNECTION_STATE = 100;
-    private static final int MSG_SET_A2DP_SRC_CONNECTION_STATE = 101;
-    private static final int MSG_SET_A2DP_SINK_CONNECTION_STATE = 102;
-    private static final int MSG_A2DP_DEVICE_CONFIG_CHANGE = 103;
-    private static final int MSG_DISABLE_AUDIO_FOR_UID = 104;
-    private static final int MSG_SET_HEARING_AID_CONNECTION_STATE = 105;
-    private static final int MSG_BTA2DP_DOCK_TIMEOUT = 106;
-    private static final int MSG_A2DP_ACTIVE_DEVICE_CHANGE = 107;
+    private static final int MSG_DISABLE_AUDIO_FOR_UID = 100;
     // end of messages handled under wakelock
 
-    private static final int BTA2DP_DOCK_TIMEOUT_MILLIS = 8000;
-    // Timeout for connection to bluetooth headset service
-    private static final int BT_HEADSET_CNCT_TIMEOUT_MS = 3000;
-
     // retry delay in case of failure to indicate system ready to AudioFlinger
     private static final int INDICATE_SYSTEM_READY_RETRY_DELAY_MS = 1000;
 
-    private static final int BT_HEARING_AID_GAIN_MIN = -128;
-
     /** @see AudioSystemThread */
     private AudioSystemThread mAudioSystemThread;
     /** @see AudioHandler */
     private AudioHandler mAudioHandler;
     /** @see VolumeStreamState */
     private VolumeStreamState[] mStreamStates;
+
+    /*package*/ VolumeStreamState getStreamState(int stream) {
+        return mStreamStates[stream];
+    }
+
     private SettingsObserver mSettingsObserver;
 
     private int mMode = AudioSystem.MODE_NORMAL;
@@ -477,135 +457,13 @@
     private final UserRestrictionsListener mUserRestrictionsListener =
             new AudioServiceUserRestrictionsListener();
 
-    // Devices currently connected
-    // Use makeDeviceListKey() to make a unique key for this list.
-    private class DeviceListSpec {
-        int mDeviceType;
-        String mDeviceName;
-        String mDeviceAddress;
-        int mDeviceCodecFormat;
-
-        DeviceListSpec(int deviceType, String deviceName, String deviceAddress,
-                int deviceCodecFormat) {
-            mDeviceType = deviceType;
-            mDeviceName = deviceName;
-            mDeviceAddress = deviceAddress;
-            mDeviceCodecFormat = deviceCodecFormat;
-        }
-
-        public String toString() {
-            return "[type:0x" + Integer.toHexString(mDeviceType) + " name:" + mDeviceName
-                    + " address:" + mDeviceAddress
-                    + " codec: " + Integer.toHexString(mDeviceCodecFormat) + "]";
-        }
-    }
-
-    // Generate a unique key for the mConnectedDevices List by composing the device "type"
-    // and the "address" associated with a specific instance of that device type
-    private String makeDeviceListKey(int device, String deviceAddress) {
-        return "0x" + Integer.toHexString(device) + ":" + deviceAddress;
-    }
-
-    private final ArrayMap<String, DeviceListSpec> mConnectedDevices = new ArrayMap<>();
-
-    private class BluetoothA2dpDeviceInfo {
-        BluetoothDevice mBtDevice;
-        int mVolume;
-        int mCodec;
-
-        BluetoothA2dpDeviceInfo(BluetoothDevice btDevice) {
-            this(btDevice, -1, AudioSystem.AUDIO_FORMAT_DEFAULT);
-        }
-
-        BluetoothA2dpDeviceInfo(BluetoothDevice btDevice,
-                     int volume, int codec) {
-            mBtDevice = btDevice;
-            mVolume = volume;
-            mCodec = codec;
-        }
-
-        public BluetoothDevice getBtDevice() {
-            return mBtDevice;
-        }
-
-        public int getVolume() {
-            return mVolume;
-        }
-
-        public int getCodec() {
-            return mCodec;
-        }
-    }
-
-    private int mapBluetoothCodecToAudioFormat(int btCodecType) {
-        switch (btCodecType) {
-            case BluetoothCodecConfig.SOURCE_CODEC_TYPE_SBC:
-                return AudioSystem.AUDIO_FORMAT_SBC;
-            case BluetoothCodecConfig.SOURCE_CODEC_TYPE_AAC:
-                return AudioSystem.AUDIO_FORMAT_AAC;
-            case BluetoothCodecConfig.SOURCE_CODEC_TYPE_APTX:
-                return AudioSystem.AUDIO_FORMAT_APTX;
-            case BluetoothCodecConfig.SOURCE_CODEC_TYPE_APTX_HD:
-                return AudioSystem.AUDIO_FORMAT_APTX_HD;
-            case BluetoothCodecConfig.SOURCE_CODEC_TYPE_LDAC:
-                return AudioSystem.AUDIO_FORMAT_LDAC;
-            default:
-                return AudioSystem.AUDIO_FORMAT_DEFAULT;
-        }
-    }
-
-    // Forced device usage for communications
-    private int mForcedUseForComm;
-    private int mForcedUseForCommExt; // External state returned by getters: always consistent
-                                      // with requests by setters
-
     // List of binder death handlers for setMode() client processes.
     // The last process to have called setMode() is at the top of the list.
-    private final ArrayList <SetModeDeathHandler> mSetModeDeathHandlers = new ArrayList <SetModeDeathHandler>();
-
-    // List of clients having issued a SCO start request
-    private final ArrayList <ScoClient> mScoClients = new ArrayList <ScoClient>();
-
-    // BluetoothHeadset API to control SCO connection
-    private BluetoothHeadset mBluetoothHeadset;
-
-    // Bluetooth headset device
-    private BluetoothDevice mBluetoothHeadsetDevice;
-
-    // Indicate if SCO audio connection is currently active and if the initiator is
-    // audio service (internal) or bluetooth headset (external)
-    private int mScoAudioState;
-    // SCO audio state is not active
-    private static final int SCO_STATE_INACTIVE = 0;
-    // SCO audio activation request waiting for headset service to connect
-    private static final int SCO_STATE_ACTIVATE_REQ = 1;
-    // SCO audio state is active or starting due to a request from AudioManager API
-    private static final int SCO_STATE_ACTIVE_INTERNAL = 3;
-    // SCO audio deactivation request waiting for headset service to connect
-    private static final int SCO_STATE_DEACTIVATE_REQ = 4;
-    // SCO audio deactivation in progress, waiting for Bluetooth audio intent
-    private static final int SCO_STATE_DEACTIVATING = 5;
-
-    // SCO audio state is active due to an action in BT handsfree (either voice recognition or
-    // in call audio)
-    private static final int SCO_STATE_ACTIVE_EXTERNAL = 2;
-
-    // Indicates the mode used for SCO audio connection. The mode is virtual call if the request
-    // originated from an app targeting an API version before JB MR2 and raw audio after that.
-    private int mScoAudioMode;
-    // SCO audio mode is undefined
-    private static final int SCO_MODE_UNDEFINED = -1;
-    // SCO audio mode is virtual voice call (BluetoothHeadset.startScoUsingVirtualVoiceCall())
-    private static final int SCO_MODE_VIRTUAL_CALL = 0;
-    // SCO audio mode is raw audio (BluetoothHeadset.connectAudio())
-    private static final int SCO_MODE_RAW = 1;
-    // SCO audio mode is Voice Recognition (BluetoothHeadset.startVoiceRecognition())
-    private static final int SCO_MODE_VR = 2;
-
-    private static final int SCO_MODE_MAX = 2;
-
-    // Current connection state indicated by bluetooth headset
-    private int mScoConnectionState;
+    // package-private so it can be accessed in AudioDeviceBroker.getSetModeDeathHandlers
+    //TODO candidate to be moved to separate class that handles synchronization
+    @GuardedBy("mDeviceBroker.mSetModeLock")
+    /*package*/ final ArrayList<SetModeDeathHandler> mSetModeDeathHandlers =
+            new ArrayList<SetModeDeathHandler>();
 
     // true if boot sequence has been completed
     private boolean mSystemReady;
@@ -636,15 +494,6 @@
     // Used to play ringtones outside system_server
     private volatile IRingtonePlayer mRingtonePlayer;
 
-    // Request to override default use of A2DP for media.
-    private boolean mBluetoothA2dpEnabled;
-    private final Object mBluetoothA2dpEnabledLock = new Object();
-
-    // Monitoring of audio routes.  Protected by mCurAudioRoutes.
-    final AudioRoutesInfo mCurAudioRoutes = new AudioRoutesInfo();
-    final RemoteCallbackList<IAudioRoutesObserver> mRoutesObservers
-            = new RemoteCallbackList<IAudioRoutesObserver>();
-
     // Devices for which the volume is fixed and VolumePanel slider should be disabled
     int mFixedVolumeDevices = AudioSystem.DEVICE_OUT_HDMI |
             AudioSystem.DEVICE_OUT_DGTL_DOCK_HEADSET |
@@ -669,17 +518,6 @@
 
     private final MediaFocusControl mMediaFocusControl;
 
-    // Reference to BluetoothA2dp to query for volume.
-    private BluetoothHearingAid mHearingAid;
-    // lock always taken synchronized on mConnectedDevices
-    private final Object mHearingAidLock = new Object();
-    // Reference to BluetoothA2dp to query for AbsoluteVolume.
-    private BluetoothA2dp mA2dp;
-    // lock always taken synchronized on mConnectedDevices
-    private final Object mA2dpAvrcpLock = new Object();
-    // If absolute volume is supported in AVRCP device
-    private boolean mAvrcpAbsVolSupported = false;
-
     // Pre-scale for Bluetooth Absolute Volume
     private float[] mPrescaleAbsoluteVolume = new float[] {
         0.5f,    // Pre-scale for index 1
@@ -687,8 +525,6 @@
         0.85f,   // Pre-scale for index 3
     };
 
-    private static Long mLastDeviceConnectMsgTime = new Long(0);
-
     private NotificationManager mNm;
     private AudioManagerInternal.RingerModeDelegate mRingerModeDelegate;
     private VolumePolicy mVolumePolicy = VolumePolicy.DEFAULT;
@@ -705,15 +541,6 @@
     @GuardedBy("mSettingsLock")
     private int mAssistantUid;
 
-    // Intent "extra" data keys.
-    public static final String CONNECT_INTENT_KEY_PORT_NAME = "portName";
-    public static final String CONNECT_INTENT_KEY_STATE = "state";
-    public static final String CONNECT_INTENT_KEY_ADDRESS = "address";
-    public static final String CONNECT_INTENT_KEY_HAS_PLAYBACK = "hasPlayback";
-    public static final String CONNECT_INTENT_KEY_HAS_CAPTURE = "hasCapture";
-    public static final String CONNECT_INTENT_KEY_HAS_MIDI = "hasMIDI";
-    public static final String CONNECT_INTENT_KEY_DEVICE_CLASS = "class";
-
     // Defines the format for the connection "address" for ALSA devices
     public static String makeAlsaAddressString(int card, int device) {
         return "card=" + card + ";device=" + device + ";";
@@ -858,8 +685,6 @@
         sSoundEffectVolumeDb = context.getResources().getInteger(
                 com.android.internal.R.integer.config_soundEffectVolumeDb);
 
-        mForcedUseForComm = AudioSystem.FORCE_NONE;
-
         createAudioSystemThread();
 
         AudioSystem.setErrorCallback(mAudioSystemCallback);
@@ -886,6 +711,8 @@
         mUseFixedVolume = mContext.getResources().getBoolean(
                 com.android.internal.R.bool.config_useFixedVolume);
 
+        mDeviceBroker = new AudioDeviceBroker(mContext, this);
+
         // must be called before readPersistedSettings() which needs a valid mStreamVolumeAlias[]
         // array initialized by updateStreamVolumeAlias()
         updateStreamVolumeAlias(false /*updateVolumes*/, TAG);
@@ -988,23 +815,7 @@
         sendMsg(mAudioHandler, MSG_LOAD_SOUND_EFFECTS, SENDMSG_QUEUE,
                 0, 0, null, 0);
 
-        mScoConnectionState = AudioManager.SCO_AUDIO_STATE_ERROR;
-        resetBluetoothSco();
-        getBluetoothHeadset();
-        //FIXME: this is to maintain compatibility with deprecated intent
-        // AudioManager.ACTION_SCO_AUDIO_STATE_CHANGED. Remove when appropriate.
-        Intent newIntent = new Intent(AudioManager.ACTION_SCO_AUDIO_STATE_CHANGED);
-        newIntent.putExtra(AudioManager.EXTRA_SCO_AUDIO_STATE,
-                AudioManager.SCO_AUDIO_STATE_DISCONNECTED);
-        sendStickyBroadcastToAll(newIntent);
-
-        BluetoothAdapter adapter = BluetoothAdapter.getDefaultAdapter();
-        if (adapter != null) {
-            adapter.getProfileProxy(mContext, mBluetoothProfileServiceListener,
-                                    BluetoothProfile.A2DP);
-            adapter.getProfileProxy(mContext, mBluetoothProfileServiceListener,
-                                    BluetoothProfile.HEARING_AID);
-        }
+        mDeviceBroker.onSystemReady();
 
         if (mContext.getPackageManager().hasSystemFeature(PackageManager.FEATURE_HDMI_CEC)) {
             synchronized (mHdmiClientLock) {
@@ -1065,39 +876,22 @@
 
         readAndSetLowRamDevice();
 
-        // Restore device connection states
-        synchronized (mConnectedDevices) {
-            for (int i = 0; i < mConnectedDevices.size(); i++) {
-                DeviceListSpec spec = mConnectedDevices.valueAt(i);
-                AudioSystem.setDeviceConnectionState(
-                                                spec.mDeviceType,
-                                                AudioSystem.DEVICE_STATE_AVAILABLE,
-                                                spec.mDeviceAddress,
-                                                spec.mDeviceName,
-                                                spec.mDeviceCodecFormat);
-            }
-        }
+        // Restore device connection states, BT state
+        mDeviceBroker.onAudioServerDied();
+
         // Restore call state
         if (AudioSystem.setPhoneState(mMode) ==  AudioSystem.AUDIO_STATUS_OK) {
             mModeLogger.log(new AudioEventLogger.StringEvent(
                 "onAudioServerDied causes setPhoneState(" + AudioSystem.modeToString(mMode) + ")"));
         }
 
-        // Restore forced usage for communications and record
-        mForceUseLogger.log(new ForceUseEvent(AudioSystem.FOR_COMMUNICATION, mForcedUseForComm,
-                "onAudioServerDied"));
-        AudioSystem.setForceUse(AudioSystem.FOR_COMMUNICATION, mForcedUseForComm);
-        mForceUseLogger.log(new ForceUseEvent(AudioSystem.FOR_RECORD, mForcedUseForComm,
-                "onAudioServerDied"));
-        AudioSystem.setForceUse(AudioSystem.FOR_RECORD, mForcedUseForComm);
         final int forSys;
         synchronized (mSettingsLock) {
             forSys = mCameraSoundForced ?
                     AudioSystem.FORCE_SYSTEM_ENFORCED : AudioSystem.FORCE_NONE;
         }
-        mForceUseLogger.log(new ForceUseEvent(AudioSystem.FOR_SYSTEM, forSys,
-                "onAudioServerDied"));
-        AudioSystem.setForceUse(AudioSystem.FOR_SYSTEM, forSys);
+
+        mDeviceBroker.setForceUse_Async(AudioSystem.FOR_SYSTEM, forSys, "onAudioServerDied");
 
         // Restore stream volumes
         int numStreamTypes = AudioSystem.getNumStreamTypes();
@@ -1120,20 +914,10 @@
             RotationHelper.updateOrientation();
         }
 
-        synchronized (mBluetoothA2dpEnabledLock) {
-            final int forMed = mBluetoothA2dpEnabled ?
-                    AudioSystem.FORCE_NONE : AudioSystem.FORCE_NO_BT_A2DP;
-            mForceUseLogger.log(new ForceUseEvent(AudioSystem.FOR_MEDIA, forMed,
-                    "onAudioServerDied"));
-            AudioSystem.setForceUse(AudioSystem.FOR_MEDIA, forMed);
-        }
-
         synchronized (mSettingsLock) {
             final int forDock = mDockAudioMediaEnabled ?
                     AudioSystem.FORCE_ANALOG_DOCK : AudioSystem.FORCE_NONE;
-            mForceUseLogger.log(new ForceUseEvent(AudioSystem.FOR_DOCK, forDock,
-                    "onAudioServerDied"));
-            AudioSystem.setForceUse(AudioSystem.FOR_DOCK, forDock);
+            mDeviceBroker.setForceUse_Async(AudioSystem.FOR_DOCK, forDock, "onAudioServerDied");
             sendEncodedSurroundMode(mContentResolver, "onAudioServerDied");
             sendEnabledSurroundFormats(mContentResolver, true);
             updateAssistantUId(true);
@@ -1209,6 +993,45 @@
         }
     }
 
+    /**
+     * Called from AudioDeviceBroker when DEVICE_OUT_HDMI is connected or disconnected.
+     */
+    /*package*/ void checkVolumeCecOnHdmiConnection(int state, String caller) {
+        if (state != 0) {
+            // DEVICE_OUT_HDMI is now connected
+            if ((AudioSystem.DEVICE_OUT_HDMI & mSafeMediaVolumeDevices) != 0) {
+                sendMsg(mAudioHandler,
+                        MSG_CHECK_MUSIC_ACTIVE,
+                        SENDMSG_REPLACE,
+                        0,
+                        0,
+                        caller,
+                        MUSIC_ACTIVE_POLL_PERIOD_MS);
+            }
+
+            if (isPlatformTelevision()) {
+                mFixedVolumeDevices |= AudioSystem.DEVICE_OUT_HDMI;
+                checkAllFixedVolumeDevices();
+                synchronized (mHdmiClientLock) {
+                    if (mHdmiManager != null && mHdmiPlaybackClient != null) {
+                        mHdmiCecSink = false;
+                        mHdmiPlaybackClient.queryDisplayStatus(mHdmiDisplayStatusCallback);
+                    }
+                }
+            }
+            sendEnabledSurroundFormats(mContentResolver, true);
+        } else {
+            // DEVICE_OUT_HDMI disconnected
+            if (isPlatformTelevision()) {
+                synchronized (mHdmiClientLock) {
+                    if (mHdmiManager != null) {
+                        mHdmiCecSink = false;
+                    }
+                }
+            }
+        }
+    }
+
     private void checkAllFixedVolumeDevices()
     {
         int numStreamTypes = AudioSystem.getNumStreamTypes();
@@ -1373,7 +1196,7 @@
 
     private void sendEncodedSurroundMode(ContentResolver cr, String eventSource)
     {
-        int encodedSurroundMode = Settings.Global.getInt(
+        final int encodedSurroundMode = Settings.Global.getInt(
                 cr, Settings.Global.ENCODED_SURROUND_OUTPUT,
                 Settings.Global.ENCODED_SURROUND_OUTPUT_AUTO);
         sendEncodedSurroundMode(encodedSurroundMode, eventSource);
@@ -1402,13 +1225,8 @@
                 break;
         }
         if (forceSetting != AudioSystem.NUM_FORCE_CONFIG) {
-            sendMsg(mAudioHandler,
-                    MSG_SET_FORCE_USE,
-                    SENDMSG_QUEUE,
-                    AudioSystem.FOR_ENCODED_SURROUND,
-                    forceSetting,
-                    eventSource,
-                    0);
+            mDeviceBroker.setForceUse_Async(AudioSystem.FOR_ENCODED_SURROUND, forceSetting,
+                    eventSource);
         }
     }
 
@@ -1632,7 +1450,7 @@
                 + ", flags=" + flags + ", caller=" + caller
                 + ", volControlStream=" + mVolumeControlStream
                 + ", userSelect=" + mUserSelectedVolumeControlStream);
-        mVolumeLogger.log(new VolumeEvent(VolumeEvent.VOL_ADJUST_SUGG_VOL, suggestedStreamType,
+        sVolumeLogger.log(new VolumeEvent(VolumeEvent.VOL_ADJUST_SUGG_VOL, suggestedStreamType,
                 direction/*val1*/, flags/*val2*/, new StringBuilder(callingPackage)
                         .append("/").append(caller).append(" uid:").append(uid).toString()));
         final int streamType;
@@ -1690,7 +1508,7 @@
                     + "CHANGE_ACCESSIBILITY_VOLUME / callingPackage=" + callingPackage);
             return;
         }
-        mVolumeLogger.log(new VolumeEvent(VolumeEvent.VOL_ADJUST_STREAM_VOL, streamType,
+        sVolumeLogger.log(new VolumeEvent(VolumeEvent.VOL_ADJUST_STREAM_VOL, streamType,
                 direction/*val1*/, flags/*val2*/, callingPackage));
         adjustStreamVolume(streamType, direction, flags, callingPackage, callingPackage,
                 Binder.getCallingUid());
@@ -1871,16 +1689,18 @@
             if (streamTypeAlias == AudioSystem.STREAM_MUSIC &&
                 (device & AudioSystem.DEVICE_OUT_ALL_A2DP) != 0 &&
                 (flags & AudioManager.FLAG_BLUETOOTH_ABS_VOLUME) == 0) {
-                synchronized (mA2dpAvrcpLock) {
-                    if (mA2dp != null && mAvrcpAbsVolSupported) {
-                        mA2dp.setAvrcpAbsoluteVolume(newIndex / 10);
-                    }
+                if (DEBUG_VOL) {
+                    Log.d(TAG, "adjustSreamVolume: postSetAvrcpAbsoluteVolumeIndex index="
+                            + newIndex + "stream=" + streamType);
                 }
+                mDeviceBroker.postSetAvrcpAbsoluteVolumeIndex(newIndex);
             }
 
             // Check if volume update should be send to Hearing Aid
             if ((device & AudioSystem.DEVICE_OUT_HEARING_AID) != 0) {
-                setHearingAidVolume(newIndex, streamType);
+                Log.i(TAG, "adjustSreamVolume postSetHearingAidVolumeIndex index=" + newIndex
+                        + " stream=" + streamType);
+                mDeviceBroker.postSetHearingAidVolumeIndex(newIndex, streamType);
             }
 
             // Check if volume update should be sent to Hdmi system audio.
@@ -2052,7 +1872,7 @@
                     + " MODIFY_PHONE_STATE  callingPackage=" + callingPackage);
             return;
         }
-        mVolumeLogger.log(new VolumeEvent(VolumeEvent.VOL_SET_STREAM_VOL, streamType,
+        sVolumeLogger.log(new VolumeEvent(VolumeEvent.VOL_SET_STREAM_VOL, streamType,
                 index/*val1*/, flags/*val2*/, callingPackage));
         setStreamVolume(streamType, index, flags, callingPackage, callingPackage,
                 Binder.getCallingUid());
@@ -2127,18 +1947,20 @@
 
             index = rescaleIndex(index * 10, streamType, streamTypeAlias);
 
-            if (streamTypeAlias == AudioSystem.STREAM_MUSIC &&
-                (device & AudioSystem.DEVICE_OUT_ALL_A2DP) != 0 &&
-                (flags & AudioManager.FLAG_BLUETOOTH_ABS_VOLUME) == 0) {
-                synchronized (mA2dpAvrcpLock) {
-                    if (mA2dp != null && mAvrcpAbsVolSupported) {
-                        mA2dp.setAvrcpAbsoluteVolume(index / 10);
-                    }
+            if (streamTypeAlias == AudioSystem.STREAM_MUSIC
+                    && (device & AudioSystem.DEVICE_OUT_ALL_A2DP) != 0
+                    && (flags & AudioManager.FLAG_BLUETOOTH_ABS_VOLUME) == 0) {
+                if (DEBUG_VOL) {
+                    Log.d(TAG, "setStreamVolume postSetAvrcpAbsoluteVolumeIndex index=" + index
+                            + "stream=" + streamType);
                 }
+                mDeviceBroker.postSetAvrcpAbsoluteVolumeIndex(index / 10);
             }
 
             if ((device & AudioSystem.DEVICE_OUT_HEARING_AID) != 0) {
-                setHearingAidVolume(index, streamType);
+                Log.i(TAG, "setStreamVolume postSetHearingAidVolumeIndex index=" + index
+                        + " stream=" + streamType);
+                mDeviceBroker.postSetHearingAidVolumeIndex(index, streamType);
             }
 
             if (streamTypeAlias == AudioSystem.STREAM_MUSIC) {
@@ -2881,6 +2703,10 @@
         }
     }
 
+    /*package*/ void setUpdateRingerModeServiceInt() {
+        setRingerModeInt(getRingerModeInternal(), false);
+    }
+
     /** @see AudioManager#shouldVibrate(int) */
     public boolean shouldVibrate(int vibrateType) {
         if (!mHasVibrator) return false;
@@ -2921,7 +2747,7 @@
 
     }
 
-    private class SetModeDeathHandler implements IBinder.DeathRecipient {
+    /*package*/ class SetModeDeathHandler implements IBinder.DeathRecipient {
         private IBinder mCb; // To be notified of client's death
         private int mPid;
         private int mMode = AudioSystem.MODE_NORMAL; // Current mode set by this client
@@ -2934,7 +2760,7 @@
         public void binderDied() {
             int oldModeOwnerPid = 0;
             int newModeOwnerPid = 0;
-            synchronized(mSetModeDeathHandlers) {
+            synchronized (mDeviceBroker.mSetModeLock) {
                 Log.w(TAG, "setMode() client died");
                 if (!mSetModeDeathHandlers.isEmpty()) {
                     oldModeOwnerPid = mSetModeDeathHandlers.get(0).getPid();
@@ -2949,9 +2775,7 @@
             // when entering RINGTONE, IN_CALL or IN_COMMUNICATION mode, clear all
             // SCO connections not started by the application changing the mode when pid changes
             if ((newModeOwnerPid != oldModeOwnerPid) && (newModeOwnerPid != 0)) {
-                final long ident = Binder.clearCallingIdentity();
-                disconnectBluetoothSco(newModeOwnerPid);
-                Binder.restoreCallingIdentity(ident);
+                mDeviceBroker.postDisconnectBluetoothSco(newModeOwnerPid);
             }
         }
 
@@ -2994,7 +2818,7 @@
 
         int oldModeOwnerPid = 0;
         int newModeOwnerPid = 0;
-        synchronized(mSetModeDeathHandlers) {
+        synchronized (mDeviceBroker.mSetModeLock) {
             if (!mSetModeDeathHandlers.isEmpty()) {
                 oldModeOwnerPid = mSetModeDeathHandlers.get(0).getPid();
             }
@@ -3006,11 +2830,11 @@
         // when entering RINGTONE, IN_CALL or IN_COMMUNICATION mode, clear all
         // SCO connections not started by the application changing the mode when pid changes
         if ((newModeOwnerPid != oldModeOwnerPid) && (newModeOwnerPid != 0)) {
-            disconnectBluetoothSco(newModeOwnerPid);
+            mDeviceBroker.postDisconnectBluetoothSco(newModeOwnerPid);
         }
     }
 
-    // must be called synchronized on mSetModeDeathHandlers
+    // must be called synchronized on mSetModeLock
     // setModeInt() returns a valid PID if the audio mode was successfully set to
     // any mode other than NORMAL.
     private int setModeInt(int mode, IBinder cb, int pid, String caller) {
@@ -3380,26 +3204,12 @@
         final String eventSource = new StringBuilder("setSpeakerphoneOn(").append(on)
                 .append(") from u/pid:").append(Binder.getCallingUid()).append("/")
                 .append(Binder.getCallingPid()).toString();
-
-        if (on) {
-            if (mForcedUseForComm == AudioSystem.FORCE_BT_SCO) {
-                    sendMsg(mAudioHandler, MSG_SET_FORCE_USE, SENDMSG_QUEUE,
-                            AudioSystem.FOR_RECORD, AudioSystem.FORCE_NONE,
-                            eventSource, 0);
-            }
-            mForcedUseForComm = AudioSystem.FORCE_SPEAKER;
-        } else if (mForcedUseForComm == AudioSystem.FORCE_SPEAKER){
-            mForcedUseForComm = AudioSystem.FORCE_NONE;
-        }
-
-        mForcedUseForCommExt = mForcedUseForComm;
-        sendMsg(mAudioHandler, MSG_SET_FORCE_USE, SENDMSG_QUEUE,
-                AudioSystem.FOR_COMMUNICATION, mForcedUseForComm, eventSource, 0);
+        mDeviceBroker.setSpeakerphoneOn(on, eventSource);
     }
 
     /** @see AudioManager#isSpeakerphoneOn() */
     public boolean isSpeakerphoneOn() {
-        return (mForcedUseForCommExt == AudioSystem.FORCE_SPEAKER);
+        return mDeviceBroker.isSpeakerphoneOn();
     }
 
     /** @see AudioManager#setBluetoothScoOn(boolean) */
@@ -3410,7 +3220,7 @@
 
         // Only enable calls from system components
         if (UserHandle.getCallingAppId() >= FIRST_APPLICATION_UID) {
-            mForcedUseForCommExt = on ? AudioSystem.FORCE_BT_SCO : AudioSystem.FORCE_NONE;
+            mDeviceBroker.setBluetoothScoOnByApp(on);
             return;
         }
 
@@ -3418,95 +3228,57 @@
         final String eventSource = new StringBuilder("setBluetoothScoOn(").append(on)
                 .append(") from u/pid:").append(Binder.getCallingUid()).append("/")
                 .append(Binder.getCallingPid()).toString();
-        setBluetoothScoOnInt(on, eventSource);
+
+        mDeviceBroker.setBluetoothScoOn(on, eventSource);
     }
 
-    public void setBluetoothScoOnInt(boolean on, String eventSource) {
-        Log.i(TAG, "setBluetoothScoOnInt: " + on + " " + eventSource);
-        if (on) {
-            // do not accept SCO ON if SCO audio is not connected
-            synchronized (mScoClients) {
-                if ((mBluetoothHeadset != null)
-                        && (mBluetoothHeadset.getAudioState(mBluetoothHeadsetDevice)
-                            != BluetoothHeadset.STATE_AUDIO_CONNECTED)) {
-                    mForcedUseForCommExt = AudioSystem.FORCE_BT_SCO;
-                    Log.w(TAG, "setBluetoothScoOnInt(true) failed because "
-                            + mBluetoothHeadsetDevice + " is not in audio connected mode");
-                    return;
-                }
-            }
-            mForcedUseForComm = AudioSystem.FORCE_BT_SCO;
-        } else if (mForcedUseForComm == AudioSystem.FORCE_BT_SCO) {
-            mForcedUseForComm = AudioSystem.FORCE_NONE;
-        }
-        mForcedUseForCommExt = mForcedUseForComm;
-        AudioSystem.setParameters("BT_SCO="+ (on ? "on" : "off"));
-        sendMsg(mAudioHandler, MSG_SET_FORCE_USE, SENDMSG_QUEUE,
-                AudioSystem.FOR_COMMUNICATION, mForcedUseForComm, eventSource, 0);
-        sendMsg(mAudioHandler, MSG_SET_FORCE_USE, SENDMSG_QUEUE,
-                AudioSystem.FOR_RECORD, mForcedUseForComm, eventSource, 0);
-        // Un-mute ringtone stream volume
-        setRingerModeInt(getRingerModeInternal(), false);
-    }
-
-    /** @see AudioManager#isBluetoothScoOn() */
+    /** @see AudioManager#isBluetoothScoOn()
+     * Note that it doesn't report internal state, but state seen by apps (which may have
+     * called setBluetoothScoOn() */
     public boolean isBluetoothScoOn() {
-        return (mForcedUseForCommExt == AudioSystem.FORCE_BT_SCO);
+        return mDeviceBroker.isBluetoothScoOnForApp();
     }
 
+    // TODO investigate internal users due to deprecation of SDK API
     /** @see AudioManager#setBluetoothA2dpOn(boolean) */
     public void setBluetoothA2dpOn(boolean on) {
         // for logging only
         final String eventSource = new StringBuilder("setBluetoothA2dpOn(").append(on)
                 .append(") from u/pid:").append(Binder.getCallingUid()).append("/")
                 .append(Binder.getCallingPid()).toString();
-
-        synchronized (mBluetoothA2dpEnabledLock) {
-            if (mBluetoothA2dpEnabled == on) {
-                return;
-            }
-            mBluetoothA2dpEnabled = on;
-            sendMsg(mAudioHandler, MSG_SET_FORCE_BT_A2DP_USE, SENDMSG_QUEUE,
-                    AudioSystem.FOR_MEDIA,
-                    mBluetoothA2dpEnabled ? AudioSystem.FORCE_NONE : AudioSystem.FORCE_NO_BT_A2DP,
-                    eventSource, 0);
-        }
+        mDeviceBroker.setBluetoothA2dpOn_Async(on, eventSource);
     }
 
     /** @see AudioManager#isBluetoothA2dpOn() */
     public boolean isBluetoothA2dpOn() {
-        synchronized (mBluetoothA2dpEnabledLock) {
-            return mBluetoothA2dpEnabled;
-        }
+        return mDeviceBroker.isBluetoothA2dpOn();
     }
 
     /** @see AudioManager#startBluetoothSco() */
     public void startBluetoothSco(IBinder cb, int targetSdkVersion) {
-        int scoAudioMode =
+        final int scoAudioMode =
                 (targetSdkVersion < Build.VERSION_CODES.JELLY_BEAN_MR2) ?
-                        SCO_MODE_VIRTUAL_CALL : SCO_MODE_UNDEFINED;
-        startBluetoothScoInt(cb, scoAudioMode);
+                        BtHelper.SCO_MODE_VIRTUAL_CALL : BtHelper.SCO_MODE_UNDEFINED;
+        final String eventSource = new StringBuilder("startBluetoothSco()")
+                .append(") from u/pid:").append(Binder.getCallingUid()).append("/")
+                .append(Binder.getCallingPid()).toString();
+        startBluetoothScoInt(cb, scoAudioMode, eventSource);
     }
 
     /** @see AudioManager#startBluetoothScoVirtualCall() */
     public void startBluetoothScoVirtualCall(IBinder cb) {
-        startBluetoothScoInt(cb, SCO_MODE_VIRTUAL_CALL);
+        final String eventSource = new StringBuilder("startBluetoothScoVirtualCall()")
+                .append(") from u/pid:").append(Binder.getCallingUid()).append("/")
+                .append(Binder.getCallingPid()).toString();
+        startBluetoothScoInt(cb, BtHelper.SCO_MODE_VIRTUAL_CALL, eventSource);
     }
 
-    void startBluetoothScoInt(IBinder cb, int scoAudioMode){
+    void startBluetoothScoInt(IBinder cb, int scoAudioMode, @NonNull String eventSource) {
         if (!checkAudioSettingsPermission("startBluetoothSco()") ||
                 !mSystemReady) {
             return;
         }
-        ScoClient client = getScoClient(cb, true);
-        // The calling identity must be cleared before calling ScoClient.incCount().
-        // inCount() calls requestScoState() which in turn can call BluetoothHeadset APIs
-        // and this must be done on behalf of system server to make sure permissions are granted.
-        // The caller identity must be cleared after getScoClient() because it is needed if a new
-        // client is created.
-        final long ident = Binder.clearCallingIdentity();
-        client.incCount(scoAudioMode);
-        Binder.restoreCallingIdentity(ident);
+        mDeviceBroker.startBluetoothScoForClient_Sync(cb, scoAudioMode, eventSource);
     }
 
     /** @see AudioManager#stopBluetoothSco() */
@@ -3515,648 +3287,15 @@
                 !mSystemReady) {
             return;
         }
-        ScoClient client = getScoClient(cb, false);
-        // The calling identity must be cleared before calling ScoClient.decCount().
-        // decCount() calls requestScoState() which in turn can call BluetoothHeadset APIs
-        // and this must be done on behalf of system server to make sure permissions are granted.
-        final long ident = Binder.clearCallingIdentity();
-        if (client != null) {
-            client.decCount();
-        }
-        Binder.restoreCallingIdentity(ident);
+        final String eventSource =  new StringBuilder("stopBluetoothSco()")
+                .append(") from u/pid:").append(Binder.getCallingUid()).append("/")
+                .append(Binder.getCallingPid()).toString();
+        mDeviceBroker.stopBluetoothScoForClient_Sync(cb, eventSource);
     }
 
 
-    private class ScoClient implements IBinder.DeathRecipient {
-        private IBinder mCb; // To be notified of client's death
-        private int mCreatorPid;
-        private int mStartcount; // number of SCO connections started by this client
-
-        ScoClient(IBinder cb) {
-            mCb = cb;
-            mCreatorPid = Binder.getCallingPid();
-            mStartcount = 0;
-        }
-
-        public void binderDied() {
-            synchronized(mScoClients) {
-                Log.w(TAG, "SCO client died");
-                int index = mScoClients.indexOf(this);
-                if (index < 0) {
-                    Log.w(TAG, "unregistered SCO client died");
-                } else {
-                    clearCount(true);
-                    mScoClients.remove(this);
-                }
-            }
-        }
-
-        public void incCount(int scoAudioMode) {
-            synchronized(mScoClients) {
-                requestScoState(BluetoothHeadset.STATE_AUDIO_CONNECTED, scoAudioMode);
-                if (mStartcount == 0) {
-                    try {
-                        mCb.linkToDeath(this, 0);
-                    } catch (RemoteException e) {
-                        // client has already died!
-                        Log.w(TAG, "ScoClient  incCount() could not link to "+mCb+" binder death");
-                    }
-                }
-                mStartcount++;
-            }
-        }
-
-        public void decCount() {
-            synchronized(mScoClients) {
-                if (mStartcount == 0) {
-                    Log.w(TAG, "ScoClient.decCount() already 0");
-                } else {
-                    mStartcount--;
-                    if (mStartcount == 0) {
-                        try {
-                            mCb.unlinkToDeath(this, 0);
-                        } catch (NoSuchElementException e) {
-                            Log.w(TAG, "decCount() going to 0 but not registered to binder");
-                        }
-                    }
-                    requestScoState(BluetoothHeadset.STATE_AUDIO_DISCONNECTED, 0);
-                }
-            }
-        }
-
-        public void clearCount(boolean stopSco) {
-            synchronized(mScoClients) {
-                if (mStartcount != 0) {
-                    try {
-                        mCb.unlinkToDeath(this, 0);
-                    } catch (NoSuchElementException e) {
-                        Log.w(TAG, "clearCount() mStartcount: "+mStartcount+" != 0 but not registered to binder");
-                    }
-                }
-                mStartcount = 0;
-                if (stopSco) {
-                    requestScoState(BluetoothHeadset.STATE_AUDIO_DISCONNECTED, 0);
-                }
-            }
-        }
-
-        public int getCount() {
-            return mStartcount;
-        }
-
-        public IBinder getBinder() {
-            return mCb;
-        }
-
-        public int getPid() {
-            return mCreatorPid;
-        }
-
-        public int totalCount() {
-            synchronized(mScoClients) {
-                int count = 0;
-                for (ScoClient mScoClient : mScoClients) {
-                    count += mScoClient.getCount();
-                }
-                return count;
-            }
-        }
-
-        private void requestScoState(int state, int scoAudioMode) {
-            checkScoAudioState();
-            int clientCount = totalCount();
-            if (clientCount != 0) {
-                Log.i(TAG, "requestScoState: state=" + state + ", scoAudioMode=" + scoAudioMode
-                        + ", clientCount=" + clientCount);
-                return;
-            }
-            if (state == BluetoothHeadset.STATE_AUDIO_CONNECTED) {
-                // Make sure that the state transitions to CONNECTING even if we cannot initiate
-                // the connection.
-                broadcastScoConnectionState(AudioManager.SCO_AUDIO_STATE_CONNECTING);
-                // Accept SCO audio activation only in NORMAL audio mode or if the mode is
-                // currently controlled by the same client process.
-                synchronized(mSetModeDeathHandlers) {
-                    int modeOwnerPid =  mSetModeDeathHandlers.isEmpty()
-                            ? 0 : mSetModeDeathHandlers.get(0).getPid();
-                    if (modeOwnerPid != 0 && (modeOwnerPid != mCreatorPid)) {
-                        Log.w(TAG, "requestScoState: audio mode is not NORMAL and modeOwnerPid "
-                                + modeOwnerPid + " != creatorPid " + mCreatorPid);
-                        broadcastScoConnectionState(AudioManager.SCO_AUDIO_STATE_DISCONNECTED);
-                        return;
-                    }
-                    switch (mScoAudioState) {
-                        case SCO_STATE_INACTIVE:
-                            mScoAudioMode = scoAudioMode;
-                            if (scoAudioMode == SCO_MODE_UNDEFINED) {
-                                mScoAudioMode = SCO_MODE_VIRTUAL_CALL;
-                                if (mBluetoothHeadsetDevice != null) {
-                                    mScoAudioMode = Settings.Global.getInt(mContentResolver,
-                                            "bluetooth_sco_channel_"
-                                                    + mBluetoothHeadsetDevice.getAddress(),
-                                            SCO_MODE_VIRTUAL_CALL);
-                                    if (mScoAudioMode > SCO_MODE_MAX || mScoAudioMode < 0) {
-                                        mScoAudioMode = SCO_MODE_VIRTUAL_CALL;
-                                    }
-                                }
-                            }
-                            if (mBluetoothHeadset == null) {
-                                if (getBluetoothHeadset()) {
-                                    mScoAudioState = SCO_STATE_ACTIVATE_REQ;
-                                } else {
-                                    Log.w(TAG, "requestScoState: getBluetoothHeadset failed during"
-                                            + " connection, mScoAudioMode=" + mScoAudioMode);
-                                    broadcastScoConnectionState(
-                                            AudioManager.SCO_AUDIO_STATE_DISCONNECTED);
-                                }
-                                break;
-                            }
-                            if (mBluetoothHeadsetDevice == null) {
-                                Log.w(TAG, "requestScoState: no active device while connecting,"
-                                        + " mScoAudioMode=" + mScoAudioMode);
-                                broadcastScoConnectionState(
-                                        AudioManager.SCO_AUDIO_STATE_DISCONNECTED);
-                                break;
-                            }
-                            if (connectBluetoothScoAudioHelper(mBluetoothHeadset,
-                                    mBluetoothHeadsetDevice, mScoAudioMode)) {
-                                mScoAudioState = SCO_STATE_ACTIVE_INTERNAL;
-                            } else {
-                                Log.w(TAG, "requestScoState: connect to " + mBluetoothHeadsetDevice
-                                        + " failed, mScoAudioMode=" + mScoAudioMode);
-                                broadcastScoConnectionState(
-                                        AudioManager.SCO_AUDIO_STATE_DISCONNECTED);
-                            }
-                            break;
-                        case SCO_STATE_DEACTIVATING:
-                            mScoAudioState = SCO_STATE_ACTIVATE_REQ;
-                            break;
-                        case SCO_STATE_DEACTIVATE_REQ:
-                            mScoAudioState = SCO_STATE_ACTIVE_INTERNAL;
-                            broadcastScoConnectionState(AudioManager.SCO_AUDIO_STATE_CONNECTED);
-                            break;
-                        default:
-                            Log.w(TAG, "requestScoState: failed to connect in state "
-                                    + mScoAudioState + ", scoAudioMode=" + scoAudioMode);
-                            broadcastScoConnectionState(AudioManager.SCO_AUDIO_STATE_DISCONNECTED);
-                            break;
-
-                    }
-                }
-            } else if (state == BluetoothHeadset.STATE_AUDIO_DISCONNECTED) {
-                switch (mScoAudioState) {
-                    case SCO_STATE_ACTIVE_INTERNAL:
-                        if (mBluetoothHeadset == null) {
-                            if (getBluetoothHeadset()) {
-                                mScoAudioState = SCO_STATE_DEACTIVATE_REQ;
-                            } else {
-                                Log.w(TAG, "requestScoState: getBluetoothHeadset failed during"
-                                        + " disconnection, mScoAudioMode=" + mScoAudioMode);
-                                mScoAudioState = SCO_STATE_INACTIVE;
-                                broadcastScoConnectionState(
-                                        AudioManager.SCO_AUDIO_STATE_DISCONNECTED);
-                            }
-                            break;
-                        }
-                        if (mBluetoothHeadsetDevice == null) {
-                            mScoAudioState = SCO_STATE_INACTIVE;
-                            broadcastScoConnectionState(
-                                    AudioManager.SCO_AUDIO_STATE_DISCONNECTED);
-                            break;
-                        }
-                        if (disconnectBluetoothScoAudioHelper(mBluetoothHeadset,
-                                mBluetoothHeadsetDevice, mScoAudioMode)) {
-                            mScoAudioState = SCO_STATE_DEACTIVATING;
-                        } else {
-                            mScoAudioState = SCO_STATE_INACTIVE;
-                            broadcastScoConnectionState(
-                                    AudioManager.SCO_AUDIO_STATE_DISCONNECTED);
-                        }
-                        break;
-                    case SCO_STATE_ACTIVATE_REQ:
-                        mScoAudioState = SCO_STATE_INACTIVE;
-                        broadcastScoConnectionState(AudioManager.SCO_AUDIO_STATE_DISCONNECTED);
-                        break;
-                    default:
-                        Log.w(TAG, "requestScoState: failed to disconnect in state "
-                                + mScoAudioState + ", scoAudioMode=" + scoAudioMode);
-                        broadcastScoConnectionState(AudioManager.SCO_AUDIO_STATE_DISCONNECTED);
-                        break;
-                }
-            }
-        }
-    }
-
-    private void checkScoAudioState() {
-        synchronized (mScoClients) {
-            if (mBluetoothHeadset != null && mBluetoothHeadsetDevice != null &&
-                    mScoAudioState == SCO_STATE_INACTIVE &&
-                    mBluetoothHeadset.getAudioState(mBluetoothHeadsetDevice)
-                            != BluetoothHeadset.STATE_AUDIO_DISCONNECTED) {
-                mScoAudioState = SCO_STATE_ACTIVE_EXTERNAL;
-            }
-        }
-    }
-
-
-    private ScoClient getScoClient(IBinder cb, boolean create) {
-        synchronized(mScoClients) {
-            for (ScoClient existingClient : mScoClients) {
-                if (existingClient.getBinder() == cb) {
-                    return existingClient;
-                }
-            }
-            if (create) {
-                ScoClient newClient = new ScoClient(cb);
-                mScoClients.add(newClient);
-                return newClient;
-            }
-            return null;
-        }
-    }
-
-    public void clearAllScoClients(int exceptPid, boolean stopSco) {
-        synchronized(mScoClients) {
-            ScoClient savedClient = null;
-            for (ScoClient cl : mScoClients) {
-                if (cl.getPid() != exceptPid) {
-                    cl.clearCount(stopSco);
-                } else {
-                    savedClient = cl;
-                }
-            }
-            mScoClients.clear();
-            if (savedClient != null) {
-                mScoClients.add(savedClient);
-            }
-        }
-    }
-
-    private boolean getBluetoothHeadset() {
-        boolean result = false;
-        BluetoothAdapter adapter = BluetoothAdapter.getDefaultAdapter();
-        if (adapter != null) {
-            result = adapter.getProfileProxy(mContext, mBluetoothProfileServiceListener,
-                                    BluetoothProfile.HEADSET);
-        }
-        // If we could not get a bluetooth headset proxy, send a failure message
-        // without delay to reset the SCO audio state and clear SCO clients.
-        // If we could get a proxy, send a delayed failure message that will reset our state
-        // in case we don't receive onServiceConnected().
-        sendMsg(mAudioHandler, MSG_BT_HEADSET_CNCT_FAILED,
-                SENDMSG_REPLACE, 0, 0, null, result ? BT_HEADSET_CNCT_TIMEOUT_MS : 0);
-        return result;
-    }
-
-    /**
-     * Disconnect all SCO connections started by {@link AudioManager} except those started by
-     * {@param exceptPid}
-     *
-     * @param exceptPid pid whose SCO connections through {@link AudioManager} should be kept
-     */
-    private void disconnectBluetoothSco(int exceptPid) {
-        synchronized(mScoClients) {
-            checkScoAudioState();
-            if (mScoAudioState == SCO_STATE_ACTIVE_EXTERNAL) {
-                return;
-            }
-            clearAllScoClients(exceptPid, true);
-        }
-    }
-
-    private static boolean disconnectBluetoothScoAudioHelper(BluetoothHeadset bluetoothHeadset,
-            BluetoothDevice device, int scoAudioMode) {
-        switch (scoAudioMode) {
-            case SCO_MODE_RAW:
-                return bluetoothHeadset.disconnectAudio();
-            case SCO_MODE_VIRTUAL_CALL:
-                return bluetoothHeadset.stopScoUsingVirtualVoiceCall();
-            case SCO_MODE_VR:
-                return bluetoothHeadset.stopVoiceRecognition(device);
-            default:
-                return false;
-        }
-    }
-
-    private static boolean connectBluetoothScoAudioHelper(BluetoothHeadset bluetoothHeadset,
-            BluetoothDevice device, int scoAudioMode) {
-        switch (scoAudioMode) {
-            case SCO_MODE_RAW:
-                return bluetoothHeadset.connectAudio();
-            case SCO_MODE_VIRTUAL_CALL:
-                return bluetoothHeadset.startScoUsingVirtualVoiceCall();
-            case SCO_MODE_VR:
-                return bluetoothHeadset.startVoiceRecognition(device);
-            default:
-                return false;
-        }
-    }
-
-    private void resetBluetoothSco() {
-        synchronized(mScoClients) {
-            clearAllScoClients(0, false);
-            mScoAudioState = SCO_STATE_INACTIVE;
-            broadcastScoConnectionState(AudioManager.SCO_AUDIO_STATE_DISCONNECTED);
-        }
-        AudioSystem.setParameters("A2dpSuspended=false");
-        setBluetoothScoOnInt(false, "resetBluetoothSco");
-    }
-
-    private void broadcastScoConnectionState(int state) {
-        sendMsg(mAudioHandler, MSG_BROADCAST_BT_CONNECTION_STATE,
-                SENDMSG_QUEUE, state, 0, null, 0);
-    }
-
-    private void onBroadcastScoConnectionState(int state) {
-        if (state != mScoConnectionState) {
-            Intent newIntent = new Intent(AudioManager.ACTION_SCO_AUDIO_STATE_UPDATED);
-            newIntent.putExtra(AudioManager.EXTRA_SCO_AUDIO_STATE, state);
-            newIntent.putExtra(AudioManager.EXTRA_SCO_AUDIO_PREVIOUS_STATE,
-                    mScoConnectionState);
-            sendStickyBroadcastToAll(newIntent);
-            mScoConnectionState = state;
-        }
-    }
-
-    private boolean handleBtScoActiveDeviceChange(BluetoothDevice btDevice, boolean isActive) {
-        if (btDevice == null) {
-            return true;
-        }
-        String address = btDevice.getAddress();
-        BluetoothClass btClass = btDevice.getBluetoothClass();
-        int inDevice = AudioSystem.DEVICE_IN_BLUETOOTH_SCO_HEADSET;
-        int[] outDeviceTypes = {
-            AudioSystem.DEVICE_OUT_BLUETOOTH_SCO,
-            AudioSystem.DEVICE_OUT_BLUETOOTH_SCO_HEADSET,
-            AudioSystem.DEVICE_OUT_BLUETOOTH_SCO_CARKIT
-        };
-        if (btClass != null) {
-            switch (btClass.getDeviceClass()) {
-                case BluetoothClass.Device.AUDIO_VIDEO_WEARABLE_HEADSET:
-                case BluetoothClass.Device.AUDIO_VIDEO_HANDSFREE:
-                    outDeviceTypes = new int[] { AudioSystem.DEVICE_OUT_BLUETOOTH_SCO_HEADSET };
-                    break;
-                case BluetoothClass.Device.AUDIO_VIDEO_CAR_AUDIO:
-                    outDeviceTypes = new int[] { AudioSystem.DEVICE_OUT_BLUETOOTH_SCO_CARKIT };
-                    break;
-            }
-        }
-        if (!BluetoothAdapter.checkBluetoothAddress(address)) {
-            address = "";
-        }
-        String btDeviceName =  btDevice.getName();
-        boolean result = false;
-        if (isActive) {
-            result |= handleDeviceConnection(isActive, outDeviceTypes[0], address, btDeviceName);
-        } else {
-            for (int outDeviceType : outDeviceTypes) {
-                result |= handleDeviceConnection(isActive, outDeviceType, address, btDeviceName);
-            }
-        }
-        // handleDeviceConnection() && result to make sure the method get executed
-        result = handleDeviceConnection(isActive, inDevice, address, btDeviceName) && result;
-        return result;
-    }
-
-    private void setBtScoActiveDevice(BluetoothDevice btDevice) {
-        synchronized (mScoClients) {
-            Log.i(TAG, "setBtScoActiveDevice: " + mBluetoothHeadsetDevice + " -> " + btDevice);
-            final BluetoothDevice previousActiveDevice = mBluetoothHeadsetDevice;
-            if (!Objects.equals(btDevice, previousActiveDevice)) {
-                if (!handleBtScoActiveDeviceChange(previousActiveDevice, false)) {
-                    Log.w(TAG, "setBtScoActiveDevice() failed to remove previous device "
-                            + previousActiveDevice);
-                }
-                if (!handleBtScoActiveDeviceChange(btDevice, true)) {
-                    Log.e(TAG, "setBtScoActiveDevice() failed to add new device " + btDevice);
-                    // set mBluetoothHeadsetDevice to null when failing to add new device
-                    btDevice = null;
-                }
-                mBluetoothHeadsetDevice = btDevice;
-                if (mBluetoothHeadsetDevice == null) {
-                    resetBluetoothSco();
-                }
-            }
-        }
-    }
-
-    private BluetoothProfile.ServiceListener mBluetoothProfileServiceListener =
-        new BluetoothProfile.ServiceListener() {
-        public void onServiceConnected(int profile, BluetoothProfile proxy) {
-            BluetoothDevice btDevice;
-            List<BluetoothDevice> deviceList;
-            switch(profile) {
-            case BluetoothProfile.A2DP:
-                synchronized (mConnectedDevices) {
-                    synchronized (mA2dpAvrcpLock) {
-                        mA2dp = (BluetoothA2dp) proxy;
-                        deviceList = mA2dp.getConnectedDevices();
-                        if (deviceList.size() > 0) {
-                            btDevice = deviceList.get(0);
-                            int state = mA2dp.getConnectionState(btDevice);
-                            int intState = (state == BluetoothA2dp.STATE_CONNECTED) ? 1 : 0;
-                            int delay = checkSendBecomingNoisyIntent(
-                                    AudioSystem.DEVICE_OUT_BLUETOOTH_A2DP, intState,
-                                    AudioSystem.DEVICE_NONE);
-                            final String addr = btDevice == null ? "null" : btDevice.getAddress();
-                            mDeviceLogger.log(new AudioEventLogger.StringEvent(
-                                    "A2DP service connected: device addr=" + addr
-                                    + " state=" + state));
-                            queueMsgUnderWakeLock(mAudioHandler,
-                                    MSG_SET_A2DP_SINK_CONNECTION_STATE,
-                                    state,
-                                    0 /* arg2 unused */,
-                                    new BluetoothA2dpDeviceInfo(btDevice),
-                                    delay);
-                        }
-                    }
-                }
-                break;
-
-            case BluetoothProfile.A2DP_SINK:
-                deviceList = proxy.getConnectedDevices();
-                if (deviceList.size() > 0) {
-                    btDevice = deviceList.get(0);
-                    synchronized (mConnectedDevices) {
-                        int state = proxy.getConnectionState(btDevice);
-                        queueMsgUnderWakeLock(mAudioHandler,
-                                MSG_SET_A2DP_SRC_CONNECTION_STATE,
-                                state,
-                                0 /* arg2 unused */,
-                                new BluetoothA2dpDeviceInfo(btDevice),
-                                0 /* delay */);
-                    }
-                }
-                break;
-
-            case BluetoothProfile.HEADSET:
-                synchronized (mScoClients) {
-                    // Discard timeout message
-                    mAudioHandler.removeMessages(MSG_BT_HEADSET_CNCT_FAILED);
-                    mBluetoothHeadset = (BluetoothHeadset) proxy;
-                    setBtScoActiveDevice(mBluetoothHeadset.getActiveDevice());
-                    // Refresh SCO audio state
-                    checkScoAudioState();
-                    // Continue pending action if any
-                    if (mScoAudioState == SCO_STATE_ACTIVATE_REQ ||
-                            mScoAudioState == SCO_STATE_DEACTIVATE_REQ) {
-                        boolean status = false;
-                        if (mBluetoothHeadsetDevice != null) {
-                            switch (mScoAudioState) {
-                                case SCO_STATE_ACTIVATE_REQ:
-                                    status = connectBluetoothScoAudioHelper(mBluetoothHeadset,
-                                            mBluetoothHeadsetDevice, mScoAudioMode);
-                                    if (status) {
-                                        mScoAudioState = SCO_STATE_ACTIVE_INTERNAL;
-                                    }
-                                    break;
-                                case SCO_STATE_DEACTIVATE_REQ:
-                                    status = disconnectBluetoothScoAudioHelper(mBluetoothHeadset,
-                                            mBluetoothHeadsetDevice, mScoAudioMode);
-                                    if (status) {
-                                        mScoAudioState = SCO_STATE_DEACTIVATING;
-                                    }
-                                    break;
-                            }
-                        }
-                        if (!status) {
-                            mScoAudioState = SCO_STATE_INACTIVE;
-                            broadcastScoConnectionState(AudioManager.SCO_AUDIO_STATE_DISCONNECTED);
-                        }
-                    }
-                }
-                break;
-
-            case BluetoothProfile.HEARING_AID:
-                synchronized (mConnectedDevices) {
-                    synchronized (mHearingAidLock) {
-                        mHearingAid = (BluetoothHearingAid) proxy;
-                        deviceList = mHearingAid.getConnectedDevices();
-                        if (deviceList.size() > 0) {
-                            btDevice = deviceList.get(0);
-                            int state = mHearingAid.getConnectionState(btDevice);
-                            int intState = (state == BluetoothHearingAid.STATE_CONNECTED) ? 1 : 0;
-                            int delay = checkSendBecomingNoisyIntent(
-                                    AudioSystem.DEVICE_OUT_HEARING_AID, intState,
-                                    AudioSystem.DEVICE_NONE);
-                            queueMsgUnderWakeLock(mAudioHandler,
-                                    MSG_SET_HEARING_AID_CONNECTION_STATE,
-                                    state,
-                                    0 /* arg2 unused */,
-                                    btDevice,
-                                    delay);
-                        }
-                    }
-                }
-
-                break;
-
-            default:
-                break;
-            }
-        }
-        public void onServiceDisconnected(int profile) {
-
-            switch (profile) {
-            case BluetoothProfile.A2DP:
-                disconnectA2dp();
-                break;
-
-            case BluetoothProfile.A2DP_SINK:
-                disconnectA2dpSink();
-                break;
-
-            case BluetoothProfile.HEADSET:
-                disconnectHeadset();
-                break;
-
-            case BluetoothProfile.HEARING_AID:
-                disconnectHearingAid();
-                break;
-
-            default:
-                break;
-            }
-        }
-    };
-
-    void disconnectAllBluetoothProfiles() {
-        disconnectA2dp();
-        disconnectA2dpSink();
-        disconnectHeadset();
-        disconnectHearingAid();
-    }
-
-    void disconnectA2dp() {
-        synchronized (mConnectedDevices) {
-            synchronized (mA2dpAvrcpLock) {
-                ArraySet<String> toRemove = null;
-                // Disconnect ALL DEVICE_OUT_BLUETOOTH_A2DP devices
-                for (int i = 0; i < mConnectedDevices.size(); i++) {
-                    DeviceListSpec deviceSpec = mConnectedDevices.valueAt(i);
-                    if (deviceSpec.mDeviceType == AudioSystem.DEVICE_OUT_BLUETOOTH_A2DP) {
-                        toRemove = toRemove != null ? toRemove : new ArraySet<String>();
-                        toRemove.add(deviceSpec.mDeviceAddress);
-                    }
-                }
-                if (toRemove != null) {
-                    int delay = checkSendBecomingNoisyIntent(AudioSystem.DEVICE_OUT_BLUETOOTH_A2DP,
-                            0, AudioSystem.DEVICE_NONE);
-                    for (int i = 0; i < toRemove.size(); i++) {
-                        makeA2dpDeviceUnavailableLater(toRemove.valueAt(i), delay);
-                    }
-                }
-            }
-        }
-    }
-
-    void disconnectA2dpSink() {
-        synchronized (mConnectedDevices) {
-            ArraySet<String> toRemove = null;
-            // Disconnect ALL DEVICE_IN_BLUETOOTH_A2DP devices
-            for(int i = 0; i < mConnectedDevices.size(); i++) {
-                DeviceListSpec deviceSpec = mConnectedDevices.valueAt(i);
-                if (deviceSpec.mDeviceType == AudioSystem.DEVICE_IN_BLUETOOTH_A2DP) {
-                    toRemove = toRemove != null ? toRemove : new ArraySet<String>();
-                    toRemove.add(deviceSpec.mDeviceAddress);
-                }
-            }
-            if (toRemove != null) {
-                for (int i = 0; i < toRemove.size(); i++) {
-                    makeA2dpSrcUnavailable(toRemove.valueAt(i));
-                }
-            }
-        }
-    }
-
-    void disconnectHeadset() {
-        synchronized (mScoClients) {
-            setBtScoActiveDevice(null);
-            mBluetoothHeadset = null;
-        }
-    }
-
-    void disconnectHearingAid() {
-        synchronized (mConnectedDevices) {
-            synchronized (mHearingAidLock) {
-                ArraySet<String> toRemove = null;
-                // Disconnect ALL DEVICE_OUT_HEARING_AID devices
-                for (int i = 0; i < mConnectedDevices.size(); i++) {
-                    DeviceListSpec deviceSpec = mConnectedDevices.valueAt(i);
-                    if (deviceSpec.mDeviceType == AudioSystem.DEVICE_OUT_HEARING_AID) {
-                        toRemove = toRemove != null ? toRemove : new ArraySet<String>();
-                        toRemove.add(deviceSpec.mDeviceAddress);
-                    }
-                }
-                if (toRemove != null) {
-                    int delay = checkSendBecomingNoisyIntent(AudioSystem.DEVICE_OUT_HEARING_AID,
-                            0, AudioSystem.DEVICE_NONE);
-                    for (int i = 0; i < toRemove.size(); i++) {
-                        makeHearingAidDeviceUnavailable(toRemove.valueAt(i) /*, delay*/);
-                    }
-                }
-            }
-        }
+    /*package*/ ContentResolver getContentResolver() {
+        return mContentResolver;
     }
 
     private void onCheckMusicActive(String caller) {
@@ -4173,8 +3312,8 @@
                             caller,
                             MUSIC_ACTIVE_POLL_PERIOD_MS);
                     int index = mStreamStates[AudioSystem.STREAM_MUSIC].getIndex(device);
-                    if (AudioSystem.isStreamActive(AudioSystem.STREAM_MUSIC, 0) &&
-                            (index > safeMediaVolumeIndex(device))) {
+                    if (AudioSystem.isStreamActive(AudioSystem.STREAM_MUSIC, 0)
+                            && (index > safeMediaVolumeIndex(device))) {
                         // Approximate cumulative active music time
                         mMusicActiveMs += MUSIC_ACTIVE_POLL_PERIOD_MS;
                         if (mMusicActiveMs > UNSAFE_VOLUME_MUSIC_ACTIVE_MS_MAX) {
@@ -4192,8 +3331,7 @@
         mAudioHandler.obtainMessage(MSG_PERSIST_MUSIC_ACTIVE_MS, mMusicActiveMs, 0).sendToTarget();
     }
 
-    private int getSafeUsbMediaVolumeIndex()
-    {
+    private int getSafeUsbMediaVolumeIndex() {
         // determine UI volume index corresponding to the wanted safe gain in dBFS
         int min = MIN_STREAM_VOLUME[AudioSystem.STREAM_MUSIC];
         int max = MAX_STREAM_VOLUME[AudioSystem.STREAM_MUSIC];
@@ -4201,7 +3339,7 @@
         mSafeUsbMediaVolumeDbfs = mContext.getResources().getInteger(
                 com.android.internal.R.integer.config_safe_media_volume_usb_mB) / 100.0f;
 
-        while (Math.abs(max-min) > 1) {
+        while (Math.abs(max - min) > 1) {
             int index = (max + min) / 2;
             float gainDB = AudioSystem.getStreamVolumeDB(
                     AudioSystem.STREAM_MUSIC, index, AudioSystem.DEVICE_OUT_USB_HEADSET);
@@ -4518,7 +3656,7 @@
                 || adjust == AudioManager.ADJUST_TOGGLE_MUTE;
     }
 
-    private boolean isInCommunication() {
+    /*package*/ boolean isInCommunication() {
         boolean IsInCall = false;
 
         TelecomManager telecomManager =
@@ -4671,25 +3809,9 @@
         } else if (existingMsgPolicy == SENDMSG_NOOP && handler.hasMessages(msg)) {
             return;
         }
-        synchronized (mLastDeviceConnectMsgTime) {
-            long time = SystemClock.uptimeMillis() + delay;
 
-            if (msg == MSG_SET_A2DP_SRC_CONNECTION_STATE ||
-                msg == MSG_SET_A2DP_SINK_CONNECTION_STATE ||
-                msg == MSG_SET_HEARING_AID_CONNECTION_STATE ||
-                msg == MSG_SET_WIRED_DEVICE_CONNECTION_STATE ||
-                msg == MSG_A2DP_DEVICE_CONFIG_CHANGE ||
-                msg == MSG_A2DP_ACTIVE_DEVICE_CHANGE ||
-                msg == MSG_BTA2DP_DOCK_TIMEOUT) {
-                if (mLastDeviceConnectMsgTime >= time) {
-                  // add a little delay to make sure messages are ordered as expected
-                  time = mLastDeviceConnectMsgTime + 30;
-                }
-                mLastDeviceConnectMsgTime = time;
-            }
-
-            handler.sendMessageAtTime(handler.obtainMessage(msg, arg1, arg2, obj), time);
-        }
+        final long time = SystemClock.uptimeMillis() + delay;
+        handler.sendMessageAtTime(handler.obtainMessage(msg, arg1, arg2, obj), time);
     }
 
     boolean checkAudioSettingsPermission(String method) {
@@ -4704,7 +3826,7 @@
         return false;
     }
 
-    private int getDeviceForStream(int stream) {
+    /*package*/ int getDeviceForStream(int stream) {
         int device = getDevicesForStream(stream);
         if ((device & (device - 1)) != 0) {
             // Multiple device selection is either:
@@ -4749,160 +3871,94 @@
         }
     }
 
-    private int getA2dpCodec(BluetoothDevice device) {
-        synchronized (mA2dpAvrcpLock) {
-            if (mA2dp == null) {
-                return AudioSystem.AUDIO_FORMAT_DEFAULT;
-            }
-            BluetoothCodecStatus btCodecStatus = mA2dp.getCodecStatus(device);
-            if (btCodecStatus == null) {
-                return AudioSystem.AUDIO_FORMAT_DEFAULT;
-            }
-            BluetoothCodecConfig btCodecConfig = btCodecStatus.getCodecConfig();
-            if (btCodecConfig == null) {
-                return AudioSystem.AUDIO_FORMAT_DEFAULT;
-            }
-            return mapBluetoothCodecToAudioFormat(btCodecConfig.getCodecType());
-        }
+
+    /*package*/ void observeDevicesForAllStreams() {
+        observeDevicesForStreams(-1);
     }
 
-    /*
-     * A class just for packaging up a set of connection parameters.
+    /*package*/ static final int CONNECTION_STATE_DISCONNECTED = 0;
+    /*package*/ static final int CONNECTION_STATE_CONNECTED = 1;
+    /**
+     * The states that can be used with AudioService.setWiredDeviceConnectionState()
+     * Attention: those values differ from those in BluetoothProfile, follow annotations to
+     * distinguish between @ConnectionState and @BtProfileConnectionState
      */
-    class WiredDeviceConnectionState {
-        public final int mType;
-        public final int mState;
-        public final String mAddress;
-        public final String mName;
-        public final String mCaller;
+    @IntDef({
+            CONNECTION_STATE_DISCONNECTED,
+            CONNECTION_STATE_CONNECTED,
+    })
+    @Retention(RetentionPolicy.SOURCE)
+    public @interface ConnectionState {}
 
-        public WiredDeviceConnectionState(int type, int state, String address, String name,
-                String caller) {
-            mType = type;
-            mState = state;
-            mAddress = address;
-            mName = name;
-            mCaller = caller;
-        }
-    }
-
-    public void setWiredDeviceConnectionState(int type, int state, String address, String name,
+    /**
+     * see AudioManager.setWiredDeviceConnectionState()
+     */
+    public void setWiredDeviceConnectionState(int type,
+            @ConnectionState int state, String address, String name,
             String caller) {
-        synchronized (mConnectedDevices) {
-            if (DEBUG_DEVICES) {
-                Slog.i(TAG, "setWiredDeviceConnectionState(" + state + " nm: " + name + " addr:"
-                        + address + ")");
-            }
-            int delay = checkSendBecomingNoisyIntent(type, state, AudioSystem.DEVICE_NONE);
-            queueMsgUnderWakeLock(mAudioHandler,
-                    MSG_SET_WIRED_DEVICE_CONNECTION_STATE,
-                    0 /* arg1 unused */,
-                    0 /* arg2 unused */,
-                    new WiredDeviceConnectionState(type, state, address, name, caller),
-                    delay);
+        if (state != CONNECTION_STATE_CONNECTED
+                && state != CONNECTION_STATE_DISCONNECTED) {
+            throw new IllegalArgumentException("Invalid state " + state);
         }
+        mDeviceBroker.setWiredDeviceConnectionState(type, state, address, name, caller);
     }
 
+    /**
+     * @hide
+     * The states that can be used with AudioService.setBluetoothHearingAidDeviceConnectionState()
+     * and AudioService.setBluetoothA2dpDeviceConnectionStateSuppressNoisyIntent()
+     */
+    @IntDef({
+            BluetoothProfile.STATE_DISCONNECTED,
+            BluetoothProfile.STATE_CONNECTED,
+    })
+    @Retention(RetentionPolicy.SOURCE)
+    public @interface BtProfileConnectionState {}
+
     public int setBluetoothHearingAidDeviceConnectionState(
-            BluetoothDevice device, int state, boolean suppressNoisyIntent,
-            int musicDevice)
+            @NonNull BluetoothDevice device, @BtProfileConnectionState int state,
+            boolean suppressNoisyIntent, int musicDevice)
     {
-        int delay;
-        mDeviceLogger.log((new AudioEventLogger.StringEvent(
-                "setHearingAidDeviceConnectionState state=" + state
-                            + " addr=" + device.getAddress()
-                            + " supprNoisy=" + suppressNoisyIntent)).printLog(TAG));
-        synchronized (mConnectedDevices) {
-            if (!suppressNoisyIntent) {
-                int intState = (state == BluetoothHearingAid.STATE_CONNECTED) ? 1 : 0;
-                delay = checkSendBecomingNoisyIntent(AudioSystem.DEVICE_OUT_HEARING_AID,
-                        intState, musicDevice);
-            } else {
-                delay = 0;
-            }
-            queueMsgUnderWakeLock(mAudioHandler,
-                    MSG_SET_HEARING_AID_CONNECTION_STATE,
-                    state,
-                    0 /* arg2 unused */,
-                    device,
-                    delay);
+        if (device == null) {
+            throw new IllegalArgumentException("Illegal null device");
         }
-        return delay;
+        if (state != BluetoothProfile.STATE_CONNECTED
+                && state != BluetoothProfile.STATE_DISCONNECTED) {
+            throw new IllegalArgumentException("Illegal BluetoothProfile state for device "
+                    + " (dis)connection, got " + state);
+        }
+        return mDeviceBroker.setBluetoothHearingAidDeviceConnectionState(
+                device, state, suppressNoisyIntent, musicDevice, "AudioService");
     }
 
-    public int setBluetoothA2dpDeviceConnectionState(
-            BluetoothDevice device, int state, int profile)
-    {
-        return setBluetoothA2dpDeviceConnectionStateSuppressNoisyIntent(
-                device, state, profile, false /* suppressNoisyIntent */,
-                -1 /* a2dpVolume */);
+    /**
+     * See AudioManager.setBluetoothA2dpDeviceConnectionStateSuppressNoisyIntent()
+     */
+    public int setBluetoothA2dpDeviceConnectionStateSuppressNoisyIntent(
+            @NonNull BluetoothDevice device, @AudioService.BtProfileConnectionState int state,
+            int profile, boolean suppressNoisyIntent, int a2dpVolume) {
+        if (device == null) {
+            throw new IllegalArgumentException("Illegal null device");
+        }
+        if (state != BluetoothProfile.STATE_CONNECTED
+                && state != BluetoothProfile.STATE_DISCONNECTED) {
+            throw new IllegalArgumentException("Illegal BluetoothProfile state for device "
+                    + " (dis)connection, got " + state);
+        }
+        return mDeviceBroker.setBluetoothA2dpDeviceConnectionStateSuppressNoisyIntent(device, state,
+                profile, suppressNoisyIntent, a2dpVolume);
     }
 
-    public int setBluetoothA2dpDeviceConnectionStateSuppressNoisyIntent(BluetoothDevice device,
-            int state, int profile, boolean suppressNoisyIntent, int a2dpVolume)
-    {
-        mDeviceLogger.log((new AudioEventLogger.StringEvent(
-                "setBluetoothA2dpDeviceConnectionStateSuppressNoisyIntent state=" + state
-                // only querying address as this is the only readily available field on the device
-                + " addr=" + device.getAddress()
-                + " prof=" + profile + " supprNoisy=" + suppressNoisyIntent
-                + " vol=" + a2dpVolume)).printLog(TAG));
-        if (mAudioHandler.hasMessages(MSG_SET_A2DP_SINK_CONNECTION_STATE, device)) {
-            mDeviceLogger.log(new AudioEventLogger.StringEvent("A2DP connection state ignored"));
-            return 0;
-        }
-        return setBluetoothA2dpDeviceConnectionStateInt(
-                device, state, profile, suppressNoisyIntent,
-                AudioSystem.DEVICE_NONE, a2dpVolume);
-    }
-
-    public int setBluetoothA2dpDeviceConnectionStateInt(
-            BluetoothDevice device, int state, int profile, boolean suppressNoisyIntent,
-            int musicDevice, int a2dpVolume)
-    {
-        int delay;
-        if (profile != BluetoothProfile.A2DP && profile != BluetoothProfile.A2DP_SINK) {
-            throw new IllegalArgumentException("invalid profile " + profile);
-        }
-        synchronized (mConnectedDevices) {
-            if (profile == BluetoothProfile.A2DP && !suppressNoisyIntent) {
-                int intState = (state == BluetoothA2dp.STATE_CONNECTED) ? 1 : 0;
-                delay = checkSendBecomingNoisyIntent(AudioSystem.DEVICE_OUT_BLUETOOTH_A2DP,
-                        intState, musicDevice);
-            } else {
-                delay = 0;
-            }
-
-            int a2dpCodec = getA2dpCodec(device);
-
-            if (DEBUG_DEVICES) {
-                Log.d(TAG, "setBluetoothA2dpDeviceConnectionStateInt device: " + device
-                        + " state: " + state + " delay(ms): " + delay + "codec:" + a2dpCodec
-                        + " suppressNoisyIntent: " + suppressNoisyIntent);
-            }
-
-            queueMsgUnderWakeLock(mAudioHandler,
-                    (profile == BluetoothProfile.A2DP ?
-                        MSG_SET_A2DP_SINK_CONNECTION_STATE : MSG_SET_A2DP_SRC_CONNECTION_STATE),
-                    state,
-                    0, /* arg2 unused */
-                    new BluetoothA2dpDeviceInfo(device, a2dpVolume, a2dpCodec),
-                    delay);
-        }
-        return delay;
-    }
-
+    /**
+     * See AudioManager.handleBluetoothA2dpDeviceConfigChange()
+     * @param device
+     */
     public void handleBluetoothA2dpDeviceConfigChange(BluetoothDevice device)
     {
-        synchronized (mConnectedDevices) {
-            int a2dpCodec = getA2dpCodec(device);
-            queueMsgUnderWakeLock(mAudioHandler,
-                    MSG_A2DP_DEVICE_CONFIG_CHANGE,
-                    0 /* arg1 unused */,
-                    0 /* arg2 unused */,
-                    new BluetoothA2dpDeviceInfo(device, -1, a2dpCodec),
-                    0 /* delay */);
+        if (device == null) {
+            throw new IllegalArgumentException("Illegal null device");
         }
+        mDeviceBroker.postBluetoothA2dpDeviceConfigChange(device);
     }
 
     /**
@@ -4912,52 +3968,18 @@
     public int handleBluetoothA2dpActiveDeviceChange(
             BluetoothDevice device, int state, int profile, boolean suppressNoisyIntent,
             int a2dpVolume) {
+        if (device == null) {
+            throw new IllegalArgumentException("Illegal null device");
+        }
         if (profile != BluetoothProfile.A2DP && profile != BluetoothProfile.A2DP_SINK) {
             throw new IllegalArgumentException("invalid profile " + profile);
         }
-
-        synchronized (mConnectedDevices) {
-            if (state == BluetoothA2dp.STATE_CONNECTED) {
-                for (int i = 0; i < mConnectedDevices.size(); i++) {
-                    DeviceListSpec deviceSpec = mConnectedDevices.valueAt(i);
-                    if (deviceSpec.mDeviceType != AudioSystem.DEVICE_OUT_BLUETOOTH_A2DP) {
-                        continue;
-                    }
-                    // If A2DP device exists, this is either an active device change or
-                    // device config change
-                    String existingDevicekey = mConnectedDevices.keyAt(i);
-                    String deviceName = device.getName();
-                    String address = device.getAddress();
-                    String newDeviceKey = makeDeviceListKey(
-                            AudioSystem.DEVICE_OUT_BLUETOOTH_A2DP, address);
-                    int a2dpCodec = getA2dpCodec(device);
-                    // Device not equal to existing device, active device change
-                    if (!TextUtils.equals(existingDevicekey, newDeviceKey)) {
-                        mConnectedDevices.remove(existingDevicekey);
-                        mConnectedDevices.put(newDeviceKey, new DeviceListSpec(
-                                AudioSystem.DEVICE_OUT_BLUETOOTH_A2DP, deviceName,
-                                address, a2dpCodec));
-                        queueMsgUnderWakeLock(mAudioHandler,
-                                MSG_A2DP_ACTIVE_DEVICE_CHANGE,
-                                0,
-                                0,
-                                new BluetoothA2dpDeviceInfo(
-                                        device, a2dpVolume, a2dpCodec),
-                                0 /* delay */);
-                        return 0;
-                    } else {
-                        // Device config change for existing device
-                        handleBluetoothA2dpDeviceConfigChange(device);
-                        return 0;
-                    }
-                }
-            }
+        if (state != BluetoothProfile.STATE_CONNECTED
+                && state != BluetoothProfile.STATE_DISCONNECTED) {
+            throw new IllegalArgumentException("Invalid state " + state);
         }
-
-        // New device connection or a device disconnect
-        return setBluetoothA2dpDeviceConnectionStateInt(
-                    device, state, profile, suppressNoisyIntent,
-                    AudioSystem.DEVICE_NONE, a2dpVolume);
+        return mDeviceBroker.handleBluetoothA2dpActiveDeviceChange(device, state, profile,
+                suppressNoisyIntent, a2dpVolume);
     }
 
     private static final int DEVICE_MEDIA_UNMUTED_ON_PLUG =
@@ -4967,24 +3989,27 @@
             AudioSystem.DEVICE_OUT_ALL_USB |
             AudioSystem.DEVICE_OUT_HDMI;
 
+    /*package*/ void postAccessoryPlugMediaUnmute(int newDevice) {
+        sendMsg(mAudioHandler, MSG_ACCESSORY_PLUG_MEDIA_UNMUTE, SENDMSG_QUEUE,
+                newDevice, 0, null, 0);
+    }
+
     private void onAccessoryPlugMediaUnmute(int newDevice) {
         if (DEBUG_VOL) {
             Log.i(TAG, String.format("onAccessoryPlugMediaUnmute newDevice=%d [%s]",
                     newDevice, AudioSystem.getOutputDeviceName(newDevice)));
         }
-        synchronized (mConnectedDevices) {
-            if (mNm.getZenMode() != Settings.Global.ZEN_MODE_NO_INTERRUPTIONS
-                    && (newDevice & DEVICE_MEDIA_UNMUTED_ON_PLUG) != 0
-                    && mStreamStates[AudioSystem.STREAM_MUSIC].mIsMuted
-                    && mStreamStates[AudioSystem.STREAM_MUSIC].getIndex(newDevice) != 0
-                    && (newDevice & AudioSystem.getDevicesForStream(AudioSystem.STREAM_MUSIC)) != 0)
-            {
-                if (DEBUG_VOL) {
-                    Log.i(TAG, String.format(" onAccessoryPlugMediaUnmute unmuting device=%d [%s]",
-                            newDevice, AudioSystem.getOutputDeviceName(newDevice)));
-                }
-                mStreamStates[AudioSystem.STREAM_MUSIC].mute(false);
+
+        if (mNm.getZenMode() != Settings.Global.ZEN_MODE_NO_INTERRUPTIONS
+                && (newDevice & DEVICE_MEDIA_UNMUTED_ON_PLUG) != 0
+                && mStreamStates[AudioSystem.STREAM_MUSIC].mIsMuted
+                && mStreamStates[AudioSystem.STREAM_MUSIC].getIndex(newDevice) != 0
+                && (newDevice & AudioSystem.getDevicesForStream(AudioSystem.STREAM_MUSIC)) != 0) {
+            if (DEBUG_VOL) {
+                Log.i(TAG, String.format(" onAccessoryPlugMediaUnmute unmuting device=%d [%s]",
+                        newDevice, AudioSystem.getOutputDeviceName(newDevice)));
             }
+            mStreamStates[AudioSystem.STREAM_MUSIC].mute(false);
         }
     }
 
@@ -4994,7 +4019,7 @@
 
     // NOTE: Locking order for synchronized objects related to volume or ringer mode management:
     //  1 mScoclient OR mSafeMediaVolumeState
-    //  2   mSetModeDeathHandlers
+    //  2   mSetModeLock
     //  3     mSettingsLock
     //  4       VolumeStreamState.class
     public class VolumeStreamState {
@@ -5138,11 +4163,11 @@
         }
 
         // must be called while synchronized VolumeStreamState.class
-        public void applyDeviceVolume_syncVSS(int device) {
+        /*package*/ void applyDeviceVolume_syncVSS(int device, boolean isAvrcpAbsVolSupported) {
             int index;
             if (mIsMuted) {
                 index = 0;
-            } else if ((device & AudioSystem.DEVICE_OUT_ALL_A2DP) != 0 && mAvrcpAbsVolSupported) {
+            } else if ((device & AudioSystem.DEVICE_OUT_ALL_A2DP) != 0 && isAvrcpAbsVolSupported) {
                 index = getAbsoluteVolumeIndex((getIndex(device) + 5)/10);
             } else if ((device & mFullVolumeDevices) != 0) {
                 index = (mIndexMax + 5)/10;
@@ -5151,10 +4176,11 @@
             } else {
                 index = (getIndex(device) + 5)/10;
             }
-            AudioSystem.setStreamVolumeIndex(mStreamType, index, device);
+            AudioSystem.setStreamVolumeIndexAS(mStreamType, index, device);
         }
 
         public void applyAllVolumes() {
+            final boolean isAvrcpAbsVolSupported = mDeviceBroker.isAvrcpAbsoluteVolumeSupported();
             synchronized (VolumeStreamState.class) {
                 // apply device specific volumes first
                 int index;
@@ -5164,7 +4190,7 @@
                         if (mIsMuted) {
                             index = 0;
                         } else if ((device & AudioSystem.DEVICE_OUT_ALL_A2DP) != 0 &&
-                                mAvrcpAbsVolSupported) {
+                                isAvrcpAbsVolSupported) {
                             index = getAbsoluteVolumeIndex((getIndex(device) + 5)/10);
                         } else if ((device & mFullVolumeDevices) != 0) {
                             index = (mIndexMax + 5)/10;
@@ -5173,7 +4199,7 @@
                         } else {
                             index = (mIndexMap.valueAt(i) + 5)/10;
                         }
-                        AudioSystem.setStreamVolumeIndex(mStreamType, index, device);
+                        AudioSystem.setStreamVolumeIndexAS(mStreamType, index, device);
                     }
                 }
                 // apply default volume last: by convention , default device volume will be used
@@ -5183,7 +4209,7 @@
                 } else {
                     index = (getIndex(AudioSystem.DEVICE_OUT_DEFAULT) + 5)/10;
                 }
-                AudioSystem.setStreamVolumeIndex(
+                AudioSystem.setStreamVolumeIndexAS(
                         mStreamType, index, AudioSystem.DEVICE_OUT_DEFAULT);
             }
         }
@@ -5373,6 +4399,7 @@
         }
 
         public void checkFixedVolumeDevices() {
+            final boolean isAvrcpAbsVolSupported = mDeviceBroker.isAvrcpAbsoluteVolumeSupported();
             synchronized (VolumeStreamState.class) {
                 // ignore settings for fixed volume devices: volume should always be at max or 0
                 if (mStreamVolumeAlias[mStreamType] == AudioSystem.STREAM_MUSIC) {
@@ -5383,7 +4410,7 @@
                                 || (((device & mFixedVolumeDevices) != 0) && index != 0)) {
                             mIndexMap.put(device, mIndexMax);
                         }
-                        applyDeviceVolume_syncVSS(device);
+                        applyDeviceVolume_syncVSS(device, isAvrcpAbsVolSupported);
                     }
                 }
             }
@@ -5465,11 +4492,13 @@
         }
     }
 
-    private void setDeviceVolume(VolumeStreamState streamState, int device) {
+    /*package*/ void setDeviceVolume(VolumeStreamState streamState, int device) {
+
+        final boolean isAvrcpAbsVolSupported = mDeviceBroker.isAvrcpAbsoluteVolumeSupported();
 
         synchronized (VolumeStreamState.class) {
             // Apply volume
-            streamState.applyDeviceVolume_syncVSS(device);
+            streamState.applyDeviceVolume_syncVSS(device, isAvrcpAbsVolSupported);
 
             // Apply change to all streams using this one as alias
             int numStreamTypes = AudioSystem.getNumStreamTypes();
@@ -5479,11 +4508,13 @@
                     // Make sure volume is also maxed out on A2DP device for aliased stream
                     // that may have a different device selected
                     int streamDevice = getDeviceForStream(streamType);
-                    if ((device != streamDevice) && mAvrcpAbsVolSupported &&
-                            ((device & AudioSystem.DEVICE_OUT_ALL_A2DP) != 0)) {
-                        mStreamStates[streamType].applyDeviceVolume_syncVSS(device);
+                    if ((device != streamDevice) && isAvrcpAbsVolSupported
+                            && ((device & AudioSystem.DEVICE_OUT_ALL_A2DP) != 0)) {
+                        mStreamStates[streamType].applyDeviceVolume_syncVSS(device,
+                                isAvrcpAbsVolSupported);
                     }
-                    mStreamStates[streamType].applyDeviceVolume_syncVSS(streamDevice);
+                    mStreamStates[streamType].applyDeviceVolume_syncVSS(streamDevice,
+                            isAvrcpAbsVolSupported);
                 }
             }
         }
@@ -5762,12 +4793,6 @@
             }
         }
 
-        private void setForceUse(int usage, int config, String eventSource) {
-            synchronized (mConnectedDevices) {
-                setForceUseInt_SyncDevices(usage, config, eventSource);
-            }
-        }
-
         private void onPersistSafeVolumeState(int state) {
             Settings.Global.putInt(mContentResolver,
                     Settings.Global.AUDIO_SAFE_VOLUME_STATE,
@@ -5834,56 +4859,20 @@
                     onPlaySoundEffect(msg.arg1, msg.arg2);
                     break;
 
-                case MSG_BTA2DP_DOCK_TIMEOUT:
-                    // msg.obj  == address of BTA2DP device
-                    synchronized (mConnectedDevices) {
-                        makeA2dpDeviceUnavailableNow((String) msg.obj, msg.arg1);
-                    }
-                    mAudioEventWakeLock.release();
-                    break;
-
                 case MSG_SET_FORCE_USE:
-                case MSG_SET_FORCE_BT_A2DP_USE:
-                    setForceUse(msg.arg1, msg.arg2, (String) msg.obj);
-                    break;
-
-                case MSG_BT_HEADSET_CNCT_FAILED:
-                    resetBluetoothSco();
-                    break;
-
-                case MSG_SET_WIRED_DEVICE_CONNECTION_STATE:
-                    {   WiredDeviceConnectionState connectState =
-                            (WiredDeviceConnectionState)msg.obj;
-                        mDeviceLogger.log(new WiredDevConnectEvent(connectState));
-                        onSetWiredDeviceConnectionState(connectState.mType, connectState.mState,
-                                connectState.mAddress, connectState.mName, connectState.mCaller);
-                        mAudioEventWakeLock.release();
+                {
+                    final String eventSource = (String) msg.obj;
+                    final int useCase = msg.arg1;
+                    final int config = msg.arg2;
+                    if (useCase == AudioSystem.FOR_MEDIA) {
+                        Log.wtf(TAG, "Invalid force use FOR_MEDIA in AudioService from "
+                                + eventSource);
+                        break;
                     }
-                    break;
-
-                case MSG_SET_A2DP_SRC_CONNECTION_STATE:
-                    onSetA2dpSourceConnectionState((BluetoothA2dpDeviceInfo) msg.obj, msg.arg1);
-                    mAudioEventWakeLock.release();
-                    break;
-
-                case MSG_SET_A2DP_SINK_CONNECTION_STATE:
-                    onSetA2dpSinkConnectionState((BluetoothA2dpDeviceInfo) msg.obj, msg.arg1);
-                    mAudioEventWakeLock.release();
-                    break;
-
-                case MSG_SET_HEARING_AID_CONNECTION_STATE:
-                    onSetHearingAidConnectionState((BluetoothDevice)msg.obj, msg.arg1);
-                    mAudioEventWakeLock.release();
-                    break;
-
-                case MSG_A2DP_DEVICE_CONFIG_CHANGE:
-                    onBluetoothA2dpDeviceConfigChange((BluetoothA2dpDeviceInfo) msg.obj);
-                    mAudioEventWakeLock.release();
-                    break;
-
-                case MSG_A2DP_ACTIVE_DEVICE_CHANGE:
-                    onBluetoothA2dpActiveDeviceChange((BluetoothA2dpDeviceInfo) msg.obj);
-                    mAudioEventWakeLock.release();
+                    sForceUseLogger.log(
+                            new AudioServiceEvents.ForceUseEvent(useCase, config, eventSource));
+                    AudioSystem.setForceUse(useCase, config);
+                }
                     break;
 
                 case MSG_DISABLE_AUDIO_FOR_UID:
@@ -5892,35 +4881,10 @@
                     mAudioEventWakeLock.release();
                     break;
 
-                case MSG_REPORT_NEW_ROUTES: {
-                    int N = mRoutesObservers.beginBroadcast();
-                    if (N > 0) {
-                        AudioRoutesInfo routes;
-                        synchronized (mCurAudioRoutes) {
-                            routes = new AudioRoutesInfo(mCurAudioRoutes);
-                        }
-                        while (N > 0) {
-                            N--;
-                            IAudioRoutesObserver obs = mRoutesObservers.getBroadcastItem(N);
-                            try {
-                                obs.dispatchAudioRoutesChanged(routes);
-                            } catch (RemoteException e) {
-                            }
-                        }
-                    }
-                    mRoutesObservers.finishBroadcast();
-                    observeDevicesForStreams(-1);
-                    break;
-                }
-
                 case MSG_CHECK_MUSIC_ACTIVE:
                     onCheckMusicActive((String) msg.obj);
                     break;
 
-                case MSG_BROADCAST_AUDIO_BECOMING_NOISY:
-                    onSendBecomingNoisyIntent();
-                    break;
-
                 case MSG_CONFIGURE_SAFE_MEDIA_VOLUME_FORCED:
                 case MSG_CONFIGURE_SAFE_MEDIA_VOLUME:
                     onConfigureSafeVolume((msg.what == MSG_CONFIGURE_SAFE_MEDIA_VOLUME_FORCED),
@@ -5930,10 +4894,6 @@
                     onPersistSafeVolumeState(msg.arg1);
                     break;
 
-                case MSG_BROADCAST_BT_CONNECTION_STATE:
-                    onBroadcastScoConnectionState(msg.arg1);
-                    break;
-
                 case MSG_SYSTEM_READY:
                     onSystemReady();
                     break;
@@ -6033,20 +4993,7 @@
             if (mEncodedSurroundMode != newSurroundMode) {
                 // Send to AudioPolicyManager
                 sendEncodedSurroundMode(newSurroundMode, "SettingsObserver");
-                synchronized(mConnectedDevices) {
-                    // Is HDMI connected?
-                    String key = makeDeviceListKey(AudioSystem.DEVICE_OUT_HDMI, "");
-                    DeviceListSpec deviceSpec = mConnectedDevices.get(key);
-                    if (deviceSpec != null) {
-                        // Toggle HDMI to retrigger broadcast with proper formats.
-                        setWiredDeviceConnectionState(AudioSystem.DEVICE_OUT_HDMI,
-                                AudioSystem.DEVICE_STATE_UNAVAILABLE, "", "",
-                                "android"); // disconnect
-                        setWiredDeviceConnectionState(AudioSystem.DEVICE_OUT_HDMI,
-                                AudioSystem.DEVICE_STATE_AVAILABLE, "", "",
-                                "android"); // reconnect
-                    }
-                }
+                mDeviceBroker.toggleHdmiIfConnected_Async();
                 mEncodedSurroundMode = newSurroundMode;
                 mSurroundModeChanged = true;
             } else {
@@ -6055,515 +5002,18 @@
         }
     }
 
-    // must be called synchronized on mConnectedDevices
-    private void makeA2dpDeviceAvailable(
-            String address, String name, String eventSource, int a2dpCodec) {
-        // enable A2DP before notifying A2DP connection to avoid unnecessary processing in
-        // audio policy manager
-        VolumeStreamState streamState = mStreamStates[AudioSystem.STREAM_MUSIC];
-        setBluetoothA2dpOnInt(true, eventSource);
-        AudioSystem.setDeviceConnectionState(AudioSystem.DEVICE_OUT_BLUETOOTH_A2DP,
-                AudioSystem.DEVICE_STATE_AVAILABLE, address, name, a2dpCodec);
-        // Reset A2DP suspend state each time a new sink is connected
-        AudioSystem.setParameters("A2dpSuspended=false");
-        mConnectedDevices.put(
-                makeDeviceListKey(AudioSystem.DEVICE_OUT_BLUETOOTH_A2DP, address),
-                new DeviceListSpec(AudioSystem.DEVICE_OUT_BLUETOOTH_A2DP, name,
-                                   address, a2dpCodec));
-        sendMsg(mAudioHandler, MSG_ACCESSORY_PLUG_MEDIA_UNMUTE, SENDMSG_QUEUE,
-                AudioSystem.DEVICE_OUT_BLUETOOTH_A2DP, 0, null, 0);
-        setCurrentAudioRouteNameIfPossible(name);
-    }
-
-    private void onSendBecomingNoisyIntent() {
-        mDeviceLogger.log((new AudioEventLogger.StringEvent(
-                "broadcast ACTION_AUDIO_BECOMING_NOISY")).printLog(TAG));
-        sendBroadcastToAll(new Intent(AudioManager.ACTION_AUDIO_BECOMING_NOISY));
-    }
-
-    // must be called synchronized on mConnectedDevices
-    private void makeA2dpDeviceUnavailableNow(String address, int a2dpCodec) {
-        if (address == null) {
-            return;
-        }
-        synchronized (mA2dpAvrcpLock) {
-            mAvrcpAbsVolSupported = false;
-        }
-        AudioSystem.setDeviceConnectionState(AudioSystem.DEVICE_OUT_BLUETOOTH_A2DP,
-                AudioSystem.DEVICE_STATE_UNAVAILABLE, address, "", a2dpCodec);
-        mConnectedDevices.remove(
-                makeDeviceListKey(AudioSystem.DEVICE_OUT_BLUETOOTH_A2DP, address));
-        // Remove A2DP routes as well
-        setCurrentAudioRouteNameIfPossible(null);
-        if (mDockAddress == address) {
-            mDockAddress = null;
-        }
-    }
-
-    // must be called synchronized on mConnectedDevices
-    private void makeA2dpDeviceUnavailableLater(String address, int delayMs) {
-        // prevent any activity on the A2DP audio output to avoid unwanted
-        // reconnection of the sink.
-        AudioSystem.setParameters("A2dpSuspended=true");
-        // Retrieve deviceSpec before removing device
-        String deviceKey = makeDeviceListKey(AudioSystem.DEVICE_OUT_BLUETOOTH_A2DP, address);
-        DeviceListSpec deviceSpec = mConnectedDevices.get(deviceKey);
-        int a2dpCodec = deviceSpec != null ? deviceSpec.mDeviceCodecFormat :
-                                AudioSystem.AUDIO_FORMAT_DEFAULT;
-        // the device will be made unavailable later, so consider it disconnected right away
-        mConnectedDevices.remove(deviceKey);
-        // send the delayed message to make the device unavailable later
-        queueMsgUnderWakeLock(mAudioHandler,
-                MSG_BTA2DP_DOCK_TIMEOUT,
-                a2dpCodec,
-                0,
-                address,
-                delayMs);
-    }
-
-    // must be called synchronized on mConnectedDevices
-    private void makeA2dpSrcAvailable(String address) {
-        AudioSystem.setDeviceConnectionState(AudioSystem.DEVICE_IN_BLUETOOTH_A2DP,
-                AudioSystem.DEVICE_STATE_AVAILABLE, address, "",
-                AudioSystem.AUDIO_FORMAT_DEFAULT);
-        mConnectedDevices.put(
-                makeDeviceListKey(AudioSystem.DEVICE_IN_BLUETOOTH_A2DP, address),
-                new DeviceListSpec(AudioSystem.DEVICE_IN_BLUETOOTH_A2DP, "",
-                                   address, AudioSystem.AUDIO_FORMAT_DEFAULT));
-    }
-
-    // must be called synchronized on mConnectedDevices
-    private void makeA2dpSrcUnavailable(String address) {
-        AudioSystem.setDeviceConnectionState(AudioSystem.DEVICE_IN_BLUETOOTH_A2DP,
-                AudioSystem.DEVICE_STATE_UNAVAILABLE, address, "",
-                AudioSystem.AUDIO_FORMAT_DEFAULT);
-        mConnectedDevices.remove(
-                makeDeviceListKey(AudioSystem.DEVICE_IN_BLUETOOTH_A2DP, address));
-    }
-
-    private void setHearingAidVolume(int index, int streamType) {
-        synchronized (mHearingAidLock) {
-            if (mHearingAid != null) {
-                //hearing aid expect volume value in range -128dB to 0dB
-                int gainDB = (int)AudioSystem.getStreamVolumeDB(streamType, index/10,
-                        AudioSystem.DEVICE_OUT_HEARING_AID);
-                if (gainDB < BT_HEARING_AID_GAIN_MIN)
-                    gainDB = BT_HEARING_AID_GAIN_MIN;
-                mHearingAid.setVolume(gainDB);
-            }
-        }
-    }
-
-    // must be called synchronized on mConnectedDevices
-    private void makeHearingAidDeviceAvailable(String address, String name, String eventSource) {
-        int index = mStreamStates[AudioSystem.STREAM_MUSIC].getIndex(AudioSystem.DEVICE_OUT_HEARING_AID);
-        setHearingAidVolume(index, AudioSystem.STREAM_MUSIC);
-
-        AudioSystem.setDeviceConnectionState(AudioSystem.DEVICE_OUT_HEARING_AID,
-                AudioSystem.DEVICE_STATE_AVAILABLE, address, name,
-                AudioSystem.AUDIO_FORMAT_DEFAULT);
-        mConnectedDevices.put(
-                makeDeviceListKey(AudioSystem.DEVICE_OUT_HEARING_AID, address),
-                new DeviceListSpec(AudioSystem.DEVICE_OUT_HEARING_AID, name,
-                                   address, AudioSystem.AUDIO_FORMAT_DEFAULT));
-        sendMsg(mAudioHandler, MSG_ACCESSORY_PLUG_MEDIA_UNMUTE, SENDMSG_QUEUE,
-                AudioSystem.DEVICE_OUT_HEARING_AID, 0, null, 0);
-        sendMsg(mAudioHandler, MSG_SET_DEVICE_VOLUME, SENDMSG_QUEUE,
-                            AudioSystem.DEVICE_OUT_HEARING_AID, 0,
-                            mStreamStates[AudioSystem.STREAM_MUSIC], 0);
-        setCurrentAudioRouteNameIfPossible(name);
-    }
-
-    // must be called synchronized on mConnectedDevices
-    private void makeHearingAidDeviceUnavailable(String address) {
-        AudioSystem.setDeviceConnectionState(AudioSystem.DEVICE_OUT_HEARING_AID,
-                AudioSystem.DEVICE_STATE_UNAVAILABLE, address, "",
-                AudioSystem.AUDIO_FORMAT_DEFAULT);
-        mConnectedDevices.remove(
-                makeDeviceListKey(AudioSystem.DEVICE_OUT_HEARING_AID, address));
-        // Remove Hearing Aid routes as well
-        setCurrentAudioRouteNameIfPossible(null);
-    }
-
-    // must be called synchronized on mConnectedDevices
-    private void cancelA2dpDeviceTimeout() {
-        mAudioHandler.removeMessages(MSG_BTA2DP_DOCK_TIMEOUT);
-    }
-
-    // must be called synchronized on mConnectedDevices
-    private boolean hasScheduledA2dpDockTimeout() {
-        return mAudioHandler.hasMessages(MSG_BTA2DP_DOCK_TIMEOUT);
-    }
-
-    private void onSetA2dpSinkConnectionState(BluetoothA2dpDeviceInfo btInfo, int state)
-    {
-        if (btInfo == null) {
-            return;
-        }
-
-        BluetoothDevice btDevice = btInfo.getBtDevice();
-        int a2dpVolume = btInfo.getVolume();
-        int a2dpCodec = btInfo.getCodec();
-
-        if (btDevice == null) {
-            return;
-        }
-        if (DEBUG_DEVICES) {
-            Log.d(TAG, "onSetA2dpSinkConnectionState btDevice= " + btDevice + " state= " + state
-                    + " is dock: " + btDevice.isBluetoothDock());
-        }
-        String address = btDevice.getAddress();
-        if (!BluetoothAdapter.checkBluetoothAddress(address)) {
-            address = "";
-        }
-
-        synchronized (mConnectedDevices) {
-            final String key = makeDeviceListKey(AudioSystem.DEVICE_OUT_BLUETOOTH_A2DP,
-                                                 btDevice.getAddress());
-            final DeviceListSpec deviceSpec = mConnectedDevices.get(key);
-            boolean isConnected = deviceSpec != null;
-
-            if (isConnected && state != BluetoothProfile.STATE_CONNECTED) {
-                if (btDevice.isBluetoothDock()) {
-                    if (state == BluetoothProfile.STATE_DISCONNECTED) {
-                        // introduction of a delay for transient disconnections of docks when
-                        // power is rapidly turned off/on, this message will be canceled if
-                        // we reconnect the dock under a preset delay
-                        makeA2dpDeviceUnavailableLater(address, BTA2DP_DOCK_TIMEOUT_MILLIS);
-                        // the next time isConnected is evaluated, it will be false for the dock
-                    }
-                } else {
-                    makeA2dpDeviceUnavailableNow(address, deviceSpec.mDeviceCodecFormat);
-                }
-            } else if (!isConnected && state == BluetoothProfile.STATE_CONNECTED) {
-                if (btDevice.isBluetoothDock()) {
-                    // this could be a reconnection after a transient disconnection
-                    cancelA2dpDeviceTimeout();
-                    mDockAddress = address;
-                } else {
-                    // this could be a connection of another A2DP device before the timeout of
-                    // a dock: cancel the dock timeout, and make the dock unavailable now
-                    if (hasScheduledA2dpDockTimeout() && mDockAddress != null) {
-                        cancelA2dpDeviceTimeout();
-                        makeA2dpDeviceUnavailableNow(mDockAddress,
-                                AudioSystem.AUDIO_FORMAT_DEFAULT);
-                    }
-                }
-                if (a2dpVolume != -1) {
-                    VolumeStreamState streamState = mStreamStates[AudioSystem.STREAM_MUSIC];
-                    // Convert index to internal representation in VolumeStreamState
-                    a2dpVolume = a2dpVolume * 10;
-                    streamState.setIndex(a2dpVolume, AudioSystem.DEVICE_OUT_BLUETOOTH_A2DP,
-                            "onSetA2dpSinkConnectionState");
-                    setDeviceVolume(streamState, AudioSystem.DEVICE_OUT_BLUETOOTH_A2DP);
-                }
-                makeA2dpDeviceAvailable(address, btDevice.getName(),
-                        "onSetA2dpSinkConnectionState", a2dpCodec);
-            }
-        }
-    }
-
-    private void onSetA2dpSourceConnectionState(BluetoothA2dpDeviceInfo btInfo, int state)
-    {
-        if (btInfo == null) {
-            return;
-        }
-        BluetoothDevice btDevice = btInfo.getBtDevice();
-
-        if (DEBUG_VOL) {
-            Log.d(TAG, "onSetA2dpSourceConnectionState btDevice=" + btDevice + " state=" + state);
-        }
-        if (btDevice == null) {
-            return;
-        }
-        String address = btDevice.getAddress();
-        if (!BluetoothAdapter.checkBluetoothAddress(address)) {
-            address = "";
-        }
-
-        synchronized (mConnectedDevices) {
-            final String key = makeDeviceListKey(AudioSystem.DEVICE_IN_BLUETOOTH_A2DP, address);
-            final DeviceListSpec deviceSpec = mConnectedDevices.get(key);
-            boolean isConnected = deviceSpec != null;
-
-            if (isConnected && state != BluetoothProfile.STATE_CONNECTED) {
-                makeA2dpSrcUnavailable(address);
-            } else if (!isConnected && state == BluetoothProfile.STATE_CONNECTED) {
-                makeA2dpSrcAvailable(address);
-            }
-        }
-    }
-
-    private void onSetHearingAidConnectionState(BluetoothDevice btDevice, int state)
-    {
-        if (DEBUG_DEVICES) {
-            Log.d(TAG, "onSetHearingAidConnectionState btDevice=" + btDevice+", state=" + state);
-        }
-        if (btDevice == null) {
-            return;
-        }
-        String address = btDevice.getAddress();
-        if (!BluetoothAdapter.checkBluetoothAddress(address)) {
-            address = "";
-        }
-
-        synchronized (mConnectedDevices) {
-            final String key = makeDeviceListKey(AudioSystem.DEVICE_OUT_HEARING_AID,
-                                                 btDevice.getAddress());
-            final DeviceListSpec deviceSpec = mConnectedDevices.get(key);
-            boolean isConnected = deviceSpec != null;
-
-            if (isConnected && state != BluetoothProfile.STATE_CONNECTED) {
-                makeHearingAidDeviceUnavailable(address);
-            } else if (!isConnected && state == BluetoothProfile.STATE_CONNECTED) {
-                makeHearingAidDeviceAvailable(address, btDevice.getName(),
-                        "onSetHearingAidConnectionState");
-            }
-        }
-    }
-
-    private void setCurrentAudioRouteNameIfPossible(String name) {
-        synchronized (mCurAudioRoutes) {
-            if (!TextUtils.equals(mCurAudioRoutes.bluetoothName, name)) {
-                if (name != null || !isCurrentDeviceConnected()) {
-                    mCurAudioRoutes.bluetoothName = name;
-                    sendMsg(mAudioHandler, MSG_REPORT_NEW_ROUTES,
-                            SENDMSG_NOOP, 0, 0, null, 0);
-                }
-            }
-        }
-    }
-
-    private boolean isCurrentDeviceConnected() {
-        for (int i = 0; i < mConnectedDevices.size(); i++) {
-            DeviceListSpec deviceSpec = mConnectedDevices.valueAt(i);
-            if (TextUtils.equals(deviceSpec.mDeviceName, mCurAudioRoutes.bluetoothName)) {
-                return true;
-            }
-        }
-        return false;
-    }
-
-    private void onBluetoothA2dpDeviceConfigChange(BluetoothA2dpDeviceInfo btInfo)
-    {
-        if (btInfo == null) {
-            return;
-        }
-        BluetoothDevice btDevice = btInfo.getBtDevice();
-        int a2dpCodec = btInfo.getCodec();
-
-        if (btDevice == null) {
-            return;
-        }
-        if (DEBUG_DEVICES) {
-            Log.d(TAG, "onBluetoothA2dpDeviceConfigChange btDevice=" + btDevice);
-        }
-        String address = btDevice.getAddress();
-        if (!BluetoothAdapter.checkBluetoothAddress(address)) {
-            address = "";
-        }
-        mDeviceLogger.log(new AudioEventLogger.StringEvent(
-                "onBluetoothA2dpDeviceConfigChange addr=" + address));
-
-        int device = AudioSystem.DEVICE_OUT_BLUETOOTH_A2DP;
-        synchronized (mConnectedDevices) {
-            if (mAudioHandler.hasMessages(MSG_SET_A2DP_SINK_CONNECTION_STATE, btDevice)) {
-                mDeviceLogger.log(new AudioEventLogger.StringEvent(
-                        "A2dp config change ignored"));
-                return;
-            }
-            final String key = makeDeviceListKey(device, address);
-            final DeviceListSpec deviceSpec = mConnectedDevices.get(key);
-            if (deviceSpec == null) {
-                return;
-            }
-            // Device is connected
-            if (deviceSpec.mDeviceCodecFormat != a2dpCodec) {
-                deviceSpec.mDeviceCodecFormat = a2dpCodec;
-                mConnectedDevices.replace(key, deviceSpec);
-            }
-            if (DEBUG_DEVICES) {
-                Log.d(TAG, "onBluetoothA2dpDeviceConfigChange: codec="
-                        + deviceSpec.mDeviceCodecFormat);
-            }
-            if (AudioSystem.handleDeviceConfigChange(device, address,
-                       btDevice.getName(), deviceSpec.mDeviceCodecFormat)
-                       != AudioSystem.AUDIO_STATUS_OK) {
-                int musicDevice = getDeviceForStream(AudioSystem.STREAM_MUSIC);
-                // force A2DP device disconnection in case of error so that AudioService state is
-                // consistent with audio policy manager state
-                setBluetoothA2dpDeviceConnectionStateInt(
-                        btDevice, BluetoothA2dp.STATE_DISCONNECTED, BluetoothProfile.A2DP,
-                        false /* suppressNoisyIntent */, musicDevice, -1 /* a2dpVolume */);
-            }
-        }
-    }
-
-    /** message handler for MSG_A2DP_ACTIVE_DEVICE_CHANGE */
-    public void onBluetoothA2dpActiveDeviceChange(BluetoothA2dpDeviceInfo btInfo) {
-        if (btInfo == null) {
-            return;
-        }
-        BluetoothDevice btDevice = btInfo.getBtDevice();
-        int a2dpVolume = btInfo.getVolume();
-        int a2dpCodec = btInfo.getCodec();
-
-        if (btDevice == null) {
-            return;
-        }
-        if (DEBUG_DEVICES) {
-            Log.d(TAG, "onBluetoothA2dpActiveDeviceChange btDevice=" + btDevice);
-        }
-        String address = btDevice.getAddress();
-        if (!BluetoothAdapter.checkBluetoothAddress(address)) {
-            address = "";
-        }
-        mDeviceLogger.log(new AudioEventLogger.StringEvent(
-                "onBluetoothA2dpActiveDeviceChange addr=" + address));
-
-        int device = AudioSystem.DEVICE_OUT_BLUETOOTH_A2DP;
-        synchronized (mConnectedDevices) {
-            if (mAudioHandler.hasMessages(MSG_SET_A2DP_SINK_CONNECTION_STATE, btDevice)) {
-                mDeviceLogger.log(new AudioEventLogger.StringEvent(
-                        "A2dp config change ignored"));
-                return;
-            }
-            final String key = makeDeviceListKey(device, address);
-            final DeviceListSpec deviceSpec = mConnectedDevices.get(key);
-            if (deviceSpec == null) {
-                return;
-            }
-
-            // Device is connected
-            if (a2dpVolume != -1) {
-                VolumeStreamState streamState = mStreamStates[AudioSystem.STREAM_MUSIC];
-                // Convert index to internal representation in VolumeStreamState
-                a2dpVolume = a2dpVolume * 10;
-                streamState.setIndex(a2dpVolume, device,
-                        "onBluetoothA2dpActiveDeviceChange");
-                setDeviceVolume(streamState, device);
-            }
-
-            if (AudioSystem.handleDeviceConfigChange(device, address,
-                    btDevice.getName(), a2dpCodec) != AudioSystem.AUDIO_STATUS_OK) {
-                int musicDevice = getDeviceForStream(AudioSystem.STREAM_MUSIC);
-                // force A2DP device disconnection in case of error so that AudioService state is
-                // consistent with audio policy manager state
-                setBluetoothA2dpDeviceConnectionStateInt(
-                        btDevice, BluetoothA2dp.STATE_DISCONNECTED, BluetoothProfile.A2DP,
-                        false /* suppressNoisyIntent */, musicDevice, -1 /* a2dpVolume */);
-            }
-        }
-    }
-
     public void avrcpSupportsAbsoluteVolume(String address, boolean support) {
         // address is not used for now, but may be used when multiple a2dp devices are supported
-        synchronized (mA2dpAvrcpLock) {
-            mAvrcpAbsVolSupported = support;
-            sendMsg(mAudioHandler, MSG_SET_DEVICE_VOLUME, SENDMSG_QUEUE,
+        mDeviceBroker.setAvrcpAbsoluteVolumeSupported(support);
+        sendMsg(mAudioHandler, MSG_SET_DEVICE_VOLUME, SENDMSG_QUEUE,
                     AudioSystem.DEVICE_OUT_BLUETOOTH_A2DP, 0,
                     mStreamStates[AudioSystem.STREAM_MUSIC], 0);
-        }
-    }
-
-    private boolean handleDeviceConnection(boolean connect, int device, String address,
-            String deviceName) {
-        if (DEBUG_DEVICES) {
-            Slog.i(TAG, "handleDeviceConnection(" + connect + " dev:" + Integer.toHexString(device)
-                    + " address:" + address + " name:" + deviceName + ")");
-        }
-        synchronized (mConnectedDevices) {
-            String deviceKey = makeDeviceListKey(device, address);
-            if (DEBUG_DEVICES) {
-                Slog.i(TAG, "deviceKey:" + deviceKey);
-            }
-            DeviceListSpec deviceSpec = mConnectedDevices.get(deviceKey);
-            boolean isConnected = deviceSpec != null;
-            if (DEBUG_DEVICES) {
-                Slog.i(TAG, "deviceSpec:" + deviceSpec + " is(already)Connected:" + isConnected);
-            }
-            if (connect && !isConnected) {
-                final int res = AudioSystem.setDeviceConnectionState(device,
-                        AudioSystem.DEVICE_STATE_AVAILABLE, address, deviceName,
-                        AudioSystem.AUDIO_FORMAT_DEFAULT);
-                if (res != AudioSystem.AUDIO_STATUS_OK) {
-                    Slog.e(TAG, "not connecting device 0x" + Integer.toHexString(device) +
-                            " due to command error " + res );
-                    return false;
-                }
-                mConnectedDevices.put(deviceKey, new DeviceListSpec(device,
-                                deviceName, address, AudioSystem.AUDIO_FORMAT_DEFAULT));
-                sendMsg(mAudioHandler, MSG_ACCESSORY_PLUG_MEDIA_UNMUTE, SENDMSG_QUEUE,
-                        device, 0, null, 0);
-                return true;
-            } else if (!connect && isConnected) {
-                AudioSystem.setDeviceConnectionState(device,
-                        AudioSystem.DEVICE_STATE_UNAVAILABLE, address, deviceName,
-                        AudioSystem.AUDIO_FORMAT_DEFAULT);
-                // always remove even if disconnection failed
-                mConnectedDevices.remove(deviceKey);
-                return true;
-            }
-            Log.w(TAG, "handleDeviceConnection() failed, deviceKey=" + deviceKey + ", deviceSpec="
-                       + deviceSpec + ", connect=" + connect);
-        }
-        return false;
-    }
-
-    // Devices which removal triggers intent ACTION_AUDIO_BECOMING_NOISY. The intent is only
-    // sent if:
-    // - none of these devices are connected anymore after one is disconnected AND
-    // - the device being disconnected is actually used for music.
-    // Access synchronized on mConnectedDevices
-    int mBecomingNoisyIntentDevices =
-            AudioSystem.DEVICE_OUT_WIRED_HEADSET | AudioSystem.DEVICE_OUT_WIRED_HEADPHONE |
-            AudioSystem.DEVICE_OUT_ALL_A2DP | AudioSystem.DEVICE_OUT_HDMI |
-            AudioSystem.DEVICE_OUT_ANLG_DOCK_HEADSET | AudioSystem.DEVICE_OUT_DGTL_DOCK_HEADSET |
-            AudioSystem.DEVICE_OUT_ALL_USB | AudioSystem.DEVICE_OUT_LINE |
-            AudioSystem.DEVICE_OUT_HEARING_AID;
-
-    // must be called before removing the device from mConnectedDevices
-    // Called synchronized on mConnectedDevices
-    // musicDevice argument is used when not AudioSystem.DEVICE_NONE instead of querying
-    // from AudioSystem
-    private int checkSendBecomingNoisyIntent(int device, int state, int musicDevice) {
-        int delay = 0;
-        if ((state == 0) && ((device & mBecomingNoisyIntentDevices) != 0)) {
-            int devices = 0;
-            for (int i = 0; i < mConnectedDevices.size(); i++) {
-                int dev = mConnectedDevices.valueAt(i).mDeviceType;
-                if (((dev & AudioSystem.DEVICE_BIT_IN) == 0)
-                        && ((dev & mBecomingNoisyIntentDevices) != 0)) {
-                    devices |= dev;
-                }
-            }
-            if (musicDevice == AudioSystem.DEVICE_NONE) {
-                musicDevice = getDeviceForStream(AudioSystem.STREAM_MUSIC);
-            }
-            // ignore condition on device being actually used for music when in communication
-            // because music routing is altered in this case.
-            // also checks whether media routing if affected by a dynamic policy
-            if (((device == musicDevice) || isInCommunication()) && (device == devices)
-                    && !hasMediaDynamicPolicy()) {
-                mAudioHandler.removeMessages(MSG_BROADCAST_AUDIO_BECOMING_NOISY);
-                sendMsg(mAudioHandler,
-                        MSG_BROADCAST_AUDIO_BECOMING_NOISY,
-                        SENDMSG_REPLACE,
-                        0,
-                        0,
-                        null,
-                        0);
-                delay = 1000;
-            }
-        }
-
-        return delay;
     }
 
     /**
      * @return true if there is currently a registered dynamic mixing policy that affects media
      */
-    private boolean hasMediaDynamicPolicy() {
+    /*package*/ boolean hasMediaDynamicPolicy() {
         synchronized (mAudioPolicies) {
             if (mAudioPolicies.isEmpty()) {
                 return false;
@@ -6578,213 +5028,25 @@
         }
     }
 
-    private void updateAudioRoutes(int device, int state)
-    {
-        int connType = 0;
-
-        if (device == AudioSystem.DEVICE_OUT_WIRED_HEADSET) {
-            connType = AudioRoutesInfo.MAIN_HEADSET;
-        } else if (device == AudioSystem.DEVICE_OUT_WIRED_HEADPHONE ||
-                   device == AudioSystem.DEVICE_OUT_LINE) {
-            connType = AudioRoutesInfo.MAIN_HEADPHONES;
-        } else if (device == AudioSystem.DEVICE_OUT_HDMI ||
-                device == AudioSystem.DEVICE_OUT_HDMI_ARC) {
-            connType = AudioRoutesInfo.MAIN_HDMI;
-        } else if (device == AudioSystem.DEVICE_OUT_USB_DEVICE||
-                device == AudioSystem.DEVICE_OUT_USB_HEADSET) {
-            connType = AudioRoutesInfo.MAIN_USB;
-        }
-
-        synchronized (mCurAudioRoutes) {
-            if (connType != 0) {
-                int newConn = mCurAudioRoutes.mainType;
-                if (state != 0) {
-                    newConn |= connType;
-                } else {
-                    newConn &= ~connType;
-                }
-                if (newConn != mCurAudioRoutes.mainType) {
-                    mCurAudioRoutes.mainType = newConn;
-                    sendMsg(mAudioHandler, MSG_REPORT_NEW_ROUTES,
-                            SENDMSG_NOOP, 0, 0, null, 0);
-                }
-            }
+    /*package*/ void checkMusicActive(int deviceType, String caller) {
+        if ((deviceType & mSafeMediaVolumeDevices) != 0) {
+            sendMsg(mAudioHandler,
+                    MSG_CHECK_MUSIC_ACTIVE,
+                    SENDMSG_REPLACE,
+                    0,
+                    0,
+                    caller,
+                    MUSIC_ACTIVE_POLL_PERIOD_MS);
         }
     }
 
-    private void sendDeviceConnectionIntent(int device, int state, String address,
-            String deviceName) {
-        if (DEBUG_DEVICES) {
-            Slog.i(TAG, "sendDeviceConnectionIntent(dev:0x" + Integer.toHexString(device) +
-                    " state:0x" + Integer.toHexString(state) + " address:" + address +
-                    " name:" + deviceName + ");");
-        }
-        Intent intent = new Intent();
-
-        if (device == AudioSystem.DEVICE_OUT_WIRED_HEADSET) {
-            intent.setAction(Intent.ACTION_HEADSET_PLUG);
-            intent.putExtra("microphone", 1);
-        } else if (device == AudioSystem.DEVICE_OUT_WIRED_HEADPHONE ||
-                   device == AudioSystem.DEVICE_OUT_LINE) {
-            intent.setAction(Intent.ACTION_HEADSET_PLUG);
-            intent.putExtra("microphone",  0);
-        } else if (device == AudioSystem.DEVICE_OUT_USB_HEADSET) {
-            intent.setAction(Intent.ACTION_HEADSET_PLUG);
-            intent.putExtra("microphone",
-                    AudioSystem.getDeviceConnectionState(AudioSystem.DEVICE_IN_USB_HEADSET, "")
-                        == AudioSystem.DEVICE_STATE_AVAILABLE ? 1 : 0);
-        } else if (device == AudioSystem.DEVICE_IN_USB_HEADSET) {
-            if (AudioSystem.getDeviceConnectionState(AudioSystem.DEVICE_OUT_USB_HEADSET, "")
-                    == AudioSystem.DEVICE_STATE_AVAILABLE) {
-                intent.setAction(Intent.ACTION_HEADSET_PLUG);
-                intent.putExtra("microphone", 1);
-            } else {
-                // do not send ACTION_HEADSET_PLUG when only the input side is seen as changing
-                return;
-            }
-        } else if (device == AudioSystem.DEVICE_OUT_HDMI ||
-                device == AudioSystem.DEVICE_OUT_HDMI_ARC) {
-            configureHdmiPlugIntent(intent, state);
-        }
-
-        if (intent.getAction() == null) {
-            return;
-        }
-
-        intent.putExtra(CONNECT_INTENT_KEY_STATE, state);
-        intent.putExtra(CONNECT_INTENT_KEY_ADDRESS, address);
-        intent.putExtra(CONNECT_INTENT_KEY_PORT_NAME, deviceName);
-
-        intent.addFlags(Intent.FLAG_RECEIVER_REGISTERED_ONLY);
-
-        final long ident = Binder.clearCallingIdentity();
-        try {
-            ActivityManager.broadcastStickyIntent(intent, UserHandle.USER_ALL);
-        } finally {
-            Binder.restoreCallingIdentity(ident);
-        }
-    }
-
-    private static final int DEVICE_OVERRIDE_A2DP_ROUTE_ON_PLUG =
-            AudioSystem.DEVICE_OUT_WIRED_HEADSET | AudioSystem.DEVICE_OUT_WIRED_HEADPHONE |
-            AudioSystem.DEVICE_OUT_LINE |
-            AudioSystem.DEVICE_OUT_ALL_USB;
-
-    private void onSetWiredDeviceConnectionState(int device, int state, String address,
-            String deviceName, String caller) {
-        if (DEBUG_DEVICES) {
-            Slog.i(TAG, "onSetWiredDeviceConnectionState(dev:" + Integer.toHexString(device)
-                    + " state:" + Integer.toHexString(state)
-                    + " address:" + address
-                    + " deviceName:" + deviceName
-                    + " caller: " + caller + ");");
-        }
-
-        synchronized (mConnectedDevices) {
-            if ((state == 0) && ((device & DEVICE_OVERRIDE_A2DP_ROUTE_ON_PLUG) != 0)) {
-                setBluetoothA2dpOnInt(true, "onSetWiredDeviceConnectionState state 0");
-            }
-
-            if (!handleDeviceConnection(state == 1, device, address, deviceName)) {
-                // change of connection state failed, bailout
-                return;
-            }
-            if (state != 0) {
-                if ((device & DEVICE_OVERRIDE_A2DP_ROUTE_ON_PLUG) != 0) {
-                    setBluetoothA2dpOnInt(false, "onSetWiredDeviceConnectionState state not 0");
-                }
-                if ((device & mSafeMediaVolumeDevices) != 0) {
-                    sendMsg(mAudioHandler,
-                            MSG_CHECK_MUSIC_ACTIVE,
-                            SENDMSG_REPLACE,
-                            0,
-                            0,
-                            caller,
-                            MUSIC_ACTIVE_POLL_PERIOD_MS);
-                }
-                // Television devices without CEC service apply software volume on HDMI output
-                if (isPlatformTelevision() && ((device & AudioSystem.DEVICE_OUT_HDMI) != 0)) {
-                    mFixedVolumeDevices |= AudioSystem.DEVICE_OUT_HDMI;
-                    checkAllFixedVolumeDevices();
-                    synchronized (mHdmiClientLock) {
-                        if (mHdmiManager != null && mHdmiPlaybackClient != null) {
-                            mHdmiCecSink = false;
-                            mHdmiPlaybackClient.queryDisplayStatus(mHdmiDisplayStatusCallback);
-                        }
-                    }
-                }
-                if ((device & AudioSystem.DEVICE_OUT_HDMI) != 0) {
-                    sendEnabledSurroundFormats(mContentResolver, true);
-                }
-            } else {
-                if (isPlatformTelevision() && ((device & AudioSystem.DEVICE_OUT_HDMI) != 0)) {
-                    synchronized (mHdmiClientLock) {
-                        if (mHdmiManager != null) {
-                            mHdmiCecSink = false;
-                        }
-                    }
-                }
-            }
-            sendDeviceConnectionIntent(device, state, address, deviceName);
-            updateAudioRoutes(device, state);
-        }
-    }
-
-    private void configureHdmiPlugIntent(Intent intent, int state) {
-        intent.setAction(AudioManager.ACTION_HDMI_AUDIO_PLUG);
-        intent.putExtra(AudioManager.EXTRA_AUDIO_PLUG_STATE, state);
-        if (state == 1) {
-            ArrayList<AudioPort> ports = new ArrayList<AudioPort>();
-            int[] portGeneration = new int[1];
-            int status = AudioSystem.listAudioPorts(ports, portGeneration);
-            if (status == AudioManager.SUCCESS) {
-                for (AudioPort port : ports) {
-                    if (port instanceof AudioDevicePort) {
-                        final AudioDevicePort devicePort = (AudioDevicePort) port;
-                        if (devicePort.type() == AudioManager.DEVICE_OUT_HDMI ||
-                                devicePort.type() == AudioManager.DEVICE_OUT_HDMI_ARC) {
-                            // format the list of supported encodings
-                            int[] formats = AudioFormat.filterPublicFormats(devicePort.formats());
-                            if (formats.length > 0) {
-                                ArrayList<Integer> encodingList = new ArrayList(1);
-                                for (int format : formats) {
-                                    // a format in the list can be 0, skip it
-                                    if (format != AudioFormat.ENCODING_INVALID) {
-                                        encodingList.add(format);
-                                    }
-                                }
-                                int[] encodingArray = new int[encodingList.size()];
-                                for (int i = 0 ; i < encodingArray.length ; i++) {
-                                    encodingArray[i] = encodingList.get(i);
-                                }
-                                intent.putExtra(AudioManager.EXTRA_ENCODINGS, encodingArray);
-                            }
-                            // find the maximum supported number of channels
-                            int maxChannels = 0;
-                            for (int mask : devicePort.channelMasks()) {
-                                int channelCount = AudioFormat.channelCountFromOutChannelMask(mask);
-                                if (channelCount > maxChannels) {
-                                    maxChannels = channelCount;
-                                }
-                            }
-                            intent.putExtra(AudioManager.EXTRA_MAX_CHANNEL_COUNT, maxChannels);
-                        }
-                    }
-                }
-            }
-        }
-    }
-
-    /* cache of the address of the last dock the device was connected to */
-    private String mDockAddress;
-
     /**
      * Receiver for misc intent broadcasts the Phone app cares about.
      */
     private class AudioServiceBroadcastReceiver extends BroadcastReceiver {
         @Override
         public void onReceive(Context context, Intent intent) {
-            String action = intent.getAction();
+            final String action = intent.getAction();
             int outDevice;
             int inDevice;
             int state;
@@ -6812,75 +5074,16 @@
                 }
                 // Low end docks have a menu to enable or disable audio
                 // (see mDockAudioMediaEnabled)
-                if (!((dockState == Intent.EXTRA_DOCK_STATE_LE_DESK) ||
-                      ((dockState == Intent.EXTRA_DOCK_STATE_UNDOCKED) &&
-                       (mDockState == Intent.EXTRA_DOCK_STATE_LE_DESK)))) {
-                    mForceUseLogger.log(new ForceUseEvent(AudioSystem.FOR_DOCK, config,
-                            "ACTION_DOCK_EVENT intent"));
-                    AudioSystem.setForceUse(AudioSystem.FOR_DOCK, config);
+                if (!((dockState == Intent.EXTRA_DOCK_STATE_LE_DESK)
+                        || ((dockState == Intent.EXTRA_DOCK_STATE_UNDOCKED)
+                                && (mDockState == Intent.EXTRA_DOCK_STATE_LE_DESK)))) {
+                    mDeviceBroker.setForceUse_Async(AudioSystem.FOR_DOCK, config,
+                            "ACTION_DOCK_EVENT intent");
                 }
                 mDockState = dockState;
-            } else if (action.equals(BluetoothHeadset.ACTION_ACTIVE_DEVICE_CHANGED)) {
-                BluetoothDevice btDevice = intent.getParcelableExtra(BluetoothDevice.EXTRA_DEVICE);
-                setBtScoActiveDevice(btDevice);
-            } else if (action.equals(BluetoothHeadset.ACTION_AUDIO_STATE_CHANGED)) {
-                boolean broadcast = false;
-                int scoAudioState = AudioManager.SCO_AUDIO_STATE_ERROR;
-                synchronized (mScoClients) {
-                    int btState = intent.getIntExtra(BluetoothProfile.EXTRA_STATE, -1);
-                    // broadcast intent if the connection was initated by AudioService
-                    if (!mScoClients.isEmpty() &&
-                            (mScoAudioState == SCO_STATE_ACTIVE_INTERNAL ||
-                             mScoAudioState == SCO_STATE_ACTIVATE_REQ ||
-                             mScoAudioState == SCO_STATE_DEACTIVATE_REQ ||
-                             mScoAudioState == SCO_STATE_DEACTIVATING)) {
-                        broadcast = true;
-                    }
-                    switch (btState) {
-                        case BluetoothHeadset.STATE_AUDIO_CONNECTED:
-                            scoAudioState = AudioManager.SCO_AUDIO_STATE_CONNECTED;
-                            if (mScoAudioState != SCO_STATE_ACTIVE_INTERNAL &&
-                                mScoAudioState != SCO_STATE_DEACTIVATE_REQ) {
-                                mScoAudioState = SCO_STATE_ACTIVE_EXTERNAL;
-                            }
-                            setBluetoothScoOn(true);
-                            break;
-                        case BluetoothHeadset.STATE_AUDIO_DISCONNECTED:
-                            setBluetoothScoOn(false);
-                            scoAudioState = AudioManager.SCO_AUDIO_STATE_DISCONNECTED;
-                            // startBluetoothSco called after stopBluetoothSco
-                            if (mScoAudioState == SCO_STATE_ACTIVATE_REQ) {
-                                if (mBluetoothHeadset != null && mBluetoothHeadsetDevice != null
-                                        && connectBluetoothScoAudioHelper(mBluetoothHeadset,
-                                        mBluetoothHeadsetDevice, mScoAudioMode)) {
-                                    mScoAudioState = SCO_STATE_ACTIVE_INTERNAL;
-                                    broadcast = false;
-                                    break;
-                                }
-                            }
-                            // Tear down SCO if disconnected from external
-                            clearAllScoClients(0, mScoAudioState == SCO_STATE_ACTIVE_INTERNAL);
-                            mScoAudioState = SCO_STATE_INACTIVE;
-                            break;
-                        case BluetoothHeadset.STATE_AUDIO_CONNECTING:
-                            if (mScoAudioState != SCO_STATE_ACTIVE_INTERNAL &&
-                                mScoAudioState != SCO_STATE_DEACTIVATE_REQ) {
-                                mScoAudioState = SCO_STATE_ACTIVE_EXTERNAL;
-                            }
-                        default:
-                            // do not broadcast CONNECTING or invalid state
-                            broadcast = false;
-                            break;
-                    }
-                }
-                if (broadcast) {
-                    broadcastScoConnectionState(scoAudioState);
-                    //FIXME: this is to maintain compatibility with deprecated intent
-                    // AudioManager.ACTION_SCO_AUDIO_STATE_CHANGED. Remove when appropriate.
-                    Intent newIntent = new Intent(AudioManager.ACTION_SCO_AUDIO_STATE_CHANGED);
-                    newIntent.putExtra(AudioManager.EXTRA_SCO_AUDIO_STATE, scoAudioState);
-                    sendStickyBroadcastToAll(newIntent);
-                }
+            } else if (action.equals(BluetoothHeadset.ACTION_ACTIVE_DEVICE_CHANGED)
+                    || action.equals(BluetoothHeadset.ACTION_AUDIO_STATE_CHANGED)) {
+                mDeviceBroker.receiveBtEvent(intent);
             } else if (action.equals(Intent.ACTION_SCREEN_ON)) {
                 if (mMonitorRotation) {
                     RotationHelper.enable();
@@ -6898,13 +5101,7 @@
                 if (mUserSwitchedReceived) {
                     // attempt to stop music playback for background user except on first user
                     // switch (i.e. first boot)
-                    sendMsg(mAudioHandler,
-                            MSG_BROADCAST_AUDIO_BECOMING_NOISY,
-                            SENDMSG_REPLACE,
-                            0,
-                            0,
-                            null,
-                            0);
+                    mDeviceBroker.broadcastBecomingNoisy();
                 }
                 mUserSwitchedReceived = true;
                 // the current audio focus owner is no longer valid
@@ -6938,7 +5135,7 @@
                 state = intent.getIntExtra(BluetoothAdapter.EXTRA_STATE, -1);
                 if (state == BluetoothAdapter.STATE_OFF ||
                         state == BluetoothAdapter.STATE_TURNING_OFF) {
-                    disconnectAllBluetoothProfiles();
+                    mDeviceBroker.disconnectAllBluetoothProfiles();
                 }
             } else if (action.equals(AudioEffect.ACTION_OPEN_AUDIO_EFFECT_CONTROL_SESSION) ||
                     action.equals(AudioEffect.ACTION_CLOSE_AUDIO_EFFECT_CONTROL_SESSION)) {
@@ -7178,22 +5375,17 @@
                         // take new state into account for streams muted by ringer mode
                         setRingerModeInt(getRingerModeInternal(), false);
                     }
-
-                    sendMsg(mAudioHandler,
-                            MSG_SET_FORCE_USE,
-                            SENDMSG_QUEUE,
-                            AudioSystem.FOR_SYSTEM,
+                    mDeviceBroker.setForceUse_Async(AudioSystem.FOR_SYSTEM,
                             cameraSoundForced ?
                                     AudioSystem.FORCE_SYSTEM_ENFORCED : AudioSystem.FORCE_NONE,
-                            new String("handleConfigurationChanged"),
-                            0);
-
+                            "handleConfigurationChanged");
                     sendMsg(mAudioHandler,
                             MSG_SET_ALL_VOLUMES,
                             SENDMSG_QUEUE,
                             0,
                             0,
                             mStreamStates[AudioSystem.STREAM_SYSTEM_ENFORCED], 0);
+
                 }
             }
             mVolumeController.setLayoutDirection(config.getLayoutDirection());
@@ -7202,28 +5394,6 @@
         }
     }
 
-    // Handles request to override default use of A2DP for media.
-    // Must be called synchronized on mConnectedDevices
-    public void setBluetoothA2dpOnInt(boolean on, String eventSource) {
-        synchronized (mBluetoothA2dpEnabledLock) {
-            mBluetoothA2dpEnabled = on;
-            mAudioHandler.removeMessages(MSG_SET_FORCE_BT_A2DP_USE);
-            setForceUseInt_SyncDevices(AudioSystem.FOR_MEDIA,
-                    mBluetoothA2dpEnabled ? AudioSystem.FORCE_NONE : AudioSystem.FORCE_NO_BT_A2DP,
-                            eventSource);
-        }
-    }
-
-    // Must be called synchronized on mConnectedDevices
-    private void setForceUseInt_SyncDevices(int usage, int config, String eventSource) {
-        if (usage == AudioSystem.FOR_MEDIA) {
-            sendMsg(mAudioHandler, MSG_REPORT_NEW_ROUTES,
-                    SENDMSG_NOOP, 0, 0, null, 0);
-        }
-        mForceUseLogger.log(new ForceUseEvent(usage, config, eventSource));
-        AudioSystem.setForceUse(usage, config);
-    }
-
     @Override
     public void setRingtonePlayer(IRingtonePlayer player) {
         mContext.enforceCallingOrSelfPermission(REMOTE_AUDIO_PLAYBACK, null);
@@ -7237,11 +5407,7 @@
 
     @Override
     public AudioRoutesInfo startWatchingRoutes(IAudioRoutesObserver observer) {
-        synchronized (mCurAudioRoutes) {
-            AudioRoutesInfo routes = new AudioRoutesInfo(mCurAudioRoutes);
-            mRoutesObservers.register(observer);
-            return routes;
-        }
+        return mDeviceBroker.startWatchingRoutes(observer);
     }
 
 
@@ -7283,9 +5449,9 @@
     // the headset is compliant to EN 60950 with a max loudness of 100dB SPL.
     private int mSafeUsbMediaVolumeIndex;
     // mSafeMediaVolumeDevices lists the devices for which safe media volume is enforced,
-    private final int mSafeMediaVolumeDevices = AudioSystem.DEVICE_OUT_WIRED_HEADSET |
-                                                AudioSystem.DEVICE_OUT_WIRED_HEADPHONE |
-                                                AudioSystem.DEVICE_OUT_USB_HEADSET;
+    /*package*/ final int mSafeMediaVolumeDevices = AudioSystem.DEVICE_OUT_WIRED_HEADSET
+            | AudioSystem.DEVICE_OUT_WIRED_HEADPHONE
+            | AudioSystem.DEVICE_OUT_USB_HEADSET;
     // mMusicActiveMs is the cumulative time of music activity since safe volume was disabled.
     // When this time reaches UNSAFE_VOLUME_MUSIC_ACTIVE_MS_MAX, the safe media volume is re-enabled
     // automatically. mMusicActiveMs is rounded to a multiple of MUSIC_ACTIVE_POLL_PERIOD_MS.
@@ -7438,9 +5604,8 @@
                     mHdmiSystemAudioSupported = on;
                     final int config = on ? AudioSystem.FORCE_HDMI_SYSTEM_AUDIO_ENFORCED :
                         AudioSystem.FORCE_NONE;
-                    mForceUseLogger.log(new ForceUseEvent(AudioSystem.FOR_HDMI_SYSTEM_AUDIO,
-                            config, "setHdmiSystemAudioSupported"));
-                    AudioSystem.setForceUse(AudioSystem.FOR_HDMI_SYSTEM_AUDIO, config);
+                    mDeviceBroker.setForceUse_Async(AudioSystem.FOR_HDMI_SYSTEM_AUDIO, config,
+                            "setHdmiSystemAudioSupported");
                 }
                 device = getDevicesForStream(AudioSystem.STREAM_MUSIC);
             }
@@ -7553,14 +5718,14 @@
     // logs for wired + A2DP device connections:
     // - wired: logged before onSetWiredDeviceConnectionState() is executed
     // - A2DP: logged at reception of method call
-    final private AudioEventLogger mDeviceLogger = new AudioEventLogger(
-            LOG_NB_EVENTS_DEVICE_CONNECTION, "wired/A2DP/hearing aid device connection");
+    /*package*/ static final AudioEventLogger sDeviceLogger = new AudioEventLogger(
+            LOG_NB_EVENTS_DEVICE_CONNECTION, "wired/A2DP/hearing aid device connection BLABLI");
 
-    final private AudioEventLogger mForceUseLogger = new AudioEventLogger(
+    static final AudioEventLogger sForceUseLogger = new AudioEventLogger(
             LOG_NB_EVENTS_FORCE_USE,
             "force use (logged before setForceUse() is executed)");
 
-    final private AudioEventLogger mVolumeLogger = new AudioEventLogger(LOG_NB_EVENTS_VOLUME,
+    static final AudioEventLogger sVolumeLogger = new AudioEventLogger(LOG_NB_EVENTS_VOLUME,
             "volume changes (logged when command received by AudioService)");
 
     final private AudioEventLogger mDynPolicyLogger = new AudioEventLogger(LOG_NB_EVENTS_DYN_POLICY,
@@ -7613,8 +5778,9 @@
         dumpStreamStates(pw);
         dumpRingerMode(pw);
         pw.println("\nAudio routes:");
-        pw.print("  mMainType=0x"); pw.println(Integer.toHexString(mCurAudioRoutes.mainType));
-        pw.print("  mBluetoothName="); pw.println(mCurAudioRoutes.bluetoothName);
+        pw.print("  mMainType=0x"); pw.println(Integer.toHexString(
+                mDeviceBroker.getCurAudioRoutes().mainType));
+        pw.print("  mBluetoothName="); pw.println(mDeviceBroker.getCurAudioRoutes().bluetoothName);
 
         pw.println("\nOther state:");
         pw.print("  mVolumeController="); pw.println(mVolumeController);
@@ -7630,7 +5796,8 @@
         pw.print("  mCameraSoundForced="); pw.println(mCameraSoundForced);
         pw.print("  mHasVibrator="); pw.println(mHasVibrator);
         pw.print("  mVolumePolicy="); pw.println(mVolumePolicy);
-        pw.print("  mAvrcpAbsVolSupported="); pw.println(mAvrcpAbsVolSupported);
+        pw.print("  mAvrcpAbsVolSupported=");
+        pw.println(mDeviceBroker.isAvrcpAbsoluteVolumeSupported());
 
         dumpAudioPolicies(pw);
         mDynPolicyLogger.dump(pw);
@@ -7643,11 +5810,11 @@
         pw.println("\nEvent logs:");
         mModeLogger.dump(pw);
         pw.println("\n");
-        mDeviceLogger.dump(pw);
+        sDeviceLogger.dump(pw);
         pw.println("\n");
-        mForceUseLogger.dump(pw);
+        sForceUseLogger.dump(pw);
         pw.println("\n");
-        mVolumeLogger.dump(pw);
+        sVolumeLogger.dump(pw);
     }
 
     private static String safeMediaVolumeStateToString(int state) {
@@ -8270,6 +6437,11 @@
     }
 
     //======================
+    // Audio device management
+    //======================
+    private final AudioDeviceBroker mDeviceBroker;
+
+    //======================
     // Audio policy proxy
     //======================
     private static final class AudioDeviceArray {
diff --git a/services/core/java/com/android/server/audio/AudioServiceEvents.java b/services/core/java/com/android/server/audio/AudioServiceEvents.java
index 9d9e35b..7ccb45e 100644
--- a/services/core/java/com/android/server/audio/AudioServiceEvents.java
+++ b/services/core/java/com/android/server/audio/AudioServiceEvents.java
@@ -19,7 +19,7 @@
 import android.media.AudioManager;
 import android.media.AudioSystem;
 
-import com.android.server.audio.AudioService.WiredDeviceConnectionState;
+import com.android.server.audio.AudioDeviceInventory.WiredDeviceConnectionState;
 
 
 public class AudioServiceEvents {
@@ -89,9 +89,11 @@
     }
 
     final static class VolumeEvent extends AudioEventLogger.Event {
-        final static int VOL_ADJUST_SUGG_VOL = 0;
-        final static int VOL_ADJUST_STREAM_VOL = 1;
-        final static int VOL_SET_STREAM_VOL = 2;
+        static final int VOL_ADJUST_SUGG_VOL = 0;
+        static final int VOL_ADJUST_STREAM_VOL = 1;
+        static final int VOL_SET_STREAM_VOL = 2;
+        static final int VOL_SET_HEARING_AID_VOL = 3;
+        static final int VOL_SET_AVRCP_VOL = 4;
 
         final int mOp;
         final int mStream;
@@ -107,6 +109,24 @@
             mCaller = caller;
         }
 
+        VolumeEvent(int op, int index, int gainDb) {
+            mOp = op;
+            mVal1 = index;
+            mVal2 = gainDb;
+            //unused
+            mStream = -1;
+            mCaller = null;
+        }
+
+        VolumeEvent(int op, int index) {
+            mOp = op;
+            mVal1 = index;
+            //unused
+            mVal2 = 0;
+            mStream = -1;
+            mCaller = null;
+        }
+
         @Override
         public String eventToString() {
             switch (mOp) {
@@ -131,6 +151,15 @@
                             .append(" flags:0x").append(Integer.toHexString(mVal2))
                             .append(") from ").append(mCaller)
                             .toString();
+                case VOL_SET_HEARING_AID_VOL:
+                    return new StringBuilder("setHearingAidVolume:")
+                            .append(" index:").append(mVal1)
+                            .append(" gain dB:").append(mVal2)
+                            .toString();
+                case VOL_SET_AVRCP_VOL:
+                    return new StringBuilder("setAvrcpVolume:")
+                            .append(" index:").append(mVal1)
+                            .toString();
                default: return new StringBuilder("FIXME invalid op:").append(mOp).toString();
             }
         }
diff --git a/services/core/java/com/android/server/audio/BtHelper.java b/services/core/java/com/android/server/audio/BtHelper.java
new file mode 100644
index 0000000..bf32501
--- /dev/null
+++ b/services/core/java/com/android/server/audio/BtHelper.java
@@ -0,0 +1,949 @@
+/*
+ * Copyright 2019 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.android.server.audio;
+
+import android.annotation.NonNull;
+import android.bluetooth.BluetoothA2dp;
+import android.bluetooth.BluetoothAdapter;
+import android.bluetooth.BluetoothClass;
+import android.bluetooth.BluetoothCodecConfig;
+import android.bluetooth.BluetoothCodecStatus;
+import android.bluetooth.BluetoothDevice;
+import android.bluetooth.BluetoothHeadset;
+import android.bluetooth.BluetoothHearingAid;
+import android.bluetooth.BluetoothProfile;
+import android.content.Intent;
+import android.media.AudioManager;
+import android.media.AudioSystem;
+import android.os.Binder;
+import android.os.IBinder;
+import android.os.RemoteException;
+import android.os.UserHandle;
+import android.provider.Settings;
+import android.util.Log;
+
+import com.android.internal.annotations.GuardedBy;
+
+import java.util.ArrayList;
+import java.util.List;
+import java.util.NoSuchElementException;
+import java.util.Objects;
+
+/**
+ * @hide
+ * Class to encapsulate all communication with Bluetooth services
+ */
+public class BtHelper {
+
+    private static final String TAG = "AS.BtHelper";
+
+    private final @NonNull AudioDeviceBroker mDeviceBroker;
+
+    BtHelper(@NonNull AudioDeviceBroker broker) {
+        mDeviceBroker = broker;
+    }
+
+    // List of clients having issued a SCO start request
+    private final ArrayList<ScoClient> mScoClients = new ArrayList<ScoClient>();
+
+    // BluetoothHeadset API to control SCO connection
+    private BluetoothHeadset mBluetoothHeadset;
+
+    // Bluetooth headset device
+    private BluetoothDevice mBluetoothHeadsetDevice;
+
+    // Indicate if SCO audio connection is currently active and if the initiator is
+    // audio service (internal) or bluetooth headset (external)
+    private int mScoAudioState;
+    // SCO audio state is not active
+    private static final int SCO_STATE_INACTIVE = 0;
+    // SCO audio activation request waiting for headset service to connect
+    private static final int SCO_STATE_ACTIVATE_REQ = 1;
+    // SCO audio state is active or starting due to a request from AudioManager API
+    private static final int SCO_STATE_ACTIVE_INTERNAL = 3;
+    // SCO audio deactivation request waiting for headset service to connect
+    private static final int SCO_STATE_DEACTIVATE_REQ = 4;
+    // SCO audio deactivation in progress, waiting for Bluetooth audio intent
+    private static final int SCO_STATE_DEACTIVATING = 5;
+
+    // SCO audio state is active due to an action in BT handsfree (either voice recognition or
+    // in call audio)
+    private static final int SCO_STATE_ACTIVE_EXTERNAL = 2;
+
+    // Indicates the mode used for SCO audio connection. The mode is virtual call if the request
+    // originated from an app targeting an API version before JB MR2 and raw audio after that.
+    private int mScoAudioMode;
+    // SCO audio mode is undefined
+    /*package*/   static final int SCO_MODE_UNDEFINED = -1;
+    // SCO audio mode is virtual voice call (BluetoothHeadset.startScoUsingVirtualVoiceCall())
+    /*package*/  static final int SCO_MODE_VIRTUAL_CALL = 0;
+    // SCO audio mode is raw audio (BluetoothHeadset.connectAudio())
+    private  static final int SCO_MODE_RAW = 1;
+    // SCO audio mode is Voice Recognition (BluetoothHeadset.startVoiceRecognition())
+    private  static final int SCO_MODE_VR = 2;
+
+    private static final int SCO_MODE_MAX = 2;
+
+    // Current connection state indicated by bluetooth headset
+    private int mScoConnectionState;
+
+    private static final int BT_HEARING_AID_GAIN_MIN = -128;
+
+    @GuardedBy("mDeviceBroker.mHearingAidLock")
+    private BluetoothHearingAid mHearingAid;
+
+    // Reference to BluetoothA2dp to query for AbsoluteVolume.
+    @GuardedBy("mDeviceBroker.mA2dpAvrcpLock")
+    private BluetoothA2dp mA2dp;
+    // If absolute volume is supported in AVRCP device
+    @GuardedBy("mDeviceBroker.mA2dpAvrcpLock")
+    private boolean mAvrcpAbsVolSupported = false;
+
+    //----------------------------------------------------------------------
+    /*package*/ static class BluetoothA2dpDeviceInfo {
+        private final @NonNull BluetoothDevice mBtDevice;
+        private final int mVolume;
+        private final int mCodec;
+
+        BluetoothA2dpDeviceInfo(@NonNull BluetoothDevice btDevice) {
+            this(btDevice, -1, AudioSystem.AUDIO_FORMAT_DEFAULT);
+        }
+
+        BluetoothA2dpDeviceInfo(@NonNull BluetoothDevice btDevice, int volume, int codec) {
+            mBtDevice = btDevice;
+            mVolume = volume;
+            mCodec = codec;
+        }
+
+        public @NonNull BluetoothDevice getBtDevice() {
+            return mBtDevice;
+        }
+
+        public int getVolume() {
+            return mVolume;
+        }
+
+        public int getCodec() {
+            return mCodec;
+        }
+    }
+
+    //----------------------------------------------------------------------
+    // Interface for AudioDeviceBroker
+
+    /*package*/ void onSystemReady() {
+        mScoConnectionState = android.media.AudioManager.SCO_AUDIO_STATE_ERROR;
+        resetBluetoothSco();
+        getBluetoothHeadset();
+
+        //FIXME: this is to maintain compatibility with deprecated intent
+        // AudioManager.ACTION_SCO_AUDIO_STATE_CHANGED. Remove when appropriate.
+        Intent newIntent = new Intent(AudioManager.ACTION_SCO_AUDIO_STATE_CHANGED);
+        newIntent.putExtra(AudioManager.EXTRA_SCO_AUDIO_STATE,
+                AudioManager.SCO_AUDIO_STATE_DISCONNECTED);
+        sendStickyBroadcastToAll(newIntent);
+
+        BluetoothAdapter adapter = BluetoothAdapter.getDefaultAdapter();
+        if (adapter != null) {
+            adapter.getProfileProxy(mDeviceBroker.getContext(),
+                    mBluetoothProfileServiceListener, BluetoothProfile.A2DP);
+            adapter.getProfileProxy(mDeviceBroker.getContext(),
+                    mBluetoothProfileServiceListener, BluetoothProfile.HEARING_AID);
+        }
+    }
+
+    @GuardedBy("mBluetoothA2dpEnabledLock")
+    /*package*/ void onAudioServerDiedRestoreA2dp() {
+        final int forMed = mDeviceBroker.getBluetoothA2dpEnabled()
+                ? AudioSystem.FORCE_NONE : AudioSystem.FORCE_NO_BT_A2DP;
+        mDeviceBroker.setForceUse_Async(AudioSystem.FOR_MEDIA, forMed, "onAudioServerDied()");
+    }
+
+    @GuardedBy("mA2dpAvrcpLock")
+    /*package*/ boolean isAvrcpAbsoluteVolumeSupported() {
+        return (mA2dp != null && mAvrcpAbsVolSupported);
+    }
+
+    @GuardedBy("mA2dpAvrcpLock")
+    /*package*/ void setAvrcpAbsoluteVolumeSupported(boolean supported) {
+        mAvrcpAbsVolSupported = supported;
+    }
+
+    /*package*/ void setAvrcpAbsoluteVolumeIndex(int index) {
+        synchronized (mDeviceBroker.mA2dpAvrcpLock) {
+            if (mA2dp == null) {
+                if (AudioService.DEBUG_VOL) {
+                    Log.d(TAG, "setAvrcpAbsoluteVolumeIndex: bailing due to null mA2dp");
+                    return;
+                }
+            }
+            if (!mAvrcpAbsVolSupported) {
+                return;
+            }
+            if (AudioService.DEBUG_VOL) {
+                Log.i(TAG, "setAvrcpAbsoluteVolumeIndex index=" + index);
+            }
+            AudioService.sVolumeLogger.log(new AudioServiceEvents.VolumeEvent(
+                    AudioServiceEvents.VolumeEvent.VOL_SET_AVRCP_VOL, index / 10));
+            mA2dp.setAvrcpAbsoluteVolume(index / 10);
+        }
+    }
+
+    @GuardedBy("mA2dpAvrcpLock")
+    /*package*/ int getA2dpCodec(@NonNull BluetoothDevice device) {
+        if (mA2dp == null) {
+            return AudioSystem.AUDIO_FORMAT_DEFAULT;
+        }
+        final BluetoothCodecStatus btCodecStatus = mA2dp.getCodecStatus(device);
+        if (btCodecStatus == null) {
+            return AudioSystem.AUDIO_FORMAT_DEFAULT;
+        }
+        final BluetoothCodecConfig btCodecConfig = btCodecStatus.getCodecConfig();
+        if (btCodecConfig == null) {
+            return AudioSystem.AUDIO_FORMAT_DEFAULT;
+        }
+        return mapBluetoothCodecToAudioFormat(btCodecConfig.getCodecType());
+    }
+
+    /*package*/ void receiveBtEvent(Intent intent) {
+        final String action = intent.getAction();
+        if (action.equals(BluetoothHeadset.ACTION_ACTIVE_DEVICE_CHANGED)) {
+            BluetoothDevice btDevice = intent.getParcelableExtra(BluetoothDevice.EXTRA_DEVICE);
+            setBtScoActiveDevice(btDevice);
+        } else if (action.equals(BluetoothHeadset.ACTION_AUDIO_STATE_CHANGED)) {
+            boolean broadcast = false;
+            int scoAudioState = AudioManager.SCO_AUDIO_STATE_ERROR;
+            synchronized (mScoClients) {
+                int btState = intent.getIntExtra(BluetoothProfile.EXTRA_STATE, -1);
+                // broadcast intent if the connection was initated by AudioService
+                if (!mScoClients.isEmpty()
+                        && (mScoAudioState == SCO_STATE_ACTIVE_INTERNAL
+                                || mScoAudioState == SCO_STATE_ACTIVATE_REQ
+                                || mScoAudioState == SCO_STATE_DEACTIVATE_REQ
+                                || mScoAudioState == SCO_STATE_DEACTIVATING)) {
+                    broadcast = true;
+                }
+                switch (btState) {
+                    case BluetoothHeadset.STATE_AUDIO_CONNECTED:
+                        scoAudioState = AudioManager.SCO_AUDIO_STATE_CONNECTED;
+                        if (mScoAudioState != SCO_STATE_ACTIVE_INTERNAL
+                                && mScoAudioState != SCO_STATE_DEACTIVATE_REQ) {
+                            mScoAudioState = SCO_STATE_ACTIVE_EXTERNAL;
+                        }
+                        mDeviceBroker.setBluetoothScoOn(true, "BtHelper.receiveBtEvent");
+                        break;
+                    case BluetoothHeadset.STATE_AUDIO_DISCONNECTED:
+                        mDeviceBroker.setBluetoothScoOn(false, "BtHelper.receiveBtEvent");
+                        scoAudioState = AudioManager.SCO_AUDIO_STATE_DISCONNECTED;
+                        // startBluetoothSco called after stopBluetoothSco
+                        if (mScoAudioState == SCO_STATE_ACTIVATE_REQ) {
+                            if (mBluetoothHeadset != null && mBluetoothHeadsetDevice != null
+                                    && connectBluetoothScoAudioHelper(mBluetoothHeadset,
+                                            mBluetoothHeadsetDevice, mScoAudioMode)) {
+                                mScoAudioState = SCO_STATE_ACTIVE_INTERNAL;
+                                broadcast = false;
+                                break;
+                            }
+                        }
+                        // Tear down SCO if disconnected from external
+                        clearAllScoClients(0, mScoAudioState == SCO_STATE_ACTIVE_INTERNAL);
+                        mScoAudioState = SCO_STATE_INACTIVE;
+                        break;
+                    case BluetoothHeadset.STATE_AUDIO_CONNECTING:
+                        if (mScoAudioState != SCO_STATE_ACTIVE_INTERNAL
+                                && mScoAudioState != SCO_STATE_DEACTIVATE_REQ) {
+                            mScoAudioState = SCO_STATE_ACTIVE_EXTERNAL;
+                        }
+                        break;
+                    default:
+                        // do not broadcast CONNECTING or invalid state
+                        broadcast = false;
+                        break;
+                }
+            }
+            if (broadcast) {
+                broadcastScoConnectionState(scoAudioState);
+                //FIXME: this is to maintain compatibility with deprecated intent
+                // AudioManager.ACTION_SCO_AUDIO_STATE_CHANGED. Remove when appropriate.
+                Intent newIntent = new Intent(AudioManager.ACTION_SCO_AUDIO_STATE_CHANGED);
+                newIntent.putExtra(AudioManager.EXTRA_SCO_AUDIO_STATE, scoAudioState);
+                sendStickyBroadcastToAll(newIntent);
+            }
+        }
+    }
+
+    /**
+     *
+     * @return false if SCO isn't connected
+     */
+    /*package*/ boolean isBluetoothScoOn() {
+        synchronized (mScoClients) {
+            if ((mBluetoothHeadset != null)
+                    && (mBluetoothHeadset.getAudioState(mBluetoothHeadsetDevice)
+                            != BluetoothHeadset.STATE_AUDIO_CONNECTED)) {
+                Log.w(TAG, "isBluetoothScoOn(true) returning false because "
+                        + mBluetoothHeadsetDevice + " is not in audio connected mode");
+                return false;
+            }
+        }
+        return true;
+    }
+
+    /**
+     * Disconnect all SCO connections started by {@link AudioManager} except those started by
+     * {@param exceptPid}
+     *
+     * @param exceptPid pid whose SCO connections through {@link AudioManager} should be kept
+     */
+    /*package*/ void disconnectBluetoothSco(int exceptPid) {
+        synchronized (mScoClients) {
+            checkScoAudioState();
+            if (mScoAudioState == SCO_STATE_ACTIVE_EXTERNAL) {
+                return;
+            }
+            clearAllScoClients(exceptPid, true);
+        }
+    }
+
+    /*package*/ void startBluetoothScoForClient(IBinder cb, int scoAudioMode,
+                @NonNull String eventSource) {
+        ScoClient client = getScoClient(cb, true);
+        // The calling identity must be cleared before calling ScoClient.incCount().
+        // inCount() calls requestScoState() which in turn can call BluetoothHeadset APIs
+        // and this must be done on behalf of system server to make sure permissions are granted.
+        // The caller identity must be cleared after getScoClient() because it is needed if a new
+        // client is created.
+        final long ident = Binder.clearCallingIdentity();
+        try {
+            eventSource += " client count before=" + client.getCount();
+            AudioService.sDeviceLogger.log(new AudioEventLogger.StringEvent(eventSource));
+            client.incCount(scoAudioMode);
+        } catch (NullPointerException e) {
+            Log.e(TAG, "Null ScoClient", e);
+        }
+        Binder.restoreCallingIdentity(ident);
+    }
+
+    /*package*/ void stopBluetoothScoForClient(IBinder cb, @NonNull String eventSource) {
+        ScoClient client = getScoClient(cb, false);
+        // The calling identity must be cleared before calling ScoClient.decCount().
+        // decCount() calls requestScoState() which in turn can call BluetoothHeadset APIs
+        // and this must be done on behalf of system server to make sure permissions are granted.
+        final long ident = Binder.clearCallingIdentity();
+        if (client != null) {
+            eventSource += " client count before=" + client.getCount();
+            AudioService.sDeviceLogger.log(new AudioEventLogger.StringEvent(eventSource));
+            client.decCount();
+        }
+        Binder.restoreCallingIdentity(ident);
+    }
+
+
+    /*package*/ void setHearingAidVolume(int index, int streamType) {
+        synchronized (mDeviceBroker.mHearingAidLock) {
+            if (mHearingAid == null) {
+                if (AudioService.DEBUG_VOL) {
+                    Log.i(TAG, "setHearingAidVolume: null mHearingAid");
+                }
+                return;
+            }
+            //hearing aid expect volume value in range -128dB to 0dB
+            int gainDB = (int) AudioSystem.getStreamVolumeDB(streamType, index / 10,
+                    AudioSystem.DEVICE_OUT_HEARING_AID);
+            if (gainDB < BT_HEARING_AID_GAIN_MIN) {
+                gainDB = BT_HEARING_AID_GAIN_MIN;
+            }
+            if (AudioService.DEBUG_VOL) {
+                Log.i(TAG, "setHearingAidVolume: calling mHearingAid.setVolume idx="
+                        + index + " gain=" + gainDB);
+            }
+            AudioService.sVolumeLogger.log(new AudioServiceEvents.VolumeEvent(
+                    AudioServiceEvents.VolumeEvent.VOL_SET_HEARING_AID_VOL, index, gainDB));
+            mHearingAid.setVolume(gainDB);
+        }
+    }
+
+    //----------------------------------------------------------------------
+    private void broadcastScoConnectionState(int state) {
+        mDeviceBroker.broadcastScoConnectionState(state);
+    }
+
+    /*package*/ void onBroadcastScoConnectionState(int state) {
+        if (state == mScoConnectionState) {
+            return;
+        }
+        Intent newIntent = new Intent(AudioManager.ACTION_SCO_AUDIO_STATE_UPDATED);
+        newIntent.putExtra(AudioManager.EXTRA_SCO_AUDIO_STATE, state);
+        newIntent.putExtra(AudioManager.EXTRA_SCO_AUDIO_PREVIOUS_STATE,
+                mScoConnectionState);
+        sendStickyBroadcastToAll(newIntent);
+        mScoConnectionState = state;
+    }
+
+    private boolean handleBtScoActiveDeviceChange(BluetoothDevice btDevice, boolean isActive) {
+        if (btDevice == null) {
+            return true;
+        }
+        String address = btDevice.getAddress();
+        BluetoothClass btClass = btDevice.getBluetoothClass();
+        int inDevice = AudioSystem.DEVICE_IN_BLUETOOTH_SCO_HEADSET;
+        int[] outDeviceTypes = {
+                AudioSystem.DEVICE_OUT_BLUETOOTH_SCO,
+                AudioSystem.DEVICE_OUT_BLUETOOTH_SCO_HEADSET,
+                AudioSystem.DEVICE_OUT_BLUETOOTH_SCO_CARKIT
+        };
+        if (btClass != null) {
+            switch (btClass.getDeviceClass()) {
+                case BluetoothClass.Device.AUDIO_VIDEO_WEARABLE_HEADSET:
+                case BluetoothClass.Device.AUDIO_VIDEO_HANDSFREE:
+                    outDeviceTypes = new int[] { AudioSystem.DEVICE_OUT_BLUETOOTH_SCO_HEADSET };
+                    break;
+                case BluetoothClass.Device.AUDIO_VIDEO_CAR_AUDIO:
+                    outDeviceTypes = new int[] { AudioSystem.DEVICE_OUT_BLUETOOTH_SCO_CARKIT };
+                    break;
+            }
+        }
+        if (!BluetoothAdapter.checkBluetoothAddress(address)) {
+            address = "";
+        }
+        String btDeviceName =  btDevice.getName();
+        boolean result = false;
+        if (isActive) {
+            result |= mDeviceBroker.handleDeviceConnection(
+                    isActive, outDeviceTypes[0], address, btDeviceName);
+        } else {
+            for (int outDeviceType : outDeviceTypes) {
+                result |= mDeviceBroker.handleDeviceConnection(
+                        isActive, outDeviceType, address, btDeviceName);
+            }
+        }
+        // handleDeviceConnection() && result to make sure the method get executed
+        result = mDeviceBroker.handleDeviceConnection(
+                isActive, inDevice, address, btDeviceName) && result;
+        return result;
+    }
+
+    private void setBtScoActiveDevice(BluetoothDevice btDevice) {
+        synchronized (mScoClients) {
+            Log.i(TAG, "setBtScoActiveDevice: " + mBluetoothHeadsetDevice + " -> " + btDevice);
+            final BluetoothDevice previousActiveDevice = mBluetoothHeadsetDevice;
+            if (Objects.equals(btDevice, previousActiveDevice)) {
+                return;
+            }
+            if (!handleBtScoActiveDeviceChange(previousActiveDevice, false)) {
+                Log.w(TAG, "setBtScoActiveDevice() failed to remove previous device "
+                        + previousActiveDevice);
+            }
+            if (!handleBtScoActiveDeviceChange(btDevice, true)) {
+                Log.e(TAG, "setBtScoActiveDevice() failed to add new device " + btDevice);
+                // set mBluetoothHeadsetDevice to null when failing to add new device
+                btDevice = null;
+            }
+            mBluetoothHeadsetDevice = btDevice;
+            if (mBluetoothHeadsetDevice == null) {
+                resetBluetoothSco();
+            }
+        }
+    }
+
+    private BluetoothProfile.ServiceListener mBluetoothProfileServiceListener =
+            new BluetoothProfile.ServiceListener() {
+                public void onServiceConnected(int profile, BluetoothProfile proxy) {
+                    final BluetoothDevice btDevice;
+                    List<BluetoothDevice> deviceList;
+                    switch(profile) {
+                        case BluetoothProfile.A2DP:
+                            synchronized (mDeviceBroker.mA2dpAvrcpLock) {
+                                mA2dp = (BluetoothA2dp) proxy;
+                                deviceList = mA2dp.getConnectedDevices();
+                                if (deviceList.size() > 0) {
+                                    btDevice = deviceList.get(0);
+                                    if (btDevice == null) {
+                                        Log.e(TAG, "Invalid null device in BT profile listener");
+                                        return;
+                                    }
+                                    final @BluetoothProfile.BtProfileState int state =
+                                            mA2dp.getConnectionState(btDevice);
+                                    mDeviceBroker.handleSetA2dpSinkConnectionState(
+                                            state, new BluetoothA2dpDeviceInfo(btDevice));
+                                }
+                            }
+                            break;
+
+                        case BluetoothProfile.A2DP_SINK:
+                            deviceList = proxy.getConnectedDevices();
+                            if (deviceList.size() > 0) {
+                                btDevice = deviceList.get(0);
+                                final @BluetoothProfile.BtProfileState int state =
+                                        proxy.getConnectionState(btDevice);
+                                mDeviceBroker.handleSetA2dpSourceConnectionState(
+                                        state, new BluetoothA2dpDeviceInfo(btDevice));
+                            }
+                            break;
+
+                        case BluetoothProfile.HEADSET:
+                            synchronized (mScoClients) {
+                                // Discard timeout message
+                                mDeviceBroker.handleCancelFailureToConnectToBtHeadsetService();
+                                mBluetoothHeadset = (BluetoothHeadset) proxy;
+                                setBtScoActiveDevice(mBluetoothHeadset.getActiveDevice());
+                                // Refresh SCO audio state
+                                checkScoAudioState();
+                                // Continue pending action if any
+                                if (mScoAudioState == SCO_STATE_ACTIVATE_REQ
+                                        || mScoAudioState == SCO_STATE_DEACTIVATE_REQ) {
+                                    boolean status = false;
+                                    if (mBluetoothHeadsetDevice != null) {
+                                        switch (mScoAudioState) {
+                                            case SCO_STATE_ACTIVATE_REQ:
+                                                status = connectBluetoothScoAudioHelper(
+                                                        mBluetoothHeadset,
+                                                        mBluetoothHeadsetDevice, mScoAudioMode);
+                                                if (status) {
+                                                    mScoAudioState = SCO_STATE_ACTIVE_INTERNAL;
+                                                }
+                                                break;
+                                            case SCO_STATE_DEACTIVATE_REQ:
+                                                status = disconnectBluetoothScoAudioHelper(
+                                                        mBluetoothHeadset,
+                                                        mBluetoothHeadsetDevice, mScoAudioMode);
+                                                if (status) {
+                                                    mScoAudioState = SCO_STATE_DEACTIVATING;
+                                                }
+                                                break;
+                                        }
+                                    }
+                                    if (!status) {
+                                        mScoAudioState = SCO_STATE_INACTIVE;
+                                        broadcastScoConnectionState(
+                                                AudioManager.SCO_AUDIO_STATE_DISCONNECTED);
+                                    }
+                                }
+                            }
+                            break;
+
+                        case BluetoothProfile.HEARING_AID:
+                            mHearingAid = (BluetoothHearingAid) proxy;
+                            deviceList = mHearingAid.getConnectedDevices();
+                            if (deviceList.size() > 0) {
+                                btDevice = deviceList.get(0);
+                                final @BluetoothProfile.BtProfileState int state =
+                                        mHearingAid.getConnectionState(btDevice);
+                                mDeviceBroker.setBluetoothHearingAidDeviceConnectionState(
+                                        btDevice, state,
+                                        /*suppressNoisyIntent*/ false,
+                                        /*musicDevice*/ android.media.AudioSystem.DEVICE_NONE,
+                                        /*eventSource*/ "mBluetoothProfileServiceListener");
+                            }
+                            break;
+
+                        default:
+                            break;
+                    }
+                }
+                public void onServiceDisconnected(int profile) {
+
+                    switch (profile) {
+                        case BluetoothProfile.A2DP:
+                            mDeviceBroker.handleDisconnectA2dp();
+                            break;
+
+                        case BluetoothProfile.A2DP_SINK:
+                            mDeviceBroker.handleDisconnectA2dpSink();
+                            break;
+
+                        case BluetoothProfile.HEADSET:
+                            disconnectHeadset();
+                            break;
+
+                        case BluetoothProfile.HEARING_AID:
+                            mDeviceBroker.handleDisconnectHearingAid();
+                            break;
+
+                        default:
+                            break;
+                    }
+                }
+            };
+
+    void disconnectAllBluetoothProfiles() {
+        mDeviceBroker.handleDisconnectA2dp();
+        mDeviceBroker.handleDisconnectA2dpSink();
+        disconnectHeadset();
+        mDeviceBroker.handleDisconnectHearingAid();
+    }
+
+    private void disconnectHeadset() {
+        synchronized (mScoClients) {
+            setBtScoActiveDevice(null);
+            mBluetoothHeadset = null;
+        }
+    }
+
+    //----------------------------------------------------------------------
+    private class ScoClient implements IBinder.DeathRecipient {
+        private IBinder mCb; // To be notified of client's death
+        private int mCreatorPid;
+        private int mStartcount; // number of SCO connections started by this client
+
+        ScoClient(IBinder cb) {
+            mCb = cb;
+            mCreatorPid = Binder.getCallingPid();
+            mStartcount = 0;
+        }
+
+        public void binderDied() {
+            synchronized (mScoClients) {
+                Log.w(TAG, "SCO client died");
+                int index = mScoClients.indexOf(this);
+                if (index < 0) {
+                    Log.w(TAG, "unregistered SCO client died");
+                } else {
+                    clearCount(true);
+                    mScoClients.remove(this);
+                }
+            }
+        }
+
+        public void incCount(int scoAudioMode) {
+            synchronized (mScoClients) {
+                requestScoState(BluetoothHeadset.STATE_AUDIO_CONNECTED, scoAudioMode);
+                if (mStartcount == 0) {
+                    try {
+                        mCb.linkToDeath(this, 0);
+                    } catch (RemoteException e) {
+                        // client has already died!
+                        Log.w(TAG, "ScoClient  incCount() could not link to "
+                                + mCb + " binder death");
+                    }
+                }
+                mStartcount++;
+            }
+        }
+
+        public void decCount() {
+            synchronized (mScoClients) {
+                if (mStartcount == 0) {
+                    Log.w(TAG, "ScoClient.decCount() already 0");
+                } else {
+                    mStartcount--;
+                    if (mStartcount == 0) {
+                        try {
+                            mCb.unlinkToDeath(this, 0);
+                        } catch (NoSuchElementException e) {
+                            Log.w(TAG, "decCount() going to 0 but not registered to binder");
+                        }
+                    }
+                    requestScoState(BluetoothHeadset.STATE_AUDIO_DISCONNECTED, 0);
+                }
+            }
+        }
+
+        public void clearCount(boolean stopSco) {
+            synchronized (mScoClients) {
+                if (mStartcount != 0) {
+                    try {
+                        mCb.unlinkToDeath(this, 0);
+                    } catch (NoSuchElementException e) {
+                        Log.w(TAG, "clearCount() mStartcount: "
+                                + mStartcount + " != 0 but not registered to binder");
+                    }
+                }
+                mStartcount = 0;
+                if (stopSco) {
+                    requestScoState(BluetoothHeadset.STATE_AUDIO_DISCONNECTED, 0);
+                }
+            }
+        }
+
+        public int getCount() {
+            return mStartcount;
+        }
+
+        public IBinder getBinder() {
+            return mCb;
+        }
+
+        public int getPid() {
+            return mCreatorPid;
+        }
+
+        public int totalCount() {
+            synchronized (mScoClients) {
+                int count = 0;
+                for (ScoClient mScoClient : mScoClients) {
+                    count += mScoClient.getCount();
+                }
+                return count;
+            }
+        }
+
+        private void requestScoState(int state, int scoAudioMode) {
+            checkScoAudioState();
+            int clientCount = totalCount();
+            if (clientCount != 0) {
+                Log.i(TAG, "requestScoState: state=" + state + ", scoAudioMode=" + scoAudioMode
+                        + ", clientCount=" + clientCount);
+                return;
+            }
+            if (state == BluetoothHeadset.STATE_AUDIO_CONNECTED) {
+                // Make sure that the state transitions to CONNECTING even if we cannot initiate
+                // the connection.
+                broadcastScoConnectionState(AudioManager.SCO_AUDIO_STATE_CONNECTING);
+                // Accept SCO audio activation only in NORMAL audio mode or if the mode is
+                // currently controlled by the same client process.
+                synchronized (mDeviceBroker.mSetModeLock) {
+                    int modeOwnerPid =  mDeviceBroker.getSetModeDeathHandlers().isEmpty()
+                            ? 0 : mDeviceBroker.getSetModeDeathHandlers().get(0).getPid();
+                    if (modeOwnerPid != 0 && (modeOwnerPid != mCreatorPid)) {
+                        Log.w(TAG, "requestScoState: audio mode is not NORMAL and modeOwnerPid "
+                                + modeOwnerPid + " != creatorPid " + mCreatorPid);
+                        broadcastScoConnectionState(AudioManager.SCO_AUDIO_STATE_DISCONNECTED);
+                        return;
+                    }
+                    switch (mScoAudioState) {
+                        case SCO_STATE_INACTIVE:
+                            mScoAudioMode = scoAudioMode;
+                            if (scoAudioMode == SCO_MODE_UNDEFINED) {
+                                mScoAudioMode = SCO_MODE_VIRTUAL_CALL;
+                                if (mBluetoothHeadsetDevice != null) {
+                                    mScoAudioMode = Settings.Global.getInt(
+                                            mDeviceBroker.getContentResolver(),
+                                            "bluetooth_sco_channel_"
+                                                    + mBluetoothHeadsetDevice.getAddress(),
+                                            SCO_MODE_VIRTUAL_CALL);
+                                    if (mScoAudioMode > SCO_MODE_MAX || mScoAudioMode < 0) {
+                                        mScoAudioMode = SCO_MODE_VIRTUAL_CALL;
+                                    }
+                                }
+                            }
+                            if (mBluetoothHeadset == null) {
+                                if (getBluetoothHeadset()) {
+                                    mScoAudioState = SCO_STATE_ACTIVATE_REQ;
+                                } else {
+                                    Log.w(TAG, "requestScoState: getBluetoothHeadset failed during"
+                                            + " connection, mScoAudioMode=" + mScoAudioMode);
+                                    broadcastScoConnectionState(
+                                            AudioManager.SCO_AUDIO_STATE_DISCONNECTED);
+                                }
+                                break;
+                            }
+                            if (mBluetoothHeadsetDevice == null) {
+                                Log.w(TAG, "requestScoState: no active device while connecting,"
+                                        + " mScoAudioMode=" + mScoAudioMode);
+                                broadcastScoConnectionState(
+                                        AudioManager.SCO_AUDIO_STATE_DISCONNECTED);
+                                break;
+                            }
+                            if (connectBluetoothScoAudioHelper(mBluetoothHeadset,
+                                    mBluetoothHeadsetDevice, mScoAudioMode)) {
+                                mScoAudioState = SCO_STATE_ACTIVE_INTERNAL;
+                            } else {
+                                Log.w(TAG, "requestScoState: connect to " + mBluetoothHeadsetDevice
+                                        + " failed, mScoAudioMode=" + mScoAudioMode);
+                                broadcastScoConnectionState(
+                                        AudioManager.SCO_AUDIO_STATE_DISCONNECTED);
+                            }
+                            break;
+                        case SCO_STATE_DEACTIVATING:
+                            mScoAudioState = SCO_STATE_ACTIVATE_REQ;
+                            break;
+                        case SCO_STATE_DEACTIVATE_REQ:
+                            mScoAudioState = SCO_STATE_ACTIVE_INTERNAL;
+                            broadcastScoConnectionState(AudioManager.SCO_AUDIO_STATE_CONNECTED);
+                            break;
+                        default:
+                            Log.w(TAG, "requestScoState: failed to connect in state "
+                                    + mScoAudioState + ", scoAudioMode=" + scoAudioMode);
+                            broadcastScoConnectionState(AudioManager.SCO_AUDIO_STATE_DISCONNECTED);
+                            break;
+
+                    }
+                }
+            } else if (state == BluetoothHeadset.STATE_AUDIO_DISCONNECTED) {
+                switch (mScoAudioState) {
+                    case SCO_STATE_ACTIVE_INTERNAL:
+                        if (mBluetoothHeadset == null) {
+                            if (getBluetoothHeadset()) {
+                                mScoAudioState = SCO_STATE_DEACTIVATE_REQ;
+                            } else {
+                                Log.w(TAG, "requestScoState: getBluetoothHeadset failed during"
+                                        + " disconnection, mScoAudioMode=" + mScoAudioMode);
+                                mScoAudioState = SCO_STATE_INACTIVE;
+                                broadcastScoConnectionState(
+                                        AudioManager.SCO_AUDIO_STATE_DISCONNECTED);
+                            }
+                            break;
+                        }
+                        if (mBluetoothHeadsetDevice == null) {
+                            mScoAudioState = SCO_STATE_INACTIVE;
+                            broadcastScoConnectionState(
+                                    AudioManager.SCO_AUDIO_STATE_DISCONNECTED);
+                            break;
+                        }
+                        if (disconnectBluetoothScoAudioHelper(mBluetoothHeadset,
+                                mBluetoothHeadsetDevice, mScoAudioMode)) {
+                            mScoAudioState = SCO_STATE_DEACTIVATING;
+                        } else {
+                            mScoAudioState = SCO_STATE_INACTIVE;
+                            broadcastScoConnectionState(
+                                    AudioManager.SCO_AUDIO_STATE_DISCONNECTED);
+                        }
+                        break;
+                    case SCO_STATE_ACTIVATE_REQ:
+                        mScoAudioState = SCO_STATE_INACTIVE;
+                        broadcastScoConnectionState(AudioManager.SCO_AUDIO_STATE_DISCONNECTED);
+                        break;
+                    default:
+                        Log.w(TAG, "requestScoState: failed to disconnect in state "
+                                + mScoAudioState + ", scoAudioMode=" + scoAudioMode);
+                        broadcastScoConnectionState(AudioManager.SCO_AUDIO_STATE_DISCONNECTED);
+                        break;
+                }
+            }
+        }
+    }
+
+    //-----------------------------------------------------
+    // Utilities
+    private void sendStickyBroadcastToAll(Intent intent) {
+        intent.addFlags(Intent.FLAG_RECEIVER_FOREGROUND);
+        final long ident = Binder.clearCallingIdentity();
+        try {
+            mDeviceBroker.getContext().sendStickyBroadcastAsUser(intent, UserHandle.ALL);
+        } finally {
+            Binder.restoreCallingIdentity(ident);
+        }
+    }
+
+    private static boolean disconnectBluetoothScoAudioHelper(BluetoothHeadset bluetoothHeadset,
+            BluetoothDevice device, int scoAudioMode) {
+        switch (scoAudioMode) {
+            case SCO_MODE_RAW:
+                return bluetoothHeadset.disconnectAudio();
+            case SCO_MODE_VIRTUAL_CALL:
+                return bluetoothHeadset.stopScoUsingVirtualVoiceCall();
+            case SCO_MODE_VR:
+                return bluetoothHeadset.stopVoiceRecognition(device);
+            default:
+                return false;
+        }
+    }
+
+    private static boolean connectBluetoothScoAudioHelper(BluetoothHeadset bluetoothHeadset,
+            BluetoothDevice device, int scoAudioMode) {
+        switch (scoAudioMode) {
+            case SCO_MODE_RAW:
+                return bluetoothHeadset.connectAudio();
+            case SCO_MODE_VIRTUAL_CALL:
+                return bluetoothHeadset.startScoUsingVirtualVoiceCall();
+            case SCO_MODE_VR:
+                return bluetoothHeadset.startVoiceRecognition(device);
+            default:
+                return false;
+        }
+    }
+
+    /*package*/ void resetBluetoothSco() {
+        synchronized (mScoClients) {
+            clearAllScoClients(0, false);
+            mScoAudioState = SCO_STATE_INACTIVE;
+            broadcastScoConnectionState(AudioManager.SCO_AUDIO_STATE_DISCONNECTED);
+        }
+        AudioSystem.setParameters("A2dpSuspended=false");
+        mDeviceBroker.setBluetoothScoOn(false, "resetBluetoothSco");
+    }
+
+
+    private void checkScoAudioState() {
+        synchronized (mScoClients) {
+            if (mBluetoothHeadset != null
+                    && mBluetoothHeadsetDevice != null
+                    && mScoAudioState == SCO_STATE_INACTIVE
+                    && mBluetoothHeadset.getAudioState(mBluetoothHeadsetDevice)
+                            != BluetoothHeadset.STATE_AUDIO_DISCONNECTED) {
+                mScoAudioState = SCO_STATE_ACTIVE_EXTERNAL;
+            }
+        }
+    }
+
+
+    private ScoClient getScoClient(IBinder cb, boolean create) {
+        synchronized (mScoClients) {
+            for (ScoClient existingClient : mScoClients) {
+                if (existingClient.getBinder() == cb) {
+                    return existingClient;
+                }
+            }
+            if (create) {
+                ScoClient newClient = new ScoClient(cb);
+                mScoClients.add(newClient);
+                return newClient;
+            }
+            return null;
+        }
+    }
+
+    private void clearAllScoClients(int exceptPid, boolean stopSco) {
+        synchronized (mScoClients) {
+            ScoClient savedClient = null;
+            for (ScoClient cl : mScoClients) {
+                if (cl.getPid() != exceptPid) {
+                    cl.clearCount(stopSco);
+                } else {
+                    savedClient = cl;
+                }
+            }
+            mScoClients.clear();
+            if (savedClient != null) {
+                mScoClients.add(savedClient);
+            }
+        }
+    }
+
+    private boolean getBluetoothHeadset() {
+        boolean result = false;
+        BluetoothAdapter adapter = BluetoothAdapter.getDefaultAdapter();
+        if (adapter != null) {
+            result = adapter.getProfileProxy(mDeviceBroker.getContext(),
+                    mBluetoothProfileServiceListener, BluetoothProfile.HEADSET);
+        }
+        // If we could not get a bluetooth headset proxy, send a failure message
+        // without delay to reset the SCO audio state and clear SCO clients.
+        // If we could get a proxy, send a delayed failure message that will reset our state
+        // in case we don't receive onServiceConnected().
+        mDeviceBroker.handleFailureToConnectToBtHeadsetService(
+                result ? AudioDeviceBroker.BT_HEADSET_CNCT_TIMEOUT_MS : 0);
+        return result;
+    }
+
+    private int mapBluetoothCodecToAudioFormat(int btCodecType) {
+        switch (btCodecType) {
+            case BluetoothCodecConfig.SOURCE_CODEC_TYPE_SBC:
+                return AudioSystem.AUDIO_FORMAT_SBC;
+            case BluetoothCodecConfig.SOURCE_CODEC_TYPE_AAC:
+                return AudioSystem.AUDIO_FORMAT_AAC;
+            case BluetoothCodecConfig.SOURCE_CODEC_TYPE_APTX:
+                return AudioSystem.AUDIO_FORMAT_APTX;
+            case BluetoothCodecConfig.SOURCE_CODEC_TYPE_APTX_HD:
+                return AudioSystem.AUDIO_FORMAT_APTX_HD;
+            case BluetoothCodecConfig.SOURCE_CODEC_TYPE_LDAC:
+                return AudioSystem.AUDIO_FORMAT_LDAC;
+            default:
+                return AudioSystem.AUDIO_FORMAT_DEFAULT;
+        }
+    }
+}
diff --git a/services/core/java/com/android/server/audio/PlaybackActivityMonitor.java b/services/core/java/com/android/server/audio/PlaybackActivityMonitor.java
index dc564ba..3a25d98 100644
--- a/services/core/java/com/android/server/audio/PlaybackActivityMonitor.java
+++ b/services/core/java/com/android/server/audio/PlaybackActivityMonitor.java
@@ -202,7 +202,7 @@
                     if (mPrivilegedAlarmActiveCount++ == 0) {
                         mSavedAlarmVolume = AudioSystem.getStreamVolumeIndex(
                                 AudioSystem.STREAM_ALARM, AudioSystem.DEVICE_OUT_SPEAKER);
-                        AudioSystem.setStreamVolumeIndex(AudioSystem.STREAM_ALARM,
+                        AudioSystem.setStreamVolumeIndexAS(AudioSystem.STREAM_ALARM,
                                 mMaxAlarmVolume, AudioSystem.DEVICE_OUT_SPEAKER);
                     }
                 } else if (event != AudioPlaybackConfiguration.PLAYER_STATE_STARTED &&
@@ -211,7 +211,7 @@
                         if (AudioSystem.getStreamVolumeIndex(
                                 AudioSystem.STREAM_ALARM, AudioSystem.DEVICE_OUT_SPEAKER) ==
                                 mMaxAlarmVolume) {
-                            AudioSystem.setStreamVolumeIndex(AudioSystem.STREAM_ALARM,
+                            AudioSystem.setStreamVolumeIndexAS(AudioSystem.STREAM_ALARM,
                                     mSavedAlarmVolume, AudioSystem.DEVICE_OUT_SPEAKER);
                         }
                     }
diff --git a/services/core/java/com/android/server/biometrics/BiometricService.java b/services/core/java/com/android/server/biometrics/BiometricService.java
index 41fedc5..15d66e6 100644
--- a/services/core/java/com/android/server/biometrics/BiometricService.java
+++ b/services/core/java/com/android/server/biometrics/BiometricService.java
@@ -29,10 +29,12 @@
 import android.app.ActivityTaskManager;
 import android.app.AppOpsManager;
 import android.app.IActivityTaskManager;
+import android.app.KeyguardManager;
 import android.app.TaskStackListener;
 import android.app.UserSwitchObserver;
 import android.content.ContentResolver;
 import android.content.Context;
+import android.content.Intent;
 import android.content.pm.PackageManager;
 import android.database.ContentObserver;
 import android.hardware.biometrics.BiometricAuthenticator;
@@ -377,6 +379,13 @@
                 new BiometricTaskStackListener();
         private final Random mRandom = new Random();
 
+        // TODO(b/123378871): Remove when moved.
+        // When BiometricPrompt#setEnableFallback is set to true, we need to store the client (app)
+        // receiver. BiometricService internally launches CDCA which invokes BiometricService to
+        // start authentication (normal path). When auth is success/rejected, CDCA will use an aidl
+        // method to poke BiometricService - the result will then be forwarded to this receiver.
+        private IBiometricServiceReceiver mConfirmDeviceCredentialReceiver;
+
         // The current authentication session, null if idle/done. We need to track both the current
         // and pending sessions since errors may be sent to either.
         private AuthSession mCurrentAuthSession;
@@ -705,6 +714,22 @@
                 }
             }
 
+            // Launch CDC instead if necessary. CDC will return results through an AIDL call, since
+            // we can't get activity results. Store the receiver somewhere so we can forward the
+            // result back to the client.
+            // TODO(b/123378871): Remove when moved.
+            if (bundle.getBoolean(BiometricPrompt.KEY_ENABLE_FALLBACK)) {
+                mConfirmDeviceCredentialReceiver = receiver;
+                final KeyguardManager kgm = getContext().getSystemService(KeyguardManager.class);
+                // Use this so we don't need to duplicate logic..
+                final Intent intent = kgm.createConfirmDeviceCredentialIntent(null /* title */,
+                        null /* description */);
+                // Then give it the bundle to do magic behavior..
+                intent.putExtra(KeyguardManager.EXTRA_BIOMETRIC_PROMPT_BUNDLE, bundle);
+                getContext().startActivityAsUser(intent, UserHandle.CURRENT);
+                return;
+            }
+
             mHandler.post(() -> {
                 final Pair<Integer, Integer> result = checkAndGetBiometricModality(userId);
                 final int modality = result.first;
@@ -745,6 +770,36 @@
             });
         }
 
+        @Override // Binder call
+        public void onConfirmDeviceCredentialSuccess() {
+            checkInternalPermission();
+            if (mConfirmDeviceCredentialReceiver == null) {
+                Slog.w(TAG, "onCDCASuccess null!");
+                return;
+            }
+            try {
+                mConfirmDeviceCredentialReceiver.onAuthenticationSucceeded();
+            } catch (RemoteException e) {
+                Slog.e(TAG, "RemoteException", e);
+            }
+            mConfirmDeviceCredentialReceiver = null;
+        }
+
+        @Override // Binder call
+        public void onConfirmDeviceCredentialError(int error, String message) {
+            checkInternalPermission();
+            if (mConfirmDeviceCredentialReceiver == null) {
+                Slog.w(TAG, "onCDCAError null! Error: " + error + " " + message);
+                return;
+            }
+            try {
+                mConfirmDeviceCredentialReceiver.onError(error, message);
+            } catch (RemoteException e) {
+                Slog.e(TAG, "RemoteException", e);
+            }
+            mConfirmDeviceCredentialReceiver = null;
+        }
+
         /**
          * authenticate() (above) which is called from BiometricPrompt determines which
          * modality/modalities to start authenticating with. authenticateInternal() should only be
diff --git a/services/core/java/com/android/server/biometrics/fingerprint/FingerprintService.java b/services/core/java/com/android/server/biometrics/fingerprint/FingerprintService.java
index 3db6a74..fcf821f2 100644
--- a/services/core/java/com/android/server/biometrics/fingerprint/FingerprintService.java
+++ b/services/core/java/com/android/server/biometrics/fingerprint/FingerprintService.java
@@ -48,14 +48,12 @@
 import android.os.UserHandle;
 import android.os.UserManager;
 import android.util.Slog;
-import android.util.StatsLog;
 import android.util.proto.ProtoOutputStream;
 
 import com.android.internal.annotations.GuardedBy;
 import com.android.internal.logging.MetricsLogger;
 import com.android.internal.util.DumpUtils;
 import com.android.server.SystemServerInitThreadPool;
-import com.android.server.biometrics.AuthenticationClient;
 import com.android.server.biometrics.BiometricServiceBase;
 import com.android.server.biometrics.BiometricUtils;
 import com.android.server.biometrics.ClientMonitor;
@@ -588,11 +586,6 @@
         public void onAcquired(final long deviceId, final int acquiredInfo, final int vendorCode) {
             mHandler.post(() -> {
                 FingerprintService.super.handleAcquired(deviceId, acquiredInfo, vendorCode);
-                if (getLockoutMode() == AuthenticationClient.LOCKOUT_NONE
-                        && getCurrentClient() instanceof AuthenticationClient) {
-                    // Ignore enrollment acquisitions or acquisitions when we are locked out.
-                    StatsLog.write(StatsLog.FINGERPRINT_ACQUIRED, mCurrentUserId, mIsCrypto);
-                }
             });
         }
 
@@ -602,22 +595,6 @@
             mHandler.post(() -> {
                 Fingerprint fp = new Fingerprint("", groupId, fingerId, deviceId);
                 FingerprintService.super.handleAuthenticated(fp, token);
-                // Send authentication to statsd.
-                final boolean authenticated = fingerId != 0;
-                StatsLog.write(StatsLog.FINGERPRINT_AUTHENTICATED, mCurrentUserId, mIsCrypto,
-                        authenticated);
-                if (!authenticated) {
-                    // If we failed to authenticate because of a lockout, inform statsd.
-                    final int lockoutMode = getLockoutMode();
-                    if (lockoutMode == AuthenticationClient.LOCKOUT_TIMED) {
-                        StatsLog.write(StatsLog.FINGERPRINT_ERROR_OCCURRED, mCurrentUserId,
-                                mIsCrypto, StatsLog.FINGERPRINT_ERROR_OCCURRED__ERROR__LOCKOUT);
-                    } else if (lockoutMode == AuthenticationClient.LOCKOUT_PERMANENT) {
-                        StatsLog.write(StatsLog.FINGERPRINT_ERROR_OCCURRED, mCurrentUserId,
-                                mIsCrypto,
-                                StatsLog.FINGERPRINT_ERROR_OCCURRED__ERROR__PERMANENT_LOCKOUT);
-                    }
-                }
             });
         }
 
diff --git a/services/core/java/com/android/server/connectivity/KeepaliveTracker.java b/services/core/java/com/android/server/connectivity/KeepaliveTracker.java
index 8a3cdca..1559ba8 100644
--- a/services/core/java/com/android/server/connectivity/KeepaliveTracker.java
+++ b/services/core/java/com/android/server/connectivity/KeepaliveTracker.java
@@ -16,40 +16,49 @@
 
 package com.android.server.connectivity;
 
-import com.android.internal.util.HexDump;
-import com.android.internal.util.IndentingPrintWriter;
-import com.android.server.connectivity.NetworkAgentInfo;
-import android.net.ConnectivityManager;
+// TODO: Clean up imports and remove references of PacketKeepalive constants.
+
+import static android.net.ConnectivityManager.PacketKeepalive.ERROR_INVALID_INTERVAL;
+import static android.net.ConnectivityManager.PacketKeepalive.ERROR_INVALID_IP_ADDRESS;
+import static android.net.ConnectivityManager.PacketKeepalive.ERROR_INVALID_NETWORK;
+import static android.net.ConnectivityManager.PacketKeepalive.MIN_INTERVAL;
+import static android.net.ConnectivityManager.PacketKeepalive.NATT_PORT;
+import static android.net.ConnectivityManager.PacketKeepalive.NO_KEEPALIVE;
+import static android.net.ConnectivityManager.PacketKeepalive.SUCCESS;
+import static android.net.NetworkAgent.CMD_START_PACKET_KEEPALIVE;
+import static android.net.NetworkAgent.CMD_STOP_PACKET_KEEPALIVE;
+import static android.net.NetworkAgent.EVENT_PACKET_KEEPALIVE;
+import static android.net.SocketKeepalive.ERROR_INVALID_SOCKET;
+
+import android.annotation.NonNull;
+import android.annotation.Nullable;
 import android.net.ConnectivityManager.PacketKeepalive;
 import android.net.KeepalivePacketData;
-import android.net.LinkAddress;
 import android.net.NetworkAgent;
 import android.net.NetworkUtils;
 import android.net.util.IpUtils;
 import android.os.Binder;
-import android.os.IBinder;
 import android.os.Handler;
+import android.os.IBinder;
 import android.os.Message;
 import android.os.Messenger;
 import android.os.Process;
 import android.os.RemoteException;
-import android.system.OsConstants;
+import android.system.ErrnoException;
+import android.system.Os;
 import android.util.Log;
 import android.util.Pair;
 
-import java.nio.ByteBuffer;
-import java.nio.ByteOrder;
-import java.net.Inet4Address;
-import java.net.Inet6Address;
+import com.android.internal.util.HexDump;
+import com.android.internal.util.IndentingPrintWriter;
+
+import java.io.FileDescriptor;
 import java.net.InetAddress;
+import java.net.InetSocketAddress;
+import java.net.SocketAddress;
 import java.util.ArrayList;
 import java.util.HashMap;
 
-import static android.net.ConnectivityManager.PacketKeepalive.*;
-import static android.net.NetworkAgent.CMD_START_PACKET_KEEPALIVE;
-import static android.net.NetworkAgent.CMD_STOP_PACKET_KEEPALIVE;
-import static android.net.NetworkAgent.EVENT_PACKET_KEEPALIVE;
-
 /**
  * Manages packet keepalive requests.
  *
@@ -300,7 +309,9 @@
         }
     }
 
-    public void handleEventPacketKeepalive(NetworkAgentInfo nai, Message message) {
+    /** Handle keepalive events from lower layer. */
+    public void handleEventPacketKeepalive(@NonNull NetworkAgentInfo nai,
+            @NonNull Message message) {
         int slot = message.arg1;
         int reason = message.arg2;
 
@@ -330,8 +341,18 @@
         }
     }
 
-    public void startNattKeepalive(NetworkAgentInfo nai, int intervalSeconds, Messenger messenger,
-            IBinder binder, String srcAddrString, int srcPort, String dstAddrString, int dstPort) {
+    /**
+     * Called when requesting that keepalives be started on a IPsec NAT-T socket. See
+     * {@link android.net.SocketKeepalive}.
+     **/
+    public void startNattKeepalive(@Nullable NetworkAgentInfo nai,
+            int intervalSeconds,
+            @NonNull Messenger messenger,
+            @NonNull IBinder binder,
+            @NonNull String srcAddrString,
+            int srcPort,
+            @NonNull String dstAddrString,
+            int dstPort) {
         if (nai == null) {
             notifyMessenger(messenger, NO_KEEPALIVE, ERROR_INVALID_NETWORK);
             return;
@@ -360,6 +381,56 @@
                 NetworkAgent.CMD_START_PACKET_KEEPALIVE, ki).sendToTarget();
     }
 
+   /**
+    * Called when requesting that keepalives be started on a IPsec NAT-T socket. This function is
+    * identical to {@link #startNattKeepalive}, but also takes a {@code resourceId}, which is the
+    * resource index bound to the {@link UdpEncapsulationSocket} when creating by
+    * {@link com.android.server.IpSecService} to verify whether the given
+    * {@link UdpEncapsulationSocket} is legitimate.
+    **/
+    public void startNattKeepalive(@Nullable NetworkAgentInfo nai,
+            @Nullable FileDescriptor fd,
+            int resourceId,
+            int intervalSeconds,
+            @NonNull Messenger messenger,
+            @NonNull IBinder binder,
+            @NonNull String srcAddrString,
+            @NonNull String dstAddrString,
+            int dstPort) {
+        // Ensure that the socket is created by IpSecService.
+        if (!isNattKeepaliveSocketValid(fd, resourceId)) {
+            notifyMessenger(messenger, NO_KEEPALIVE, ERROR_INVALID_SOCKET);
+        }
+
+        // Get src port to adopt old API.
+        int srcPort = 0;
+        try {
+            final SocketAddress srcSockAddr = Os.getsockname(fd);
+            srcPort = ((InetSocketAddress) srcSockAddr).getPort();
+        } catch (ErrnoException e) {
+            notifyMessenger(messenger, NO_KEEPALIVE, ERROR_INVALID_SOCKET);
+        }
+
+        // Forward request to old API.
+        startNattKeepalive(nai, intervalSeconds, messenger, binder, srcAddrString, srcPort,
+                dstAddrString, dstPort);
+    }
+
+    /**
+     * Verify if the IPsec NAT-T file descriptor and resource Id hold for IPsec keepalive is valid.
+     **/
+    public static boolean isNattKeepaliveSocketValid(@Nullable FileDescriptor fd, int resourceId) {
+        // TODO: 1. confirm whether the fd is called from system api or created by IpSecService.
+        //       2. If the fd is created from the system api, check that it's bounded. And
+        //          call dup to keep the fd open.
+        //       3. If the fd is created from IpSecService, check if the resource ID is valid. And
+        //          hold the resource needed in IpSecService.
+        if (null == fd) {
+            return false;
+        }
+        return true;
+    }
+
     public void dump(IndentingPrintWriter pw) {
         pw.println("Packet keepalives:");
         pw.increaseIndent();
diff --git a/services/core/java/com/android/server/connectivity/NetworkAgentInfo.java b/services/core/java/com/android/server/connectivity/NetworkAgentInfo.java
index 9ea73fb..d0cff25 100644
--- a/services/core/java/com/android/server/connectivity/NetworkAgentInfo.java
+++ b/services/core/java/com/android/server/connectivity/NetworkAgentInfo.java
@@ -152,6 +152,10 @@
     // Whether a captive portal was found during the last network validation attempt.
     public boolean lastCaptivePortalDetected;
 
+    // Indicates the user was notified of a successful captive portal login since a portal was
+    // last detected.
+    public boolean captivePortalLoginNotified;
+
     // Networks are lingered when they become unneeded as a result of their NetworkRequests being
     // satisfied by a higher-scoring network. so as to allow communication to wrap up before the
     // network is taken down.  This usually only happens to the default network. Lingering ends with
@@ -618,18 +622,19 @@
     }
 
     public String toString() {
-        return "NetworkAgentInfo{ ni{" + networkInfo + "}  " +
-                "network{" + network + "}  nethandle{" + network.getNetworkHandle() + "}  " +
-                "lp{" + linkProperties + "}  " +
-                "nc{" + networkCapabilities + "}  Score{" + getCurrentScore() + "}  " +
-                "everValidated{" + everValidated + "}  lastValidated{" + lastValidated + "}  " +
-                "created{" + created + "} lingering{" + isLingering() + "} " +
-                "explicitlySelected{" + networkMisc.explicitlySelected + "} " +
-                "acceptUnvalidated{" + networkMisc.acceptUnvalidated + "} " +
-                "everCaptivePortalDetected{" + everCaptivePortalDetected + "} " +
-                "lastCaptivePortalDetected{" + lastCaptivePortalDetected + "} " +
-                "clat{" + clatd + "} " +
-                "}";
+        return "NetworkAgentInfo{ ni{" + networkInfo + "}  "
+                + "network{" + network + "}  nethandle{" + network.getNetworkHandle() + "}  "
+                + "lp{" + linkProperties + "}  "
+                + "nc{" + networkCapabilities + "}  Score{" + getCurrentScore() + "}  "
+                + "everValidated{" + everValidated + "}  lastValidated{" + lastValidated + "}  "
+                + "created{" + created + "} lingering{" + isLingering() + "} "
+                + "explicitlySelected{" + networkMisc.explicitlySelected + "} "
+                + "acceptUnvalidated{" + networkMisc.acceptUnvalidated + "} "
+                + "everCaptivePortalDetected{" + everCaptivePortalDetected + "} "
+                + "lastCaptivePortalDetected{" + lastCaptivePortalDetected + "} "
+                + "captivePortalLoginNotified{" + captivePortalLoginNotified + "} "
+                + "clat{" + clatd + "} "
+                + "}";
     }
 
     public String name() {
diff --git a/services/core/java/com/android/server/connectivity/NetworkNotificationManager.java b/services/core/java/com/android/server/connectivity/NetworkNotificationManager.java
index 36a2476..b50477b 100644
--- a/services/core/java/com/android/server/connectivity/NetworkNotificationManager.java
+++ b/services/core/java/com/android/server/connectivity/NetworkNotificationManager.java
@@ -16,13 +16,16 @@
 
 package com.android.server.connectivity;
 
+import static android.net.NetworkCapabilities.NET_CAPABILITY_INTERNET;
+import static android.net.NetworkCapabilities.TRANSPORT_CELLULAR;
+import static android.net.NetworkCapabilities.TRANSPORT_WIFI;
+
 import android.app.Notification;
 import android.app.NotificationManager;
 import android.app.PendingIntent;
 import android.content.Context;
 import android.content.Intent;
 import android.content.res.Resources;
-import android.net.NetworkCapabilities;
 import android.net.wifi.WifiInfo;
 import android.os.UserHandle;
 import android.telephony.TelephonyManager;
@@ -31,15 +34,12 @@
 import android.util.SparseArray;
 import android.util.SparseIntArray;
 import android.widget.Toast;
+
 import com.android.internal.R;
 import com.android.internal.annotations.VisibleForTesting;
 import com.android.internal.messages.nano.SystemMessageProto.SystemMessage;
 import com.android.internal.notification.SystemNotificationChannels;
 
-import static android.net.NetworkCapabilities.NET_CAPABILITY_INTERNET;
-import static android.net.NetworkCapabilities.TRANSPORT_CELLULAR;
-import static android.net.NetworkCapabilities.TRANSPORT_WIFI;
-
 public class NetworkNotificationManager {
 
 
@@ -47,7 +47,8 @@
         LOST_INTERNET(SystemMessage.NOTE_NETWORK_LOST_INTERNET),
         NETWORK_SWITCH(SystemMessage.NOTE_NETWORK_SWITCH),
         NO_INTERNET(SystemMessage.NOTE_NETWORK_NO_INTERNET),
-        SIGN_IN(SystemMessage.NOTE_NETWORK_SIGN_IN);
+        SIGN_IN(SystemMessage.NOTE_NETWORK_SIGN_IN),
+        LOGGED_IN(SystemMessage.NOTE_NETWORK_LOGGED_IN);
 
         public final int eventId;
 
@@ -192,6 +193,9 @@
                     details = r.getString(R.string.network_available_sign_in_detailed, name);
                     break;
             }
+        } else if (notifyType == NotificationType.LOGGED_IN) {
+            title = WifiInfo.removeDoubleQuotes(nai.networkCapabilities.getSSID());
+            details = r.getString(R.string.captive_portal_logged_in_detailed);
         } else if (notifyType == NotificationType.NETWORK_SWITCH) {
             String fromTransport = getTransportName(transportType);
             String toTransport = getTransportName(getFirstTransportType(switchToNai));
@@ -239,6 +243,18 @@
         }
     }
 
+    /**
+     * Clear the notification with the given id, only if it matches the given type.
+     */
+    public void clearNotification(int id, NotificationType notifyType) {
+        final int previousEventId = mNotificationTypeMap.get(id);
+        final NotificationType previousNotifyType = NotificationType.getFromId(previousEventId);
+        if (notifyType != previousNotifyType) {
+            return;
+        }
+        clearNotification(id);
+    }
+
     public void clearNotification(int id) {
         if (mNotificationTypeMap.indexOfKey(id) < 0) {
             return;
@@ -290,6 +306,10 @@
         return (t != null) ? t.name() : "UNKNOWN";
     }
 
+    /**
+     * A notification with a higher number will take priority over a notification with a lower
+     * number.
+     */
     private static int priority(NotificationType t) {
         if (t == null) {
             return 0;
@@ -302,6 +322,7 @@
             case NETWORK_SWITCH:
                 return 2;
             case LOST_INTERNET:
+            case LOGGED_IN:
                 return 1;
             default:
                 return 0;
diff --git a/services/core/java/com/android/server/connectivity/ProxyTracker.java b/services/core/java/com/android/server/connectivity/ProxyTracker.java
index fdddccd..a671287 100644
--- a/services/core/java/com/android/server/connectivity/ProxyTracker.java
+++ b/services/core/java/com/android/server/connectivity/ProxyTracker.java
@@ -309,22 +309,4 @@
             }
         }
     }
-
-    /**
-     * Enable or disable the default proxy.
-     *
-     * This sets the flag for enabling/disabling the default proxy and sends the broadcast
-     * if applicable.
-     * @param enabled whether the default proxy should be enabled.
-     */
-    public void setDefaultProxyEnabled(final boolean enabled) {
-        synchronized (mProxyLock) {
-            if (mDefaultProxyEnabled != enabled) {
-                mDefaultProxyEnabled = enabled;
-                if (mGlobalProxy == null && mDefaultProxy != null) {
-                    sendProxyBroadcast();
-                }
-            }
-        }
-    }
 }
diff --git a/services/core/java/com/android/server/connectivity/Vpn.java b/services/core/java/com/android/server/connectivity/Vpn.java
index c72c9dd..62a1b03 100644
--- a/services/core/java/com/android/server/connectivity/Vpn.java
+++ b/services/core/java/com/android/server/connectivity/Vpn.java
@@ -793,6 +793,8 @@
             }
         }
 
+        lp.setHttpProxy(mConfig.proxyInfo);
+
         if (!allowIPv4) {
             lp.addRoute(new RouteInfo(new IpPrefix(Inet4Address.ANY, 0), RTN_UNREACHABLE));
         }
diff --git a/services/core/java/com/android/server/content/ContentService.java b/services/core/java/com/android/server/content/ContentService.java
index 5ed6263..e268e44 100644
--- a/services/core/java/com/android/server/content/ContentService.java
+++ b/services/core/java/com/android/server/content/ContentService.java
@@ -48,7 +48,6 @@
 import android.os.Bundle;
 import android.os.FactoryTest;
 import android.os.IBinder;
-import android.os.Parcel;
 import android.os.Process;
 import android.os.RemoteException;
 import android.os.ResultReceiver;
@@ -255,21 +254,6 @@
         }
     }
 
-    @Override
-    public boolean onTransact(int code, Parcel data, Parcel reply, int flags)
-            throws RemoteException {
-        try {
-            return super.onTransact(code, data, reply, flags);
-        } catch (RuntimeException e) {
-            // The content service only throws security exceptions, so let's
-            // log all others.
-            if (!(e instanceof SecurityException)) {
-                Slog.wtf(TAG, "Content Service Crash", e);
-            }
-            throw e;
-        }
-    }
-
     /*package*/ ContentService(Context context, boolean factoryTest) {
         mContext = context;
         mFactoryTest = factoryTest;
diff --git a/services/core/java/com/android/server/display/ColorDisplayService.java b/services/core/java/com/android/server/display/ColorDisplayService.java
index 3a58160..eb0ed0a 100644
--- a/services/core/java/com/android/server/display/ColorDisplayService.java
+++ b/services/core/java/com/android/server/display/ColorDisplayService.java
@@ -16,6 +16,13 @@
 
 package com.android.server.display;
 
+import static android.hardware.display.ColorDisplayManager.AUTO_MODE_CUSTOM_TIME;
+import static android.hardware.display.ColorDisplayManager.AUTO_MODE_DISABLED;
+import static android.hardware.display.ColorDisplayManager.AUTO_MODE_TWILIGHT;
+import static android.hardware.display.ColorDisplayManager.COLOR_MODE_AUTOMATIC;
+import static android.hardware.display.ColorDisplayManager.COLOR_MODE_BOOSTED;
+import static android.hardware.display.ColorDisplayManager.COLOR_MODE_NATURAL;
+import static android.hardware.display.ColorDisplayManager.COLOR_MODE_SATURATED;
 import static com.android.server.display.DisplayTransformManager.LEVEL_COLOR_MATRIX_DISPLAY_WHITE_BALANCE;
 import static com.android.server.display.DisplayTransformManager.LEVEL_COLOR_MATRIX_NIGHT_DISPLAY;
 import static com.android.server.display.DisplayTransformManager.LEVEL_COLOR_MATRIX_SATURATION;
@@ -40,13 +47,17 @@
 import android.database.ContentObserver;
 import android.graphics.ColorSpace;
 import android.hardware.display.ColorDisplayManager;
+import android.hardware.display.ColorDisplayManager.AutoMode;
+import android.hardware.display.ColorDisplayManager.ColorMode;
 import android.hardware.display.IColorDisplayManager;
+import android.hardware.display.Time;
 import android.net.Uri;
 import android.opengl.Matrix;
 import android.os.Binder;
 import android.os.Handler;
 import android.os.Looper;
 import android.os.Message;
+import android.os.SystemProperties;
 import android.os.UserHandle;
 import android.provider.Settings.Secure;
 import android.provider.Settings.System;
@@ -55,7 +66,6 @@
 import android.view.SurfaceControl;
 import android.view.accessibility.AccessibilityManager;
 import android.view.animation.AnimationUtils;
-
 import com.android.internal.R;
 import com.android.internal.annotations.VisibleForTesting;
 import com.android.internal.app.ColorDisplayController;
@@ -65,7 +75,6 @@
 import com.android.server.twilight.TwilightListener;
 import com.android.server.twilight.TwilightManager;
 import com.android.server.twilight.TwilightState;
-
 import java.io.FileDescriptor;
 import java.io.PrintWriter;
 import java.lang.ref.WeakReference;
@@ -103,59 +112,17 @@
     private static final int MSG_APPLY_GLOBAL_SATURATION = 2;
 
     /**
+     * Return value if a setting has not been set.
+     */
+    private static final int NOT_SET = -1;
+
+    /**
      * Evaluator used to animate color matrix transitions.
      */
     private static final ColorMatrixEvaluator COLOR_MATRIX_EVALUATOR = new ColorMatrixEvaluator();
 
-    private final TintController mNightDisplayTintController = new TintController() {
-
-        private float[] mMatrixNightDisplay = new float[16];
-        private final float[] mColorTempCoefficients = new float[9];
-
-        /**
-         * Set coefficients based on whether the color matrix is linear or not.
-         */
-        @Override
-        public void setUp(Context context, boolean needsLinear) {
-            final String[] coefficients = context.getResources().getStringArray(needsLinear
-                    ? R.array.config_nightDisplayColorTemperatureCoefficients
-                    : R.array.config_nightDisplayColorTemperatureCoefficientsNative);
-            for (int i = 0; i < 9 && i < coefficients.length; i++) {
-                mColorTempCoefficients[i] = Float.parseFloat(coefficients[i]);
-            }
-        }
-
-        @Override
-        public void setMatrix(int cct) {
-            if (mMatrixNightDisplay.length != 16) {
-                Slog.d(TAG, "The display transformation matrix must be 4x4");
-                return;
-            }
-
-            Matrix.setIdentityM(mMatrixNightDisplay, 0);
-
-            final float squareTemperature = cct * cct;
-            final float red = squareTemperature * mColorTempCoefficients[0]
-                    + cct * mColorTempCoefficients[1] + mColorTempCoefficients[2];
-            final float green = squareTemperature * mColorTempCoefficients[3]
-                    + cct * mColorTempCoefficients[4] + mColorTempCoefficients[5];
-            final float blue = squareTemperature * mColorTempCoefficients[6]
-                    + cct * mColorTempCoefficients[7] + mColorTempCoefficients[8];
-            mMatrixNightDisplay[0] = red;
-            mMatrixNightDisplay[5] = green;
-            mMatrixNightDisplay[10] = blue;
-        }
-
-        @Override
-        public float[] getMatrix() {
-            return isActivated() ? mMatrixNightDisplay : MATRIX_IDENTITY;
-        }
-
-        @Override
-        public int getLevel() {
-            return LEVEL_COLOR_MATRIX_NIGHT_DISPLAY;
-        }
-    };
+    private final NightDisplayTintController mNightDisplayTintController =
+            new NightDisplayTintController();
 
     private final TintController mDisplayWhiteBalanceTintController = new TintController() {
         // Three chromaticity coordinates per color: X, Y, and Z
@@ -198,7 +165,7 @@
             }
 
             float[] displayRedGreenBlueXYZ =
-                new float[NUM_DISPLAY_PRIMARIES_VALS - NUM_VALUES_PER_PRIMARY];
+                    new float[NUM_DISPLAY_PRIMARIES_VALS - NUM_VALUES_PER_PRIMARY];
             float[] displayWhiteXYZ = new float[NUM_VALUES_PER_PRIMARY];
             for (int i = 0; i < displayRedGreenBlueXYZ.length; i++) {
                 displayRedGreenBlueXYZ[i] = Float.parseFloat(displayPrimariesValues[i]);
@@ -209,10 +176,10 @@
             }
 
             final ColorSpace.Rgb displayColorSpaceRGB = new ColorSpace.Rgb(
-                "Display Color Space",
-                displayRedGreenBlueXYZ,
-                displayWhiteXYZ,
-                2.2f // gamma, unused for display white balance
+                    "Display Color Space",
+                    displayRedGreenBlueXYZ,
+                    displayWhiteXYZ,
+                    2.2f // gamma, unused for display white balance
             );
 
             float[] displayNominalWhiteXYZ = new float[NUM_VALUES_PER_PRIMARY];
@@ -278,8 +245,8 @@
                 mCurrentColorTemperatureXYZ = ColorSpace.cctToIlluminantdXyz(cct);
 
                 mChromaticAdaptationMatrix =
-                    ColorSpace.chromaticAdaptation(ColorSpace.Adaptation.BRADFORD,
-                            mDisplayNominalWhiteXYZ, mCurrentColorTemperatureXYZ);
+                        ColorSpace.chromaticAdaptation(ColorSpace.Adaptation.BRADFORD,
+                                mDisplayNominalWhiteXYZ, mCurrentColorTemperatureXYZ);
 
                 // Convert the adaptation matrix to RGB space
                 float[] result = ColorSpace.mul3x3(mChromaticAdaptationMatrix,
@@ -386,7 +353,7 @@
                 float saturation = saturationLevel * 0.1f;
                 float desaturation = 1.0f - saturation;
                 float[] luminance = {0.231f * desaturation, 0.715f * desaturation,
-                    0.072f * desaturation};
+                        0.072f * desaturation};
                 mMatrixGlobalSaturation[0] = luminance[0] + saturation;
                 mMatrixGlobalSaturation[1] = luminance[0];
                 mMatrixGlobalSaturation[2] = luminance[0];
@@ -421,7 +388,7 @@
      * subtraction from 1. The last row represents a non-multiplied addition, see surfaceflinger's
      * ProgramCache for full implementation details.
      */
-    private static final float[] MATRIX_INVERT_COLOR = new float[] {
+    private static final float[] MATRIX_INVERT_COLOR = new float[]{
             0.402f, -0.598f, -0.599f, 0f,
             -1.174f, -0.174f, -1.175f, 0f,
             -0.228f, -0.228f, 0.772f, 0f,
@@ -445,7 +412,7 @@
 
     public ColorDisplayService(Context context) {
         super(context);
-        mHandler = new TintHandler(Looper.getMainLooper());
+        mHandler = new TintHandler(DisplayThread.get().getLooper());
     }
 
     @Override
@@ -548,23 +515,30 @@
                     if (setting != null) {
                         switch (setting) {
                             case Secure.NIGHT_DISPLAY_ACTIVATED:
-                                onNightDisplayActivated(mNightDisplayController.isActivated());
+                                final boolean activated = isNightDisplayActivatedSetting();
+                                if (mNightDisplayTintController.isActivatedStateNotSet()
+                                        || mNightDisplayTintController.isActivated() != activated) {
+                                    mNightDisplayTintController.onActivated(activated);
+                                }
                                 break;
                             case Secure.NIGHT_DISPLAY_COLOR_TEMPERATURE:
-                                onNightDisplayColorTemperatureChanged(
-                                        mNightDisplayController.getColorTemperature());
+                                final int temperature = getNightDisplayColorTemperatureSetting();
+                                if (mNightDisplayTintController.getColorTemperature()
+                                        != temperature) {
+                                    mNightDisplayTintController
+                                            .onColorTemperatureChanged(temperature);
+                                }
                                 break;
                             case Secure.NIGHT_DISPLAY_AUTO_MODE:
-                                onNightDisplayAutoModeChanged(
-                                        mNightDisplayController.getAutoMode());
+                                onNightDisplayAutoModeChanged(getNightDisplayAutoModeInternal());
                                 break;
                             case Secure.NIGHT_DISPLAY_CUSTOM_START_TIME:
                                 onNightDisplayCustomStartTimeChanged(
-                                        mNightDisplayController.getCustomStartTime());
+                                        getNightDisplayCustomStartTimeInternal().getLocalTime());
                                 break;
                             case Secure.NIGHT_DISPLAY_CUSTOM_END_TIME:
                                 onNightDisplayCustomEndTimeChanged(
-                                        mNightDisplayController.getCustomEndTime());
+                                        getNightDisplayCustomEndTimeInternal().getLocalTime());
                                 break;
                             case System.DISPLAY_COLOR_MODE:
                                 onDisplayColorModeChanged(mNightDisplayController.getColorMode());
@@ -624,14 +598,14 @@
             // Prepare the night display color transformation matrix.
             mNightDisplayTintController
                     .setUp(getContext(), DisplayTransformManager.needsLinearColorMatrix());
-            mNightDisplayTintController.setMatrix(mNightDisplayController.getColorTemperature());
+            mNightDisplayTintController.setMatrix(getNightDisplayColorTemperatureSetting());
 
             // Initialize the current auto mode.
-            onNightDisplayAutoModeChanged(mNightDisplayController.getAutoMode());
+            onNightDisplayAutoModeChanged(getNightDisplayAutoModeInternal());
 
-            // Force the initialization current activated state.
+            // Force the initialization of the current saved activation state.
             if (mNightDisplayTintController.isActivatedStateNotSet()) {
-                onNightDisplayActivated(mNightDisplayController.isActivated());
+                mNightDisplayTintController.onActivated(isNightDisplayActivatedSetting());
             }
         }
 
@@ -665,23 +639,6 @@
         }
     }
 
-    private void onNightDisplayActivated(boolean activated) {
-        if (mNightDisplayTintController.isActivatedStateNotSet()
-                || mNightDisplayTintController.isActivated() != activated) {
-            Slog.i(TAG, activated ? "Turning on night display" : "Turning off night display");
-
-            mNightDisplayTintController.setActivated(activated);
-
-            if (mNightDisplayAutoMode != null) {
-                mNightDisplayAutoMode.onActivated(activated);
-            }
-
-            updateDisplayWhiteBalanceStatus();
-
-            applyTint(mNightDisplayTintController, false);
-        }
-    }
-
     private void onNightDisplayAutoModeChanged(int autoMode) {
         Slog.d(TAG, "onNightDisplayAutoModeChanged: autoMode=" + autoMode);
 
@@ -690,9 +647,9 @@
             mNightDisplayAutoMode = null;
         }
 
-        if (autoMode == ColorDisplayController.AUTO_MODE_CUSTOM) {
+        if (autoMode == AUTO_MODE_CUSTOM_TIME) {
             mNightDisplayAutoMode = new CustomNightDisplayAutoMode();
-        } else if (autoMode == ColorDisplayController.AUTO_MODE_TWILIGHT) {
+        } else if (autoMode == AUTO_MODE_TWILIGHT) {
             mNightDisplayAutoMode = new TwilightNightDisplayAutoMode();
         }
 
@@ -717,13 +674,8 @@
         }
     }
 
-    private void onNightDisplayColorTemperatureChanged(int colorTemperature) {
-        mNightDisplayTintController.setMatrix(colorTemperature);
-        applyTint(mNightDisplayTintController, true);
-    }
-
     private void onDisplayColorModeChanged(int mode) {
-        if (mode == -1) {
+        if (mode == NOT_SET) {
             return;
         }
 
@@ -732,7 +684,7 @@
 
         mNightDisplayTintController
                 .setUp(getContext(), DisplayTransformManager.needsLinearColorMatrix(mode));
-        mNightDisplayTintController.setMatrix(mNightDisplayController.getColorTemperature());
+        mNightDisplayTintController.setMatrix(getNightDisplayColorTemperatureSetting());
 
         updateDisplayWhiteBalanceStatus();
 
@@ -901,6 +853,71 @@
         return availabilityFlags;
     }
 
+    private boolean setNightDisplayAutoModeInternal(@AutoMode int autoMode) {
+        if (getNightDisplayAutoModeInternal() != autoMode) {
+            Secure.putStringForUser(getContext().getContentResolver(),
+                    Secure.NIGHT_DISPLAY_LAST_ACTIVATED_TIME,
+                    null,
+                    mCurrentUser);
+        }
+        return Secure.putIntForUser(getContext().getContentResolver(),
+                Secure.NIGHT_DISPLAY_AUTO_MODE, autoMode, mCurrentUser);
+    }
+
+    private int getNightDisplayAutoModeInternal() {
+        int autoMode = getNightDisplayAutoModeRawInternal();
+        if (autoMode == NOT_SET) {
+            autoMode = getContext().getResources().getInteger(
+                    R.integer.config_defaultNightDisplayAutoMode);
+        }
+        if (autoMode != AUTO_MODE_DISABLED
+                && autoMode != AUTO_MODE_CUSTOM_TIME
+                && autoMode != AUTO_MODE_TWILIGHT) {
+            Slog.e(TAG, "Invalid autoMode: " + autoMode);
+            autoMode = AUTO_MODE_DISABLED;
+        }
+        return autoMode;
+    }
+
+    private int getNightDisplayAutoModeRawInternal() {
+        return Secure
+                .getIntForUser(getContext().getContentResolver(), Secure.NIGHT_DISPLAY_AUTO_MODE,
+                        NOT_SET, mCurrentUser);
+    }
+
+    private Time getNightDisplayCustomStartTimeInternal() {
+        int startTimeValue = Secure.getIntForUser(getContext().getContentResolver(),
+                Secure.NIGHT_DISPLAY_CUSTOM_START_TIME, NOT_SET, mCurrentUser);
+        if (startTimeValue == NOT_SET) {
+            startTimeValue = getContext().getResources().getInteger(
+                    R.integer.config_defaultNightDisplayCustomStartTime);
+        }
+        return new Time(LocalTime.ofSecondOfDay(startTimeValue / 1000));
+    }
+
+    private boolean setNightDisplayCustomStartTimeInternal(Time startTime) {
+        return Secure.putIntForUser(getContext().getContentResolver(),
+                Secure.NIGHT_DISPLAY_CUSTOM_START_TIME,
+                startTime.getLocalTime().toSecondOfDay() * 1000,
+                mCurrentUser);
+    }
+
+    private Time getNightDisplayCustomEndTimeInternal() {
+        int endTimeValue = Secure.getIntForUser(getContext().getContentResolver(),
+                Secure.NIGHT_DISPLAY_CUSTOM_END_TIME, NOT_SET, mCurrentUser);
+        if (endTimeValue == NOT_SET) {
+            endTimeValue = getContext().getResources().getInteger(
+                    R.integer.config_defaultNightDisplayCustomEndTime);
+        }
+        return new Time(LocalTime.ofSecondOfDay(endTimeValue / 1000));
+    }
+
+    private boolean setNightDisplayCustomEndTimeInternal(Time endTime) {
+        return Secure.putIntForUser(getContext().getContentResolver(),
+                Secure.NIGHT_DISPLAY_CUSTOM_END_TIME, endTime.getLocalTime().toSecondOfDay() * 1000,
+                mCurrentUser);
+    }
+
     /**
      * Returns the last time the night display transform activation state was changed, or {@link
      * LocalDateTime#MIN} if night display has never been activated.
@@ -930,6 +947,89 @@
                 .setSaturationLevel(packageName, mCurrentUser, saturationLevel);
     }
 
+    private void setColorModeInternal(@ColorMode int colorMode) {
+        if (!isColorModeAvailable(colorMode)) {
+            throw new IllegalArgumentException("Invalid colorMode: " + colorMode);
+        }
+        System.putIntForUser(getContext().getContentResolver(), System.DISPLAY_COLOR_MODE,
+                colorMode,
+                mCurrentUser);
+    }
+
+    private @ColorMode
+    int getColorModeInternal() {
+        final ContentResolver cr = getContext().getContentResolver();
+        if (Secure.getIntForUser(cr, Secure.ACCESSIBILITY_DISPLAY_INVERSION_ENABLED,
+                0, mCurrentUser) == 1
+                || Secure.getIntForUser(cr, Secure.ACCESSIBILITY_DISPLAY_DALTONIZER_ENABLED,
+                0, mCurrentUser) == 1) {
+            // There are restrictions on the available color modes combined with a11y transforms.
+            if (isColorModeAvailable(COLOR_MODE_SATURATED)) {
+                return COLOR_MODE_SATURATED;
+            } else if (isColorModeAvailable(COLOR_MODE_AUTOMATIC)) {
+                return COLOR_MODE_AUTOMATIC;
+            }
+        }
+
+        int colorMode = System.getIntForUser(cr, System.DISPLAY_COLOR_MODE, -1, mCurrentUser);
+        if (colorMode == -1) {
+            // There might be a system property controlling color mode that we need to respect; if
+            // not, this will set a suitable default.
+            colorMode = getCurrentColorModeFromSystemProperties();
+        }
+
+        // This happens when a color mode is no longer available (e.g., after system update or B&R)
+        // or the device does not support any color mode.
+        if (!isColorModeAvailable(colorMode)) {
+            if (colorMode == COLOR_MODE_BOOSTED && isColorModeAvailable(COLOR_MODE_NATURAL)) {
+                colorMode = COLOR_MODE_NATURAL;
+            } else if (colorMode == COLOR_MODE_SATURATED
+                    && isColorModeAvailable(COLOR_MODE_AUTOMATIC)) {
+                colorMode = COLOR_MODE_AUTOMATIC;
+            } else if (colorMode == COLOR_MODE_AUTOMATIC
+                    && isColorModeAvailable(COLOR_MODE_SATURATED)) {
+                colorMode = COLOR_MODE_SATURATED;
+            } else {
+                colorMode = -1;
+            }
+        }
+
+        return colorMode;
+    }
+
+    /**
+     * Get the current color mode from system properties, or return -1 if invalid.
+     *
+     * See {@link com.android.server.display.DisplayTransformManager}
+     */
+    private @ColorMode
+    int getCurrentColorModeFromSystemProperties() {
+        final int displayColorSetting = SystemProperties.getInt("persist.sys.sf.native_mode", 0);
+        if (displayColorSetting == 0) {
+            return "1.0".equals(SystemProperties.get("persist.sys.sf.color_saturation"))
+                    ? COLOR_MODE_NATURAL : COLOR_MODE_BOOSTED;
+        } else if (displayColorSetting == 1) {
+            return COLOR_MODE_SATURATED;
+        } else if (displayColorSetting == 2) {
+            return COLOR_MODE_AUTOMATIC;
+        } else {
+            return -1;
+        }
+    }
+
+    private boolean isColorModeAvailable(@ColorMode int colorMode) {
+        final int[] availableColorModes = getContext().getResources().getIntArray(
+                R.array.config_availableColorModes);
+        if (availableColorModes != null) {
+            for (int mode : availableColorModes) {
+                if (mode == colorMode) {
+                    return true;
+                }
+            }
+        }
+        return false;
+    }
+
     private void dumpInternal(PrintWriter pw) {
         pw.println("COLOR DISPLAY MANAGER dumpsys (color_display)");
         pw.println("Night Display:");
@@ -941,6 +1041,33 @@
         mAppSaturationController.dump(pw);
     }
 
+    private boolean isNightDisplayActivatedSetting() {
+        return Secure.getIntForUser(getContext().getContentResolver(),
+                Secure.NIGHT_DISPLAY_ACTIVATED, 0, mCurrentUser) == 1;
+    }
+
+    private int getNightDisplayColorTemperatureSetting() {
+        return clampNightDisplayColorTemperature(Secure.getIntForUser(
+                getContext().getContentResolver(), Secure.NIGHT_DISPLAY_COLOR_TEMPERATURE, NOT_SET,
+                mCurrentUser));
+    }
+
+    private int clampNightDisplayColorTemperature(int colorTemperature) {
+        if (colorTemperature == NOT_SET) {
+            colorTemperature = getContext().getResources().getInteger(
+                    R.integer.config_nightDisplayColorTemperatureDefault);
+        }
+        final int minimumTemperature = ColorDisplayManager.getMinimumColorTemperature(getContext());
+        final int maximumTemperature = ColorDisplayManager.getMaximumColorTemperature(getContext());
+        if (colorTemperature < minimumTemperature) {
+            colorTemperature = minimumTemperature;
+        } else if (colorTemperature > maximumTemperature) {
+            colorTemperature = maximumTemperature;
+        }
+
+        return colorTemperature;
+    }
+
     private abstract class NightDisplayAutoMode {
 
         public abstract void onActivated(boolean activated);
@@ -987,13 +1114,13 @@
                 // Maintain the existing activated state if within the current period.
                 if (mLastActivatedTime.isBefore(now) && mLastActivatedTime.isAfter(start)
                         && (mLastActivatedTime.isAfter(end) || now.isBefore(end))) {
-                    activate = mNightDisplayController.isActivated();
+                    activate = isNightDisplayActivatedSetting();
                 }
             }
 
             if (mNightDisplayTintController.isActivatedStateNotSet() || (
                     mNightDisplayTintController.isActivated() != activate)) {
-                mNightDisplayController.setActivated(activate);
+                mNightDisplayTintController.setActivated(activate);
             }
 
             updateNextAlarm(mNightDisplayTintController.isActivated(), now);
@@ -1014,8 +1141,8 @@
             intentFilter.addAction(Intent.ACTION_TIMEZONE_CHANGED);
             getContext().registerReceiver(mTimeChangedReceiver, intentFilter);
 
-            mStartTime = mNightDisplayController.getCustomStartTime();
-            mEndTime = mNightDisplayController.getCustomEndTime();
+            mStartTime = getNightDisplayCustomStartTimeInternal().getLocalTime();
+            mEndTime = getNightDisplayCustomEndTimeInternal().getLocalTime();
 
             mLastActivatedTime = getNightDisplayLastActivatedTimeSetting();
 
@@ -1083,13 +1210,13 @@
                 // Maintain the existing activated state if within the current period.
                 if (mLastActivatedTime.isBefore(now) && (mLastActivatedTime.isBefore(sunrise)
                         ^ mLastActivatedTime.isBefore(sunset))) {
-                    activate = mNightDisplayController.isActivated();
+                    activate = isNightDisplayActivatedSetting();
                 }
             }
 
             if (mNightDisplayTintController.isActivatedStateNotSet() || (
                     mNightDisplayTintController.isActivated() != activate)) {
-                mNightDisplayController.setActivated(activate);
+                mNightDisplayTintController.setActivated(activate);
             }
         }
 
@@ -1211,6 +1338,114 @@
         public abstract int getLevel();
     }
 
+    private final class NightDisplayTintController extends TintController {
+
+        private float[] mMatrix = new float[16];
+        private final float[] mColorTempCoefficients = new float[9];
+        private Integer mColorTemp;
+
+        /**
+         * Set coefficients based on whether the color matrix is linear or not.
+         */
+        @Override
+        public void setUp(Context context, boolean needsLinear) {
+            final String[] coefficients = context.getResources().getStringArray(needsLinear
+                    ? R.array.config_nightDisplayColorTemperatureCoefficients
+                    : R.array.config_nightDisplayColorTemperatureCoefficientsNative);
+            for (int i = 0; i < 9 && i < coefficients.length; i++) {
+                mColorTempCoefficients[i] = Float.parseFloat(coefficients[i]);
+            }
+        }
+
+        @Override
+        public void setMatrix(int cct) {
+            if (mMatrix.length != 16) {
+                Slog.d(TAG, "The display transformation matrix must be 4x4");
+                return;
+            }
+
+            Matrix.setIdentityM(mMatrix, 0);
+
+            final float squareTemperature = cct * cct;
+            final float red = squareTemperature * mColorTempCoefficients[0]
+                    + cct * mColorTempCoefficients[1] + mColorTempCoefficients[2];
+            final float green = squareTemperature * mColorTempCoefficients[3]
+                    + cct * mColorTempCoefficients[4] + mColorTempCoefficients[5];
+            final float blue = squareTemperature * mColorTempCoefficients[6]
+                    + cct * mColorTempCoefficients[7] + mColorTempCoefficients[8];
+            mMatrix[0] = red;
+            mMatrix[5] = green;
+            mMatrix[10] = blue;
+        }
+
+        @Override
+        public float[] getMatrix() {
+            return isActivated() ? mMatrix : MATRIX_IDENTITY;
+        }
+
+        @Override
+        public void setActivated(Boolean activated) {
+            if (activated == null) {
+                super.setActivated(null);
+                return;
+            }
+
+            boolean activationStateChanged = activated != isActivated();
+
+            if (!isActivatedStateNotSet() && activationStateChanged) {
+                // This is a true state change, so set this as the last activation time.
+                Secure.putStringForUser(getContext().getContentResolver(),
+                        Secure.NIGHT_DISPLAY_LAST_ACTIVATED_TIME,
+                        LocalDateTime.now().toString(),
+                        mCurrentUser);
+            }
+
+            if (isActivatedStateNotSet() || activationStateChanged) {
+                super.setActivated(activated);
+                Secure.putIntForUser(getContext().getContentResolver(),
+                        Secure.NIGHT_DISPLAY_ACTIVATED,
+                        activated ? 1 : 0, mCurrentUser);
+                onActivated(activated);
+            }
+        }
+
+        @Override
+        public int getLevel() {
+            return LEVEL_COLOR_MATRIX_NIGHT_DISPLAY;
+        }
+
+        void onActivated(boolean activated) {
+            Slog.i(TAG, activated ? "Turning on night display" : "Turning off night display");
+            if (mNightDisplayAutoMode != null) {
+                mNightDisplayAutoMode.onActivated(activated);
+            }
+
+            if (ColorDisplayManager.isDisplayWhiteBalanceAvailable(getContext())) {
+                updateDisplayWhiteBalanceStatus();
+            }
+
+            mHandler.sendEmptyMessage(MSG_APPLY_NIGHT_DISPLAY_ANIMATED);
+        }
+
+        int getColorTemperature() {
+            return mColorTemp != null ? clampNightDisplayColorTemperature(mColorTemp)
+                    : getNightDisplayColorTemperatureSetting();
+        }
+
+        boolean setColorTemperature(int temperature) {
+            mColorTemp = temperature;
+            final boolean success = Secure.putIntForUser(getContext().getContentResolver(),
+                    Secure.NIGHT_DISPLAY_COLOR_TEMPERATURE, temperature, mCurrentUser);
+            onColorTemperatureChanged(temperature);
+            return success;
+        }
+
+        void onColorTemperatureChanged(int temperature) {
+            setMatrix(temperature);
+            mHandler.sendEmptyMessage(MSG_APPLY_NIGHT_DISPLAY_IMMEDIATE);
+        }
+    }
+
     /**
      * Local service that allows color transforms to be enabled from other system services.
      */
@@ -1270,7 +1505,7 @@
 
     private final class TintHandler extends Handler {
 
-        TintHandler(Looper looper) {
+        private TintHandler(Looper looper) {
             super(looper, null, true /* async */);
         }
 
@@ -1281,6 +1516,12 @@
                     mGlobalSaturationTintController.setMatrix(msg.arg1);
                     applyTint(mGlobalSaturationTintController, false);
                     break;
+                case MSG_APPLY_NIGHT_DISPLAY_IMMEDIATE:
+                    applyTint(mNightDisplayTintController, true);
+                    break;
+                case MSG_APPLY_NIGHT_DISPLAY_ANIMATED:
+                    applyTint(mNightDisplayTintController, false);
+                    break;
             }
         }
     }
@@ -1290,11 +1531,37 @@
      */
     public interface ColorTransformController {
 
-        /** Apply the given saturation (grayscale) matrix to the associated AppWindow. */
+        /**
+         * Apply the given saturation (grayscale) matrix to the associated AppWindow.
+         */
         void applyAppSaturation(@Size(9) float[] matrix, @Size(3) float[] translation);
     }
 
-    private final class BinderService extends IColorDisplayManager.Stub {
+    @VisibleForTesting
+    final class BinderService extends IColorDisplayManager.Stub {
+
+        @Override
+        public void setColorMode(int colorMode) {
+            getContext().enforceCallingOrSelfPermission(
+                    Manifest.permission.CONTROL_DISPLAY_COLOR_TRANSFORMS,
+                    "Permission required to set display color mode");
+            final long token = Binder.clearCallingIdentity();
+            try {
+                setColorModeInternal(colorMode);
+            } finally {
+                Binder.restoreCallingIdentity(token);
+            }
+        }
+
+        @Override
+        public int getColorMode() {
+            final long token = Binder.clearCallingIdentity();
+            try {
+                return getColorModeInternal();
+            } finally {
+                Binder.restoreCallingIdentity(token);
+            }
+        }
 
         @Override
         public boolean isDeviceColorManaged() {
@@ -1354,8 +1621,139 @@
         }
 
         @Override
+        public boolean setNightDisplayActivated(boolean activated) {
+            getContext().enforceCallingOrSelfPermission(
+                    Manifest.permission.CONTROL_DISPLAY_COLOR_TRANSFORMS,
+                    "Permission required to set night display activated");
+            final long token = Binder.clearCallingIdentity();
+            try {
+                mNightDisplayTintController.setActivated(activated);
+                return true;
+            } finally {
+                Binder.restoreCallingIdentity(token);
+            }
+        }
+
+        @Override
+        public boolean isNightDisplayActivated() {
+            final long token = Binder.clearCallingIdentity();
+            try {
+                return mNightDisplayTintController.isActivated();
+            } finally {
+                Binder.restoreCallingIdentity(token);
+            }
+        }
+
+        @Override
+        public boolean setNightDisplayColorTemperature(int temperature) {
+            getContext().enforceCallingOrSelfPermission(
+                    Manifest.permission.CONTROL_DISPLAY_COLOR_TRANSFORMS,
+                    "Permission required to set night display temperature");
+            final long token = Binder.clearCallingIdentity();
+            try {
+                return mNightDisplayTintController.setColorTemperature(temperature);
+            } finally {
+                Binder.restoreCallingIdentity(token);
+            }
+        }
+
+        @Override
+        public int getNightDisplayColorTemperature() {
+            final long token = Binder.clearCallingIdentity();
+            try {
+                return mNightDisplayTintController.getColorTemperature();
+            } finally {
+                Binder.restoreCallingIdentity(token);
+            }
+        }
+
+        @Override
+        public boolean setNightDisplayAutoMode(int autoMode) {
+            getContext().enforceCallingOrSelfPermission(
+                    Manifest.permission.CONTROL_DISPLAY_COLOR_TRANSFORMS,
+                    "Permission required to set night display auto mode");
+            final long token = Binder.clearCallingIdentity();
+            try {
+                return setNightDisplayAutoModeInternal(autoMode);
+            } finally {
+                Binder.restoreCallingIdentity(token);
+            }
+        }
+
+        @Override
+        public int getNightDisplayAutoMode() {
+            getContext().enforceCallingOrSelfPermission(
+                    Manifest.permission.CONTROL_DISPLAY_COLOR_TRANSFORMS,
+                    "Permission required to get night display auto mode");
+            final long token = Binder.clearCallingIdentity();
+            try {
+                return getNightDisplayAutoModeInternal();
+            } finally {
+                Binder.restoreCallingIdentity(token);
+            }
+        }
+
+        @Override
+        public int getNightDisplayAutoModeRaw() {
+            final long token = Binder.clearCallingIdentity();
+            try {
+                return getNightDisplayAutoModeRawInternal();
+            } finally {
+                Binder.restoreCallingIdentity(token);
+            }
+        }
+
+        @Override
+        public boolean setNightDisplayCustomStartTime(Time startTime) {
+            getContext().enforceCallingOrSelfPermission(
+                    Manifest.permission.CONTROL_DISPLAY_COLOR_TRANSFORMS,
+                    "Permission required to set night display custom start time");
+            final long token = Binder.clearCallingIdentity();
+            try {
+                return setNightDisplayCustomStartTimeInternal(startTime);
+            } finally {
+                Binder.restoreCallingIdentity(token);
+            }
+        }
+
+        @Override
+        public Time getNightDisplayCustomStartTime() {
+            final long token = Binder.clearCallingIdentity();
+            try {
+                return getNightDisplayCustomStartTimeInternal();
+            } finally {
+                Binder.restoreCallingIdentity(token);
+            }
+        }
+
+        @Override
+        public boolean setNightDisplayCustomEndTime(Time endTime) {
+            getContext().enforceCallingOrSelfPermission(
+                    Manifest.permission.CONTROL_DISPLAY_COLOR_TRANSFORMS,
+                    "Permission required to set night display custom end time");
+            final long token = Binder.clearCallingIdentity();
+            try {
+                return setNightDisplayCustomEndTimeInternal(endTime);
+            } finally {
+                Binder.restoreCallingIdentity(token);
+            }
+        }
+
+        @Override
+        public Time getNightDisplayCustomEndTime() {
+            final long token = Binder.clearCallingIdentity();
+            try {
+                return getNightDisplayCustomEndTimeInternal();
+            } finally {
+                Binder.restoreCallingIdentity(token);
+            }
+        }
+
+        @Override
         public void dump(FileDescriptor fd, PrintWriter pw, String[] args) {
-            if (!DumpUtils.checkDumpPermission(getContext(), TAG, pw)) return;
+            if (!DumpUtils.checkDumpPermission(getContext(), TAG, pw)) {
+                return;
+            }
 
             final long token = Binder.clearCallingIdentity();
             try {
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/DisplayTransformManager.java b/services/core/java/com/android/server/display/DisplayTransformManager.java
index a5e9728..b1b7d3c 100644
--- a/services/core/java/com/android/server/display/DisplayTransformManager.java
+++ b/services/core/java/com/android/server/display/DisplayTransformManager.java
@@ -17,6 +17,7 @@
 package com.android.server.display;
 
 import android.app.ActivityTaskManager;
+import android.hardware.display.ColorDisplayManager;
 import android.opengl.Matrix;
 import android.os.IBinder;
 import android.os.Parcel;
@@ -27,7 +28,6 @@
 import android.util.SparseArray;
 
 import com.android.internal.annotations.GuardedBy;
-import com.android.internal.app.ColorDisplayController;
 
 import java.util.Arrays;
 
@@ -248,20 +248,20 @@
      * work in linear space.
      */
     public static boolean needsLinearColorMatrix(int colorMode) {
-        return colorMode != ColorDisplayController.COLOR_MODE_SATURATED;
+        return colorMode != ColorDisplayManager.COLOR_MODE_SATURATED;
     }
 
     public boolean setColorMode(int colorMode, float[] nightDisplayMatrix) {
-        if (colorMode == ColorDisplayController.COLOR_MODE_NATURAL) {
+        if (colorMode == ColorDisplayManager.COLOR_MODE_NATURAL) {
             applySaturation(COLOR_SATURATION_NATURAL);
             setDisplayColor(DISPLAY_COLOR_MANAGED);
-        } else if (colorMode == ColorDisplayController.COLOR_MODE_BOOSTED) {
+        } else if (colorMode == ColorDisplayManager.COLOR_MODE_BOOSTED) {
             applySaturation(COLOR_SATURATION_BOOSTED);
             setDisplayColor(DISPLAY_COLOR_MANAGED);
-        } else if (colorMode == ColorDisplayController.COLOR_MODE_SATURATED) {
+        } else if (colorMode == ColorDisplayManager.COLOR_MODE_SATURATED) {
             applySaturation(COLOR_SATURATION_NATURAL);
             setDisplayColor(DISPLAY_COLOR_UNMANAGED);
-        } else if (colorMode == ColorDisplayController.COLOR_MODE_AUTOMATIC) {
+        } else if (colorMode == ColorDisplayManager.COLOR_MODE_AUTOMATIC) {
             applySaturation(COLOR_SATURATION_NATURAL);
             setDisplayColor(DISPLAY_COLOR_ENHANCED);
         }
diff --git a/services/core/java/com/android/server/inputmethod/InputMethodManagerService.java b/services/core/java/com/android/server/inputmethod/InputMethodManagerService.java
index 3fd3945..99bed73 100644
--- a/services/core/java/com/android/server/inputmethod/InputMethodManagerService.java
+++ b/services/core/java/com/android/server/inputmethod/InputMethodManagerService.java
@@ -207,6 +207,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 +290,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 +308,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 +408,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
@@ -615,6 +624,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 +1374,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 +1407,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 +1696,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 +1752,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);
     }
@@ -2264,7 +2278,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 +2389,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();
@@ -2830,6 +2853,14 @@
                 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) {
@@ -3298,6 +3329,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 +3550,7 @@
                 try {
                     setEnabledSessionInMainThread(session);
                     session.method.startInput(startInputToken, inputContext, missingMethods,
-                            editorInfo, restarting);
+                            editorInfo, restarting, session.client.shouldPreRenderIme);
                 } catch (RemoteException e) {
                 }
                 args.recycle();
@@ -3551,6 +3608,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:
@@ -4421,6 +4504,7 @@
         @ShellCommandResult
         private int refreshDebugProperties() {
             DebugFlags.FLAG_OPTIMIZE_START_INPUT.refresh();
+            DebugFlags.FLAG_PRE_RENDER_IME_VIEWS.refresh();
             return ShellCommandResult.SUCCESS;
         }
 
@@ -4726,5 +4810,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/JobConcurrencyManager.java b/services/core/java/com/android/server/job/JobConcurrencyManager.java
index 4d9b5f5..bec1947 100644
--- a/services/core/java/com/android/server/job/JobConcurrencyManager.java
+++ b/services/core/java/com/android/server/job/JobConcurrencyManager.java
@@ -36,6 +36,7 @@
 import com.android.internal.util.IndentingPrintWriter;
 import com.android.internal.util.StatLogger;
 import com.android.server.job.JobSchedulerService.Constants;
+import com.android.server.job.JobSchedulerService.MaxJobCountsPerMemoryTrimLevel;
 import com.android.server.job.controllers.JobStatus;
 import com.android.server.job.controllers.StateController;
 
@@ -148,14 +149,14 @@
                 Slog.d(TAG, "Interactive: " + interactive);
             }
 
-            final long now = JobSchedulerService.sElapsedRealtimeClock.millis();
+            final long nowRealtime = JobSchedulerService.sElapsedRealtimeClock.millis();
             if (interactive) {
-                mLastScreenOnRealtime = now;
+                mLastScreenOnRealtime = nowRealtime;
                 mEffectiveInteractiveState = true;
 
                 mHandler.removeCallbacks(mRampUpForScreenOff);
             } else {
-                mLastScreenOffRealtime = now;
+                mLastScreenOffRealtime = nowRealtime;
 
                 // Set mEffectiveInteractiveState to false after the delay, when we may increase
                 // the concurrency.
@@ -232,38 +233,24 @@
     private void updateMaxCountsLocked() {
         refreshSystemStateLocked();
 
-        if (mEffectiveInteractiveState) {
-            // Screen on
-            switch (mLastMemoryTrimLevel) {
-                case ProcessStats.ADJ_MEM_FACTOR_MODERATE:
-                    mMaxJobCounts = mConstants.MAX_JOB_COUNTS_ON_MODERATE;
-                    break;
-                case ProcessStats.ADJ_MEM_FACTOR_LOW:
-                    mMaxJobCounts = mConstants.MAX_JOB_COUNTS_ON_LOW;
-                    break;
-                case ProcessStats.ADJ_MEM_FACTOR_CRITICAL:
-                    mMaxJobCounts = mConstants.MAX_JOB_COUNTS_ON_CRITICAL;
-                    break;
-                default:
-                    mMaxJobCounts = mConstants.MAX_JOB_COUNTS_ON_NORMAL;
-                    break;
-            }
-        } else {
-            // Screen off
-            switch (mLastMemoryTrimLevel) {
-                case ProcessStats.ADJ_MEM_FACTOR_MODERATE:
-                    mMaxJobCounts = mConstants.MAX_JOB_COUNTS_OFF_MODERATE;
-                    break;
-                case ProcessStats.ADJ_MEM_FACTOR_LOW:
-                    mMaxJobCounts = mConstants.MAX_JOB_COUNTS_OFF_LOW;
-                    break;
-                case ProcessStats.ADJ_MEM_FACTOR_CRITICAL:
-                    mMaxJobCounts = mConstants.MAX_JOB_COUNTS_OFF_CRITICAL;
-                    break;
-                default:
-                    mMaxJobCounts = mConstants.MAX_JOB_COUNTS_OFF_NORMAL;
-                    break;
-            }
+        final MaxJobCountsPerMemoryTrimLevel jobCounts = mEffectiveInteractiveState
+                ? mConstants.MAX_JOB_COUNTS_SCREEN_ON
+                : mConstants.MAX_JOB_COUNTS_SCREEN_OFF;
+
+
+        switch (mLastMemoryTrimLevel) {
+            case ProcessStats.ADJ_MEM_FACTOR_MODERATE:
+                mMaxJobCounts = jobCounts.moderate;
+                break;
+            case ProcessStats.ADJ_MEM_FACTOR_LOW:
+                mMaxJobCounts = jobCounts.low;
+                break;
+            case ProcessStats.ADJ_MEM_FACTOR_CRITICAL:
+                mMaxJobCounts = jobCounts.critical;
+                break;
+            default:
+                mMaxJobCounts = jobCounts.normal;
+                break;
         }
     }
 
@@ -303,7 +290,7 @@
 
         // Initialize the work variables and also count running jobs.
         mJobCountTracker.reset(
-                mMaxJobCounts.getTotalMax(),
+                mMaxJobCounts.getMaxTotal(),
                 mMaxJobCounts.getMaxBg(),
                 mMaxJobCounts.getMinBg());
 
@@ -482,10 +469,7 @@
     }
 
 
-    public void dumpLocked(IndentingPrintWriter pw) {
-        final long now = System.currentTimeMillis();
-        final long nowRealtime = JobSchedulerService.sElapsedRealtimeClock.millis();
-
+    public void dumpLocked(IndentingPrintWriter pw, long now, long nowRealtime) {
         pw.println("Concurrency:");
 
         pw.increaseIndent();
@@ -522,19 +506,36 @@
         }
     }
 
-    public void dumpProtoLocked(ProtoOutputStream proto) {
-        // TODO Implement it.
+    public void dumpProtoLocked(ProtoOutputStream proto, long tag, long now, long nowRealtime) {
+        final long token = proto.start(tag);
+
+        proto.write(JobConcurrencyManagerProto.CURRENT_INTERACTIVE,
+                mCurrentInteractiveState);
+        proto.write(JobConcurrencyManagerProto.EFFECTIVE_INTERACTIVE,
+                mEffectiveInteractiveState);
+
+        proto.write(JobConcurrencyManagerProto.TIME_SINCE_LAST_SCREEN_ON_MS,
+                nowRealtime - mLastScreenOnRealtime);
+        proto.write(JobConcurrencyManagerProto.TIME_SINCE_LAST_SCREEN_OFF_MS,
+                nowRealtime - mLastScreenOffRealtime);
+
+        mJobCountTracker.dumpProto(proto, JobConcurrencyManagerProto.JOB_COUNT_TRACKER);
+
+        proto.write(JobConcurrencyManagerProto.MEMORY_TRIM_LEVEL,
+                mLastMemoryTrimLevel);
+
+        proto.end(token);
     }
 
     /**
-     * This class decides, taking into account {@link #mMaxJobCounts} and how many jos are running /
+     * This class decides, taking into account {@link #mMaxJobCounts} and how mny jos are running /
      * pending, how many more job can start.
      *
      * Extracted for testing and logging.
      */
     @VisibleForTesting
     static class JobCountTracker {
-        private int mConfigNumTotalMaxJobs;
+        private int mConfigNumMaxTotalJobs;
         private int mConfigNumMaxBgJobs;
         private int mConfigNumMinBgJobs;
 
@@ -552,7 +553,7 @@
         private int mNumActualMaxBgJobs;
 
         void reset(int numTotalMaxJobs, int numMaxBgJobs, int numMinBgJobs) {
-            mConfigNumTotalMaxJobs = numTotalMaxJobs;
+            mConfigNumMaxTotalJobs = numTotalMaxJobs;
             mConfigNumMaxBgJobs = numMaxBgJobs;
             mConfigNumMinBgJobs = numMinBgJobs;
 
@@ -607,12 +608,12 @@
 
             // However, if there are FG jobs already running, we have to adjust it.
             mNumReservedForBg = Math.min(reservedForBg,
-                    mConfigNumTotalMaxJobs - mNumRunningFgJobs);
+                    mConfigNumMaxTotalJobs - mNumRunningFgJobs);
 
             // Max FG is [total - [number needed for BG jobs]]
             // [number needed for BG jobs] is the bigger one of [running BG] or [reserved BG]
             final int maxFg =
-                    mConfigNumTotalMaxJobs - Math.max(mNumRunningBgJobs, mNumReservedForBg);
+                    mConfigNumMaxTotalJobs - Math.max(mNumRunningBgJobs, mNumReservedForBg);
 
             // The above maxFg is the theoretical max. If there are less FG jobs, the actual
             // max FG will be lower accordingly.
@@ -623,7 +624,7 @@
             // Max BG is [total - actual max FG], but cap at [config max BG].
             final int maxBg = Math.min(
                     mConfigNumMaxBgJobs,
-                    mConfigNumTotalMaxJobs - mNumActualMaxFgJobs);
+                    mConfigNumMaxTotalJobs - mNumActualMaxFgJobs);
 
             // If there are less BG jobs than maxBg, then reduce the actual max BG accordingly.
             // This isn't needed for the logic to work, but this will give consistent output
@@ -669,12 +670,13 @@
             final int totalBg = mNumRunningBgJobs + mNumStartingBgJobs;
             return String.format(
                     "Config={tot=%d bg min/max=%d/%d}"
-                            + " Running: %d / %d (%d)"
+                            + " Running[FG/BG (total)]: %d / %d (%d)"
                             + " Pending: %d / %d (%d)"
                             + " Actual max: %d%s / %d%s (%d%s)"
+                            + " Res BG: %d"
                             + " Starting: %d / %d (%d)"
                             + " Total: %d%s / %d%s (%d%s)",
-                    mConfigNumTotalMaxJobs,
+                    mConfigNumMaxTotalJobs,
                     mConfigNumMinBgJobs,
                     mConfigNumMaxBgJobs,
 
@@ -684,19 +686,37 @@
                     mNumPendingFgJobs, mNumPendingBgJobs,
                     mNumPendingFgJobs + mNumPendingBgJobs,
 
-                    mNumActualMaxFgJobs, (totalFg <= mConfigNumTotalMaxJobs) ? "" : "*",
+                    mNumActualMaxFgJobs, (totalFg <= mConfigNumMaxTotalJobs) ? "" : "*",
                     mNumActualMaxBgJobs, (totalBg <= mConfigNumMaxBgJobs) ? "" : "*",
 
                     mNumActualMaxFgJobs + mNumActualMaxBgJobs,
-                    (mNumActualMaxFgJobs + mNumActualMaxBgJobs <= mConfigNumTotalMaxJobs)
+                    (mNumActualMaxFgJobs + mNumActualMaxBgJobs <= mConfigNumMaxTotalJobs)
                             ? "" : "*",
 
+                    mNumReservedForBg,
+
                     mNumStartingFgJobs, mNumStartingBgJobs, mNumStartingFgJobs + mNumStartingBgJobs,
 
                     totalFg, (totalFg <= mNumActualMaxFgJobs) ? "" : "*",
                     totalBg, (totalBg <= mNumActualMaxBgJobs) ? "" : "*",
-                    totalFg + totalBg, (totalFg + totalBg <= mConfigNumTotalMaxJobs) ? "" : "*"
+                    totalFg + totalBg, (totalFg + totalBg <= mConfigNumMaxTotalJobs) ? "" : "*"
             );
         }
+
+        public void dumpProto(ProtoOutputStream proto, long fieldId) {
+            final long token = proto.start(fieldId);
+
+            proto.write(JobCountTrackerProto.CONFIG_NUM_MAX_TOTAL_JOBS, mConfigNumMaxTotalJobs);
+            proto.write(JobCountTrackerProto.CONFIG_NUM_MAX_BG_JOBS, mConfigNumMaxBgJobs);
+            proto.write(JobCountTrackerProto.CONFIG_NUM_MIN_BG_JOBS, mConfigNumMinBgJobs);
+
+            proto.write(JobCountTrackerProto.NUM_RUNNING_FG_JOBS, mNumRunningFgJobs);
+            proto.write(JobCountTrackerProto.NUM_RUNNING_BG_JOBS, mNumRunningBgJobs);
+
+            proto.write(JobCountTrackerProto.NUM_PENDING_FG_JOBS, mNumPendingFgJobs);
+            proto.write(JobCountTrackerProto.NUM_PENDING_BG_JOBS, mNumPendingBgJobs);
+
+            proto.end(token);
+        }
     }
 }
diff --git a/services/core/java/com/android/server/job/JobSchedulerService.java b/services/core/java/com/android/server/job/JobSchedulerService.java
index bd12075..7625aaf 100644
--- a/services/core/java/com/android/server/job/JobSchedulerService.java
+++ b/services/core/java/com/android/server/job/JobSchedulerService.java
@@ -366,14 +366,20 @@
             }
         }
 
-        public int getTotalMax() {
+        /** Total number of jobs to run simultaneously. */
+        public int getMaxTotal() {
             return mTotal.getValue();
         }
 
+        /** Max number of BG (== owned by non-TOP apps) jobs to run simultaneously. */
         public int getMaxBg() {
             return mMaxBg.getValue();
         }
 
+        /**
+         * We try to run at least this many BG (== owned by non-TOP apps) jobs, when there are any
+         * pending, rather than always running the TOTAL number of FG jobs.
+         */
         public int getMinBg() {
             return mMinBg.getValue();
         }
@@ -384,10 +390,39 @@
             mMinBg.dump(pw, prefix);
         }
 
-        public void dumpProto(ProtoOutputStream proto, long tagTotal, long tagBg) {
-            mTotal.dumpProto(proto, tagTotal);
-            mMaxBg.dumpProto(proto, tagBg);
-            mMinBg.dumpProto(proto, tagBg);
+        public void dumpProto(ProtoOutputStream proto, long fieldId) {
+            final long token = proto.start(fieldId);
+            mTotal.dumpProto(proto, MaxJobCountsProto.TOTAL_JOBS);
+            mMaxBg.dumpProto(proto, MaxJobCountsProto.MAX_BG);
+            mMinBg.dumpProto(proto, MaxJobCountsProto.MIN_BG);
+            proto.end(token);
+        }
+    }
+
+    /** {@link MaxJobCounts} for each memory trim level. */
+    static class MaxJobCountsPerMemoryTrimLevel {
+        public final MaxJobCounts normal;
+        public final MaxJobCounts moderate;
+        public final MaxJobCounts low;
+        public final MaxJobCounts critical;
+
+        MaxJobCountsPerMemoryTrimLevel(
+                MaxJobCounts normal,
+                MaxJobCounts moderate, MaxJobCounts low,
+                MaxJobCounts critical) {
+            this.normal = normal;
+            this.moderate = moderate;
+            this.low = low;
+            this.critical = critical;
+        }
+
+        public void dumpProto(ProtoOutputStream proto, long fieldId) {
+            final long token = proto.start(fieldId);
+            normal.dumpProto(proto, MaxJobCountsPerMemoryTrimLevelProto.NORMAL);
+            moderate.dumpProto(proto, MaxJobCountsPerMemoryTrimLevelProto.MODERATE);
+            low.dumpProto(proto, MaxJobCountsPerMemoryTrimLevelProto.LOW);
+            critical.dumpProto(proto, MaxJobCountsPerMemoryTrimLevelProto.CRITICAL);
+            proto.end(token);
         }
     }
 
@@ -546,45 +581,44 @@
         float MODERATE_USE_FACTOR = DEFAULT_MODERATE_USE_FACTOR;
 
         // Max job counts for screen on / off, for each memory trim level.
-        final MaxJobCounts MAX_JOB_COUNTS_ON_NORMAL = new MaxJobCounts(
-                8, "max_job_total_on_normal",
-                6, "max_job_max_bg_on_normal",
-                2, "max_job_min_bg_on_normal");
+        final MaxJobCountsPerMemoryTrimLevel MAX_JOB_COUNTS_SCREEN_ON =
+                new MaxJobCountsPerMemoryTrimLevel(
+                        new MaxJobCounts(
+                                8, "max_job_total_on_normal",
+                                6, "max_job_max_bg_on_normal",
+                                2, "max_job_min_bg_on_normal"),
+                        new MaxJobCounts(
+                                8, "max_job_total_on_moderate",
+                                4, "max_job_max_bg_on_moderate",
+                                2, "max_job_min_bg_on_moderate"),
+                        new MaxJobCounts(
+                                5, "max_job_total_on_low",
+                                1, "max_job_max_bg_on_low",
+                                1, "max_job_min_bg_on_low"),
+                        new MaxJobCounts(
+                                5, "max_job_total_on_critical",
+                                1, "max_job_max_bg_on_critical",
+                                1, "max_job_min_bg_on_critical"));
 
-        final MaxJobCounts MAX_JOB_COUNTS_ON_MODERATE = new MaxJobCounts(
-                8, "max_job_total_on_moderate",
-                4, "max_job_max_bg_on_moderate",
-                2, "max_job_min_bg_on_moderate");
+        final MaxJobCountsPerMemoryTrimLevel MAX_JOB_COUNTS_SCREEN_OFF =
+                new MaxJobCountsPerMemoryTrimLevel(
+                        new MaxJobCounts(
+                                10, "max_job_total_off_normal",
+                                6, "max_job_max_bg_off_normal",
+                                2, "max_job_min_bg_off_normal"),
+                        new MaxJobCounts(
+                                10, "max_job_total_off_moderate",
+                                4, "max_job_max_bg_off_moderate",
+                                2, "max_job_min_bg_off_moderate"),
+                        new MaxJobCounts(
+                                5, "max_job_total_off_low",
+                                1, "max_job_max_bg_off_low",
+                                1, "max_job_min_bg_off_low"),
+                        new MaxJobCounts(
+                                5, "max_job_total_off_critical",
+                                1, "max_job_max_bg_off_critical",
+                                1, "max_job_min_bg_off_critical"));
 
-        final MaxJobCounts MAX_JOB_COUNTS_ON_LOW = new MaxJobCounts(
-                5, "max_job_total_on_low",
-                1, "max_job_max_bg_on_low",
-                1, "max_job_min_bg_on_low");
-
-        final MaxJobCounts MAX_JOB_COUNTS_ON_CRITICAL = new MaxJobCounts(
-                5, "max_job_total_on_critical",
-                1, "max_job_max_bg_on_critical",
-                1, "max_job_min_bg_on_critical");
-
-        final MaxJobCounts MAX_JOB_COUNTS_OFF_NORMAL = new MaxJobCounts(
-                10, "max_job_total_off_normal",
-                6, "max_job_max_bg_off_normal",
-                2, "max_job_min_bg_off_normal");
-
-        final MaxJobCounts MAX_JOB_COUNTS_OFF_MODERATE = new MaxJobCounts(
-                10, "max_job_total_off_moderate",
-                4, "max_job_max_bg_off_moderate",
-                2, "max_job_min_bg_off_moderate");
-
-        final MaxJobCounts MAX_JOB_COUNTS_OFF_LOW = new MaxJobCounts(
-                5, "max_job_total_off_low",
-                1, "max_job_max_bg_off_low",
-                1, "max_job_min_bg_off_low");
-
-        final MaxJobCounts MAX_JOB_COUNTS_OFF_CRITICAL = new MaxJobCounts(
-                5, "max_job_total_off_critical",
-                1, "max_job_max_bg_off_critical",
-                1, "max_job_min_bg_off_critical");
 
         /** Wait for this long after screen off before increasing the job concurrency. */
         final KeyValueListParser.IntValue SCREEN_OFF_JOB_CONCURRENCY_INCREASE_DELAY_MS =
@@ -766,15 +800,15 @@
             MODERATE_USE_FACTOR = mParser.getFloat(KEY_MODERATE_USE_FACTOR,
                     DEFAULT_MODERATE_USE_FACTOR);
 
-            MAX_JOB_COUNTS_ON_NORMAL.parse(mParser);
-            MAX_JOB_COUNTS_ON_MODERATE.parse(mParser);
-            MAX_JOB_COUNTS_ON_LOW.parse(mParser);
-            MAX_JOB_COUNTS_ON_CRITICAL.parse(mParser);
+            MAX_JOB_COUNTS_SCREEN_ON.normal.parse(mParser);
+            MAX_JOB_COUNTS_SCREEN_ON.moderate.parse(mParser);
+            MAX_JOB_COUNTS_SCREEN_ON.low.parse(mParser);
+            MAX_JOB_COUNTS_SCREEN_ON.critical.parse(mParser);
 
-            MAX_JOB_COUNTS_OFF_NORMAL.parse(mParser);
-            MAX_JOB_COUNTS_OFF_MODERATE.parse(mParser);
-            MAX_JOB_COUNTS_OFF_LOW.parse(mParser);
-            MAX_JOB_COUNTS_OFF_CRITICAL.parse(mParser);
+            MAX_JOB_COUNTS_SCREEN_OFF.normal.parse(mParser);
+            MAX_JOB_COUNTS_SCREEN_OFF.moderate.parse(mParser);
+            MAX_JOB_COUNTS_SCREEN_OFF.low.parse(mParser);
+            MAX_JOB_COUNTS_SCREEN_OFF.critical.parse(mParser);
 
             MAX_STANDARD_RESCHEDULE_COUNT = mParser.getInt(KEY_MAX_STANDARD_RESCHEDULE_COUNT,
                     DEFAULT_MAX_STANDARD_RESCHEDULE_COUNT);
@@ -851,15 +885,17 @@
             pw.printPair(KEY_HEAVY_USE_FACTOR, HEAVY_USE_FACTOR).println();
             pw.printPair(KEY_MODERATE_USE_FACTOR, MODERATE_USE_FACTOR).println();
 
-            MAX_JOB_COUNTS_ON_NORMAL.dump(pw, "");
-            MAX_JOB_COUNTS_ON_MODERATE.dump(pw, "");
-            MAX_JOB_COUNTS_ON_LOW.dump(pw, "");
-            MAX_JOB_COUNTS_ON_CRITICAL.dump(pw, "");
+            MAX_JOB_COUNTS_SCREEN_ON.normal.dump(pw, "");
+            MAX_JOB_COUNTS_SCREEN_ON.moderate.dump(pw, "");
+            MAX_JOB_COUNTS_SCREEN_ON.low.dump(pw, "");
+            MAX_JOB_COUNTS_SCREEN_ON.critical.dump(pw, "");
 
-            MAX_JOB_COUNTS_OFF_NORMAL.dump(pw, "");
-            MAX_JOB_COUNTS_OFF_MODERATE.dump(pw, "");
-            MAX_JOB_COUNTS_OFF_LOW.dump(pw, "");
-            MAX_JOB_COUNTS_OFF_CRITICAL.dump(pw, "");
+            MAX_JOB_COUNTS_SCREEN_OFF.normal.dump(pw, "");
+            MAX_JOB_COUNTS_SCREEN_OFF.moderate.dump(pw, "");
+            MAX_JOB_COUNTS_SCREEN_OFF.low.dump(pw, "");
+            MAX_JOB_COUNTS_SCREEN_OFF.critical.dump(pw, "");
+
+            SCREEN_OFF_JOB_CONCURRENCY_INCREASE_DELAY_MS.dump(pw, "");
 
             pw.printPair(KEY_MAX_STANDARD_RESCHEDULE_COUNT, MAX_STANDARD_RESCHEDULE_COUNT).println();
             pw.printPair(KEY_MAX_WORK_RESCHEDULE_COUNT, MAX_WORK_RESCHEDULE_COUNT).println();
@@ -917,7 +953,11 @@
             proto.write(ConstantsProto.HEAVY_USE_FACTOR, HEAVY_USE_FACTOR);
             proto.write(ConstantsProto.MODERATE_USE_FACTOR, MODERATE_USE_FACTOR);
 
-            // TODO Dump max job counts.
+            MAX_JOB_COUNTS_SCREEN_ON.dumpProto(proto, ConstantsProto.MAX_JOB_COUNTS_SCREEN_ON);
+            MAX_JOB_COUNTS_SCREEN_OFF.dumpProto(proto, ConstantsProto.MAX_JOB_COUNTS_SCREEN_OFF);
+
+            SCREEN_OFF_JOB_CONCURRENCY_INCREASE_DELAY_MS.dumpProto(proto,
+                    ConstantsProto.SCREEN_OFF_JOB_CONCURRENCY_INCREASE_DELAY_MS);
 
             proto.write(ConstantsProto.MAX_STANDARD_RESCHEDULE_COUNT, MAX_STANDARD_RESCHEDULE_COUNT);
             proto.write(ConstantsProto.MAX_WORK_RESCHEDULE_COUNT, MAX_WORK_RESCHEDULE_COUNT);
@@ -3371,8 +3411,10 @@
 
     void dumpInternal(final IndentingPrintWriter pw, int filterUid) {
         final int filterUidFinal = UserHandle.getAppId(filterUid);
+        final long now = sSystemClock.millis();
         final long nowElapsed = sElapsedRealtimeClock.millis();
         final long nowUptime = sUptimeMillisClock.millis();
+
         final Predicate<JobStatus> predicate = (js) -> {
             return filterUidFinal == -1 || UserHandle.getAppId(js.getUid()) == filterUidFinal
                     || UserHandle.getAppId(js.getSourceUid()) == filterUidFinal;
@@ -3548,7 +3590,7 @@
             }
             pw.println();
 
-            mConcurrencyManager.dumpLocked(pw);
+            mConcurrencyManager.dumpLocked(pw, now, nowElapsed);
 
             pw.println();
             pw.print("PersistStats: ");
@@ -3560,6 +3602,7 @@
     void dumpInternalProto(final FileDescriptor fd, int filterUid) {
         ProtoOutputStream proto = new ProtoOutputStream(fd);
         final int filterUidFinal = UserHandle.getAppId(filterUid);
+        final long now = sSystemClock.millis();
         final long nowElapsed = sElapsedRealtimeClock.millis();
         final long nowUptime = sUptimeMillisClock.millis();
         final Predicate<JobStatus> predicate = (js) -> {
@@ -3703,7 +3746,8 @@
                 proto.write(JobSchedulerServiceDumpProto.IS_READY_TO_ROCK, mReadyToRock);
                 proto.write(JobSchedulerServiceDumpProto.REPORTED_ACTIVE, mReportedActive);
             }
-            mConcurrencyManager.dumpProtoLocked(proto);
+            mConcurrencyManager.dumpProtoLocked(proto,
+                    JobSchedulerServiceDumpProto.CONCURRENCY_MANAGER, now, nowElapsed);
         }
 
         proto.flush();
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..dd26a29 100644
--- a/services/core/java/com/android/server/media/MediaSessionServiceImpl.java
+++ b/services/core/java/com/android/server/media/MediaSessionServiceImpl.java
@@ -42,8 +42,6 @@
 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;
@@ -60,7 +58,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,17 +1004,50 @@
                 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 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);
             }
@@ -2114,30 +2144,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 47a5597..de3f50a 100644
--- a/services/core/java/com/android/server/notification/NotificationManagerService.java
+++ b/services/core/java/com/android/server/notification/NotificationManagerService.java
@@ -845,10 +845,12 @@
                         reportSeen(r);
                     }
                     r.setVisibility(true, nv.rank, nv.count);
+                    boolean isHun = (nv.location
+                            == NotificationVisibility.NotificationLocation.LOCATION_FIRST_HEADS_UP);
                     // hasBeenVisiblyExpanded must be called after updating the expansion state of
                     // the NotificationRecord to ensure the expansion state is up-to-date.
-                    if (r.hasBeenVisiblyExpanded()) {
-                        logSmartSuggestionsVisible(r);
+                    if (isHun || r.hasBeenVisiblyExpanded()) {
+                        logSmartSuggestionsVisible(r, nv.location.toMetricsEventEnum());
                     }
                     maybeRecordInterruptionLocked(r);
                     nv.recycle();
@@ -876,7 +878,7 @@
                     // hasBeenVisiblyExpanded must be called after updating the expansion state of
                     // the NotificationRecord to ensure the expansion state is up-to-date.
                     if (r.hasBeenVisiblyExpanded()) {
-                        logSmartSuggestionsVisible(r);
+                        logSmartSuggestionsVisible(r, notificationLocation);
                     }
                     if (userAction) {
                         MetricsLogger.action(r.getItemLogMaker()
@@ -952,7 +954,7 @@
     };
 
     @VisibleForTesting
-    void logSmartSuggestionsVisible(NotificationRecord r) {
+    void logSmartSuggestionsVisible(NotificationRecord r, int notificationLocation) {
         // If the newly visible notification has smart suggestions
         // then log that the user has seen them.
         if ((r.getNumSmartRepliesAdded() > 0 || r.getNumSmartActionsAdded() > 0)
@@ -966,7 +968,10 @@
                             r.getNumSmartActionsAdded())
                     .addTaggedData(
                             MetricsEvent.NOTIFICATION_SMART_SUGGESTION_ASSISTANT_GENERATED,
-                            r.getSuggestionsGeneratedByAssistant() ? 1 : 0);
+                            r.getSuggestionsGeneratedByAssistant() ? 1 : 0)
+                    // The fields in the NotificationVisibility.NotificationLocation enum map
+                    // directly to the fields in the MetricsEvent.NotificationLocation enum.
+                    .addTaggedData(MetricsEvent.NOTIFICATION_LOCATION, notificationLocation);
             mMetricsLogger.write(logMaker);
         }
     }
@@ -1082,7 +1087,8 @@
                     || (queryRestart=action.equals(Intent.ACTION_QUERY_PACKAGE_RESTART))
                     || action.equals(Intent.ACTION_EXTERNAL_APPLICATIONS_UNAVAILABLE)
                     || action.equals(Intent.ACTION_PACKAGES_SUSPENDED)
-                    || action.equals(Intent.ACTION_PACKAGES_UNSUSPENDED)) {
+                    || action.equals(Intent.ACTION_PACKAGES_UNSUSPENDED)
+                    || action.equals(Intent.ACTION_DISTRACTING_PACKAGES_CHANGED)) {
                 int changeUserId = intent.getIntExtra(Intent.EXTRA_USER_HANDLE,
                         UserHandle.USER_ALL);
                 String pkgList[] = null;
@@ -1103,6 +1109,23 @@
                     uidList = intent.getIntArrayExtra(Intent.EXTRA_CHANGED_UID_LIST);
                     cancelNotifications = false;
                     unhideNotifications = true;
+                } else if (action.equals(Intent.ACTION_DISTRACTING_PACKAGES_CHANGED)) {
+                    final int distractionRestrictions =
+                            intent.getIntExtra(Intent.EXTRA_DISTRACTION_RESTRICTIONS,
+                                    PackageManager.RESTRICTION_NONE);
+                    if ((distractionRestrictions
+                            & PackageManager.RESTRICTION_HIDE_NOTIFICATIONS) != 0) {
+                        pkgList = intent.getStringArrayExtra(Intent.EXTRA_CHANGED_PACKAGE_LIST);
+                        uidList = intent.getIntArrayExtra(Intent.EXTRA_CHANGED_UID_LIST);
+                        cancelNotifications = false;
+                        hideNotifications = true;
+                    } else {
+                        pkgList = intent.getStringArrayExtra(Intent.EXTRA_CHANGED_PACKAGE_LIST);
+                        uidList = intent.getIntArrayExtra(Intent.EXTRA_CHANGED_UID_LIST);
+                        cancelNotifications = false;
+                        unhideNotifications = true;
+                    }
+
                 } else if (queryRestart) {
                     pkgList = intent.getStringArrayExtra(Intent.EXTRA_PACKAGES);
                     uidList = new int[] {intent.getIntExtra(Intent.EXTRA_UID, -1)};
@@ -1646,6 +1669,7 @@
         IntentFilter suspendedPkgFilter = new IntentFilter();
         suspendedPkgFilter.addAction(Intent.ACTION_PACKAGES_SUSPENDED);
         suspendedPkgFilter.addAction(Intent.ACTION_PACKAGES_UNSUSPENDED);
+        suspendedPkgFilter.addAction(Intent.ACTION_DISTRACTING_PACKAGES_CHANGED);
         getContext().registerReceiverAsUser(mPackageIntentReceiver, UserHandle.ALL,
                 suspendedPkgFilter, null, null);
 
@@ -7738,6 +7762,20 @@
         mPackageIntentReceiver.onReceive(getContext(), intent);
     }
 
+    @VisibleForTesting
+    protected void simulatePackageDistractionBroadcast(int flag, String[] pkgs) {
+        // only use for testing: mimic receive broadcast that package is (un)distracting
+        // but does not actually register that info with packagemanager
+        final Bundle extras = new Bundle();
+        extras.putStringArray(Intent.EXTRA_CHANGED_PACKAGE_LIST, pkgs);
+        extras.putInt(Intent.EXTRA_DISTRACTION_RESTRICTIONS, flag);
+
+        final Intent intent = new Intent(Intent.ACTION_DISTRACTING_PACKAGES_CHANGED);
+        intent.putExtras(extras);
+
+        mPackageIntentReceiver.onReceive(getContext(), intent);
+    }
+
     /**
      * Wrapper for a StatusBarNotification object that allows transfer across a oneway
      * binder without sending large amounts of data over a oneway transaction.
diff --git a/services/core/java/com/android/server/notification/NotificationRecord.java b/services/core/java/com/android/server/notification/NotificationRecord.java
index 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/pm/BackgroundDexOptService.java b/services/core/java/com/android/server/pm/BackgroundDexOptService.java
index 65fc982..ad9ac12 100644
--- a/services/core/java/com/android/server/pm/BackgroundDexOptService.java
+++ b/services/core/java/com/android/server/pm/BackgroundDexOptService.java
@@ -50,6 +50,7 @@
 import java.util.Set;
 import java.util.concurrent.TimeUnit;
 import java.util.concurrent.atomic.AtomicBoolean;
+import java.util.function.Supplier;
 
 /**
  * {@hide}
@@ -57,7 +58,7 @@
 public class BackgroundDexOptService extends JobService {
     private static final String TAG = "BackgroundDexOptService";
 
-    private static final boolean DEBUG = false;
+    private static final boolean DEBUG = Log.isLoggable(TAG, Log.DEBUG);
 
     private static final int JOB_IDLE_OPTIMIZE = 800;
     private static final int JOB_POST_BOOT_UPDATE = 801;
@@ -102,7 +103,6 @@
     private final AtomicBoolean mExitPostBootUpdate = new AtomicBoolean(false);
 
     private final File mDataDir = Environment.getDataDirectory();
-
     private static final long mDowngradeUnusedAppsThresholdInMillis =
             getDowngradeUnusedAppsThresholdInMillis();
 
@@ -275,21 +275,18 @@
 
         long lowStorageThreshold = getLowStorageThreshold(context);
         // Optimize primary apks.
-        int result = optimizePackages(pm, pkgs, lowStorageThreshold, /*is_for_primary_dex*/ true,
-                sFailedPackageNamesPrimary);
-
+        int result = optimizePackages(pm, pkgs, lowStorageThreshold,
+            /*isForPrimaryDex=*/ true);
         if (result == OPTIMIZE_ABORT_BY_JOB_SCHEDULER) {
             return result;
         }
-
-        if (SystemProperties.getBoolean("dalvik.vm.dexopt.secondary", false)) {
+        if (supportSecondaryDex()) {
             result = reconcileSecondaryDexFiles(pm.getDexManager());
             if (result == OPTIMIZE_ABORT_BY_JOB_SCHEDULER) {
                 return result;
             }
-
-            result = optimizePackages(pm, pkgs, lowStorageThreshold, /*is_for_primary_dex*/ false,
-                    sFailedPackageNamesSecondary);
+            result = optimizePackages(pm, pkgs, lowStorageThreshold,
+                /*isForPrimaryDex=*/ false);
         }
         return result;
     }
@@ -339,94 +336,86 @@
     }
 
     private int optimizePackages(PackageManagerService pm, ArraySet<String> pkgs,
-            long lowStorageThreshold, boolean is_for_primary_dex,
-            ArraySet<String> failedPackageNames) {
+            long lowStorageThreshold, boolean isForPrimaryDex) {
         ArraySet<String> updatedPackages = new ArraySet<>();
         Set<String> unusedPackages = pm.getUnusedPackages(mDowngradeUnusedAppsThresholdInMillis);
+        Log.d(TAG, "Unsused Packages " +  String.join(",", unusedPackages));
         // Only downgrade apps when space is low on device.
         // Threshold is selected above the lowStorageThreshold so that we can pro-actively clean
         // up disk before user hits the actual lowStorageThreshold.
         final long lowStorageThresholdForDowngrade = LOW_THRESHOLD_MULTIPLIER_FOR_DOWNGRADE *
                 lowStorageThreshold;
         boolean shouldDowngrade = shouldDowngrade(lowStorageThresholdForDowngrade);
+        Log.d(TAG, "Should Downgrade " + shouldDowngrade);
+        boolean dex_opt_performed = false;
         for (String pkg : pkgs) {
             int abort_code = abortIdleOptimizations(lowStorageThreshold);
             if (abort_code == OPTIMIZE_ABORT_BY_JOB_SCHEDULER) {
                 return abort_code;
             }
-
-            synchronized (failedPackageNames) {
-                if (failedPackageNames.contains(pkg)) {
-                    // Skip previously failing package
-                    continue;
-                }
-            }
-
-            int reason;
-            boolean downgrade;
-            long package_size_before = 0; //used when the app is downgraded
             // Downgrade unused packages.
             if (unusedPackages.contains(pkg) && shouldDowngrade) {
-                package_size_before = getPackageSize(pm, pkg);
-                // This applies for system apps or if packages location is not a directory, i.e.
-                // monolithic install.
-                if (is_for_primary_dex && !pm.canHaveOatDir(pkg)) {
-                    // For apps that don't have the oat directory, instead of downgrading,
-                    // remove their compiler artifacts from dalvik cache.
-                    pm.deleteOatArtifactsOfPackage(pkg);
+                dex_opt_performed = downgradePackage(pm, pkg, isForPrimaryDex);
+            } else {
+                if (abort_code == OPTIMIZE_ABORT_NO_SPACE_LEFT) {
+                    // can't dexopt because of low space.
                     continue;
-                } else {
-                    reason = PackageManagerService.REASON_INACTIVE_PACKAGE_DOWNGRADE;
-                    downgrade = true;
                 }
-            } else if (abort_code != OPTIMIZE_ABORT_NO_SPACE_LEFT) {
-                reason = PackageManagerService.REASON_BACKGROUND_DEXOPT;
-                downgrade = false;
-            } else {
-                // can't dexopt because of low space.
-                continue;
+                dex_opt_performed = optimizePackage(pm, pkg, isForPrimaryDex);
             }
-
-            synchronized (failedPackageNames) {
-                // Conservatively add package to the list of failing ones in case
-                // performDexOpt never returns.
-                failedPackageNames.add(pkg);
-            }
-
-            // Optimize package if needed. Note that there can be no race between
-            // concurrent jobs because PackageDexOptimizer.performDexOpt is synchronized.
-            boolean success;
-            int dexoptFlags =
-                    DexoptOptions.DEXOPT_CHECK_FOR_PROFILES_UPDATES |
-                    DexoptOptions.DEXOPT_BOOT_COMPLETE |
-                    (downgrade ? DexoptOptions.DEXOPT_DOWNGRADE : 0) |
-                    DexoptOptions.DEXOPT_IDLE_BACKGROUND_JOB;
-            if (is_for_primary_dex) {
-                int result = pm.performDexOptWithStatus(new DexoptOptions(pkg, reason,
-                        dexoptFlags));
-                success = result != PackageDexOptimizer.DEX_OPT_FAILED;
-                if (result == PackageDexOptimizer.DEX_OPT_PERFORMED) {
-                    updatedPackages.add(pkg);
-                }
-            } else {
-                success = pm.performDexOpt(new DexoptOptions(pkg,
-                        reason, dexoptFlags | DexoptOptions.DEXOPT_ONLY_SECONDARY_DEX));
-            }
-            if (success) {
-                // Dexopt succeeded, remove package from the list of failing ones.
-                synchronized (failedPackageNames) {
-                    failedPackageNames.remove(pkg);
-                }
-                if (downgrade) {
-                    StatsLog.write(StatsLog.APP_DOWNGRADED, pkg, package_size_before,
-                            getPackageSize(pm, pkg), /*aggressive=*/ false);
-                }
+            if (dex_opt_performed) {
+                updatedPackages.add(pkg);
             }
         }
+
         notifyPinService(updatedPackages);
         return OPTIMIZE_PROCESSED;
     }
 
+
+    /**
+     * Try to downgrade the package to a smaller compilation filter.
+     * eg. if the package is in speed-profile the package will be downgraded to verify.
+     * @param pm PackageManagerService
+     * @param pkg The package to be downgraded.
+     * @param isForPrimaryDex. Apps can have several dex file, primary and secondary.
+     * @return true if the package was downgraded.
+     */
+    private boolean downgradePackage(PackageManagerService pm, String pkg,
+            boolean isForPrimaryDex) {
+        Log.d(TAG, "Downgrading " + pkg);
+        boolean dex_opt_performed = false;
+        int reason = PackageManagerService.REASON_INACTIVE_PACKAGE_DOWNGRADE;
+        int dexoptFlags = DexoptOptions.DEXOPT_BOOT_COMPLETE
+                | DexoptOptions.DEXOPT_IDLE_BACKGROUND_JOB
+                | DexoptOptions.DEXOPT_DOWNGRADE;
+        long package_size_before = getPackageSize(pm, pkg);
+
+        if (isForPrimaryDex) {
+            // This applies for system apps or if packages location is not a directory, i.e.
+            // monolithic install.
+            if (!pm.canHaveOatDir(pkg)) {
+                // For apps that don't have the oat directory, instead of downgrading,
+                // remove their compiler artifacts from dalvik cache.
+                pm.deleteOatArtifactsOfPackage(pkg);
+            } else {
+                dex_opt_performed = performDexOptPrimary(pm, pkg, reason, dexoptFlags);
+            }
+        } else {
+            dex_opt_performed = performDexOptSecondary(pm, pkg, reason, dexoptFlags);
+        }
+
+        if (dex_opt_performed) {
+            StatsLog.write(StatsLog.APP_DOWNGRADED, pkg, package_size_before,
+                    getPackageSize(pm, pkg), /*aggressive=*/ false);
+        }
+        return dex_opt_performed;
+    }
+
+    private boolean supportSecondaryDex() {
+        return (SystemProperties.getBoolean("dalvik.vm.dexopt.secondary", false));
+    }
+
     private int reconcileSecondaryDexFiles(DexManager dm) {
         // TODO(calin): should we blacklist packages for which we fail to reconcile?
         for (String p : dm.getAllPackagesWithSecondaryDexFiles()) {
@@ -438,6 +427,73 @@
         return OPTIMIZE_PROCESSED;
     }
 
+    /**
+     *
+     * Optimize package if needed. Note that there can be no race between
+     * concurrent jobs because PackageDexOptimizer.performDexOpt is synchronized.
+     * @param pm An instance of PackageManagerService
+     * @param pkg The package to be downgraded.
+     * @param isForPrimaryDex. Apps can have several dex file, primary and secondary.
+     * @return true if the package was downgraded.
+     */
+    private boolean optimizePackage(PackageManagerService pm, String pkg,
+            boolean isForPrimaryDex) {
+        int reason = PackageManagerService.REASON_BACKGROUND_DEXOPT;
+        int dexoptFlags = DexoptOptions.DEXOPT_CHECK_FOR_PROFILES_UPDATES
+                | DexoptOptions.DEXOPT_BOOT_COMPLETE
+                | DexoptOptions.DEXOPT_IDLE_BACKGROUND_JOB;
+
+        return isForPrimaryDex
+            ? performDexOptPrimary(pm, pkg, reason, dexoptFlags)
+            : performDexOptSecondary(pm, pkg, reason, dexoptFlags);
+    }
+
+    private boolean performDexOptPrimary(PackageManagerService pm, String pkg, int reason,
+            int dexoptFlags) {
+        int result = trackPerformDexOpt(pkg, /*isForPrimaryDex=*/ false,
+                () -> pm.performDexOptWithStatus(new DexoptOptions(pkg, reason, dexoptFlags)));
+        return result == PackageDexOptimizer.DEX_OPT_PERFORMED;
+    }
+
+    private boolean performDexOptSecondary(PackageManagerService pm, String pkg, int reason,
+            int dexoptFlags) {
+        DexoptOptions dexoptOptions = new DexoptOptions(pkg, reason,
+                dexoptFlags | DexoptOptions.DEXOPT_ONLY_SECONDARY_DEX);
+        int result = trackPerformDexOpt(pkg, /*isForPrimaryDex=*/ true,
+                () -> pm.performDexOpt(dexoptOptions)
+                    ? PackageDexOptimizer.DEX_OPT_PERFORMED : PackageDexOptimizer.DEX_OPT_FAILED
+        );
+        return result == PackageDexOptimizer.DEX_OPT_PERFORMED;
+    }
+
+    /**
+     * Execute the dexopt wrapper and make sure that if performDexOpt wrapper fails
+     * the package is added to the list of failed packages.
+     * Return one of following result:
+     *  {@link PackageDexOptimizer#DEX_OPT_SKIPPED}
+     *  {@link PackageDexOptimizer#DEX_OPT_PERFORMED}
+     *  {@link PackageDexOptimizer#DEX_OPT_FAILED}
+     */
+    private int trackPerformDexOpt(String pkg, boolean isForPrimaryDex,
+            Supplier<Integer> performDexOptWrapper) {
+        ArraySet<String> sFailedPackageNames =
+                isForPrimaryDex ? sFailedPackageNamesPrimary : sFailedPackageNamesSecondary;
+        synchronized (sFailedPackageNames) {
+            if (sFailedPackageNames.contains(pkg)) {
+                // Skip previously failing package
+                return PackageDexOptimizer.DEX_OPT_SKIPPED;
+            }
+            sFailedPackageNames.add(pkg);
+        }
+        int result = performDexOptWrapper.get();
+        if (result != PackageDexOptimizer.DEX_OPT_FAILED) {
+            synchronized (sFailedPackageNames) {
+                sFailedPackageNames.remove(pkg);
+            }
+        }
+        return result;
+    }
+
     // Evaluate whether or not idle optimizations should continue.
     private int abortIdleOptimizations(long lowStorageThreshold) {
         if (mAbortIdleOptimization.get()) {
diff --git a/services/core/java/com/android/server/pm/LauncherAppsService.java b/services/core/java/com/android/server/pm/LauncherAppsService.java
index a33f14b..d0ef4f1 100644
--- a/services/core/java/com/android/server/pm/LauncherAppsService.java
+++ b/services/core/java/com/android/server/pm/LauncherAppsService.java
@@ -25,6 +25,7 @@
 import android.app.IApplicationThread;
 import android.app.PendingIntent;
 import android.app.admin.DevicePolicyManager;
+import android.app.usage.UsageStatsManagerInternal;
 import android.content.ComponentName;
 import android.content.Context;
 import android.content.Intent;
@@ -135,6 +136,7 @@
         private final Context mContext;
         private final UserManager mUm;
         private final UserManagerInternal mUserManagerInternal;
+        private final UsageStatsManagerInternal mUsageStatsManagerInternal;
         private final ActivityManagerInternal mActivityManagerInternal;
         private final ActivityTaskManagerInternal mActivityTaskManagerInternal;
         private final ShortcutServiceInternal mShortcutServiceInternal;
@@ -156,6 +158,8 @@
             mUm = (UserManager) mContext.getSystemService(Context.USER_SERVICE);
             mUserManagerInternal = Preconditions.checkNotNull(
                     LocalServices.getService(UserManagerInternal.class));
+            mUsageStatsManagerInternal = Preconditions.checkNotNull(
+                    LocalServices.getService(UsageStatsManagerInternal.class));
             mActivityManagerInternal = Preconditions.checkNotNull(
                     LocalServices.getService(ActivityManagerInternal.class));
             mActivityTaskManagerInternal = Preconditions.checkNotNull(
@@ -671,6 +675,30 @@
             }
         }
 
+        @Override
+        public LauncherApps.AppUsageLimit getAppUsageLimit(String callingPackage,
+                String packageName, UserHandle user) {
+            verifyCallingPackage(callingPackage);
+            if (!canAccessProfile(user.getIdentifier(), "Cannot access usage limit")) {
+                return null;
+            }
+
+            final PackageManagerInternal pmi =
+                    LocalServices.getService(PackageManagerInternal.class);
+            final ComponentName cn = pmi.getDefaultHomeActivity(user.getIdentifier());
+            if (!cn.getPackageName().equals(callingPackage)) {
+                throw new SecurityException("Caller is not the active launcher");
+            }
+
+            final UsageStatsManagerInternal.AppUsageLimitData data =
+                    mUsageStatsManagerInternal.getAppUsageLimit(packageName, user);
+            if (data == null) {
+                return null;
+            }
+            return new LauncherApps.AppUsageLimit(
+                    data.isGroupLimit(), data.getTotalUsageLimit(), data.getUsageRemaining());
+        }
+
         private void ensureShortcutPermission(@NonNull String callingPackage) {
             verifyCallingPackage(callingPackage);
             if (!mShortcutServiceInternal.hasShortcutHostPermission(getCallingUserId(),
diff --git a/services/core/java/com/android/server/pm/PackageDexOptimizer.java b/services/core/java/com/android/server/pm/PackageDexOptimizer.java
index 5412e94..94b1b36 100644
--- a/services/core/java/com/android/server/pm/PackageDexOptimizer.java
+++ b/services/core/java/com/android/server/pm/PackageDexOptimizer.java
@@ -506,8 +506,10 @@
             boolean isUsedByOtherApps) {
         int flags = info.flags;
         boolean vmSafeMode = (flags & ApplicationInfo.FLAG_VM_SAFE_MODE) != 0;
-        // When a priv app is configured to run out of box, only verify it.
-        if (info.isPrivilegedApp() && DexManager.isPackageSelectedToRunOob(info.packageName)) {
+        // When an app or priv app is configured to run out of box, only verify it.
+        if (info.isCodeIntegrityPreferred()
+                || (info.isPrivilegedApp()
+                    && DexManager.isPackageSelectedToRunOob(info.packageName))) {
             return "verify";
         }
         if (vmSafeMode) {
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 b8342cf..6eff815 100644
--- a/services/core/java/com/android/server/pm/PackageManagerService.java
+++ b/services/core/java/com/android/server/pm/PackageManagerService.java
@@ -449,8 +449,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 +9118,7 @@
                 pkgCompilationReason = PackageManagerService.REASON_BACKGROUND_DEXOPT;
             }
 
-            if (PRECOMPILED_LAYOUT_ENABLED) {
+            if (SystemProperties.getBoolean(PRECOMPILE_LAYOUTS, false)) {
                 mArtManagerService.compileLayouts(pkg);
             }
 
@@ -10574,8 +10573,6 @@
                 Log.d(TAG, "Scanning package " + pkg.packageName);
         }
 
-        DexManager.maybeLogUnexpectedPackageDetails(pkg);
-
         // Initialize package source and resource directories
         final File scanFile = new File(pkg.codePath);
         final File destCodeFile = new File(pkg.applicationInfo.getCodePath());
@@ -16213,7 +16210,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);
@@ -20672,7 +20669,6 @@
         storage.registerListener(mStorageListener);
 
         mInstallerService.systemReady();
-        mDexManager.systemReady();
         mPackageDexOptimizer.systemReady();
 
         getStorageManagerInternal().addExternalStoragePolicy(
diff --git a/services/core/java/com/android/server/pm/PackageManagerShellCommand.java b/services/core/java/com/android/server/pm/PackageManagerShellCommand.java
index 022c1aa..6f1eeeb 100644
--- a/services/core/java/com/android/server/pm/PackageManagerShellCommand.java
+++ b/services/core/java/com/android/server/pm/PackageManagerShellCommand.java
@@ -39,11 +39,12 @@
 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.PackageManagerInternal;
 import android.content.pm.PackageItemInfo;
 import android.content.pm.PackageManager;
 import android.content.pm.PackageManager.NameNotFoundException;
+import android.content.pm.PackageManagerInternal;
 import android.content.pm.PackageParser;
 import android.content.pm.PackageParser.ApkLite;
 import android.content.pm.PackageParser.PackageLite;
@@ -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<>();
@@ -634,9 +659,9 @@
                 if (showVersionCode) {
                     pw.print(" versionCode:");
                     if (info.applicationInfo != null) {
-                        pw.print(info.applicationInfo.versionCode);
+                        pw.print(info.applicationInfo.longVersionCode);
                     } else {
-                        pw.print(info.versionCode);
+                        pw.print(info.getLongVersionCode());
                     }
                 }
                 if (listInstaller && !isApex) {
@@ -2307,7 +2332,7 @@
                     sessionParams.installFlags |= PackageManager.INSTALL_FORCE_SDK;
                     break;
                 case "--apex":
-                    sessionParams.installFlags |= PackageManager.INSTALL_APEX;
+                    sessionParams.setInstallAsApex();
                     sessionParams.setStaged();
                     break;
                 case "--multi-package":
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..792b34c 100644
--- a/services/core/java/com/android/server/pm/ShortcutService.java
+++ b/services/core/java/com/android/server/pm/ShortcutService.java
@@ -2167,6 +2167,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/UserRestrictionsUtils.java b/services/core/java/com/android/server/pm/UserRestrictionsUtils.java
index aaa1874..2455113 100644
--- a/services/core/java/com/android/server/pm/UserRestrictionsUtils.java
+++ b/services/core/java/com/android/server/pm/UserRestrictionsUtils.java
@@ -16,6 +16,10 @@
 
 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;
@@ -38,10 +42,6 @@
 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;
 
diff --git a/services/core/java/com/android/server/pm/dex/DexManager.java b/services/core/java/com/android/server/pm/dex/DexManager.java
index 1a2b115..7ac7395 100644
--- a/services/core/java/com/android/server/pm/dex/DexManager.java
+++ b/services/core/java/com/android/server/pm/dex/DexManager.java
@@ -16,31 +16,28 @@
 
 package com.android.server.pm.dex;
 
+import static android.provider.DeviceConfig.FsiBoot;
+
 import static com.android.server.pm.InstructionSets.getAppDexInstructionSets;
 import static com.android.server.pm.dex.PackageDexUsage.DexUseInfo;
 import static com.android.server.pm.dex.PackageDexUsage.PackageUseInfo;
 
-import android.content.ContentResolver;
 import android.content.Context;
 import android.content.pm.ApplicationInfo;
 import android.content.pm.IPackageManager;
 import android.content.pm.PackageInfo;
-import android.content.pm.PackageParser;
-import android.database.ContentObserver;
-import android.os.Build;
 import android.os.FileUtils;
 import android.os.RemoteException;
 import android.os.SystemProperties;
 import android.os.UserHandle;
 import android.os.storage.StorageManager;
-import android.provider.Settings.Global;
+import android.provider.DeviceConfig;
 import android.util.Log;
 import android.util.Slog;
 import android.util.jar.StrictJarFile;
 
 import com.android.internal.annotations.GuardedBy;
 import com.android.internal.annotations.VisibleForTesting;
-import com.android.internal.util.ArrayUtils;
 import com.android.server.pm.Installer;
 import com.android.server.pm.Installer.InstallerException;
 import com.android.server.pm.PackageDexOptimizer;
@@ -136,10 +133,6 @@
         return mDexLogger;
     }
 
-    public void systemReady() {
-        registerSettingObserver();
-    }
-
     /**
      * Notify about dex files loads.
      * Note that this method is invoked when apps load dex files and it should
@@ -699,47 +692,10 @@
         mDexLogger.writeNow();
     }
 
-    private void registerSettingObserver() {
-        final ContentResolver resolver = mContext.getContentResolver();
-
-        // This observer provides a one directional mapping from Global.PRIV_APP_OOB_ENABLED to
-        // pm.dexopt.priv-apps-oob property. This is only for experiment and should be removed once
-        // it is done.
-        ContentObserver privAppOobObserver = new ContentObserver(null) {
-            @Override
-            public void onChange(boolean selfChange) {
-                int oobEnabled = Global.getInt(resolver, Global.PRIV_APP_OOB_ENABLED, 0);
-                SystemProperties.set(PROPERTY_NAME_PM_DEXOPT_PRIV_APPS_OOB,
-                        oobEnabled == 1 ? "true" : "false");
-            }
-        };
-        resolver.registerContentObserver(
-                Global.getUriFor(Global.PRIV_APP_OOB_ENABLED), false, privAppOobObserver,
-                UserHandle.USER_SYSTEM);
-        // At boot, restore the value from the setting, which persists across reboot.
-        privAppOobObserver.onChange(true);
-
-        ContentObserver privAppOobListObserver = new ContentObserver(null) {
-            @Override
-            public void onChange(boolean selfChange) {
-                String oobList = Global.getString(resolver, Global.PRIV_APP_OOB_LIST);
-                if (oobList == null) {
-                    oobList = "ALL";
-                }
-                SystemProperties.set(PROPERTY_NAME_PM_DEXOPT_PRIV_APPS_OOB_LIST, oobList);
-            }
-        };
-        resolver.registerContentObserver(
-                Global.getUriFor(Global.PRIV_APP_OOB_LIST), false, privAppOobListObserver,
-                UserHandle.USER_SYSTEM);
-        // At boot, restore the value from the setting, which persists across reboot.
-        privAppOobListObserver.onChange(true);
-    }
-
     /**
      * Returns whether the given package is in the list of privilaged apps that should run out of
-     * box. This only makes sense if PROPERTY_NAME_PM_DEXOPT_PRIV_APPS_OOB is true. Note that when
-     * the the OOB list is empty, all priv apps will run in OOB mode.
+     * box. This only makes sense if the feature is enabled. Note that when the the OOB list is
+     * empty, all priv apps will run in OOB mode.
      */
     public static boolean isPackageSelectedToRunOob(String packageName) {
         return isPackageSelectedToRunOob(Arrays.asList(packageName));
@@ -747,19 +703,35 @@
 
     /**
      * Returns whether any of the given packages are in the list of privilaged apps that should run
-     * out of box. This only makes sense if PROPERTY_NAME_PM_DEXOPT_PRIV_APPS_OOB is true. Note that
-     * when the the OOB list is empty, all priv apps will run in OOB mode.
+     * out of box. This only makes sense if the feature is enabled. Note that when the the OOB list
+     * is empty, all priv apps will run in OOB mode.
      */
     public static boolean isPackageSelectedToRunOob(Collection<String> packageNamesInSameProcess) {
-        if (!SystemProperties.getBoolean(PROPERTY_NAME_PM_DEXOPT_PRIV_APPS_OOB, false)) {
+        return isPackageSelectedToRunOobInternal(
+                SystemProperties.getBoolean(PROPERTY_NAME_PM_DEXOPT_PRIV_APPS_OOB, false),
+                SystemProperties.get(PROPERTY_NAME_PM_DEXOPT_PRIV_APPS_OOB_LIST, "ALL"),
+                DeviceConfig.getProperty(FsiBoot.NAMESPACE, FsiBoot.OOB_ENABLED),
+                DeviceConfig.getProperty(FsiBoot.NAMESPACE, FsiBoot.OOB_WHITELIST),
+                packageNamesInSameProcess);
+    }
+
+    @VisibleForTesting
+    /* package */ static boolean isPackageSelectedToRunOobInternal(
+            boolean isDefaultEnabled, String defaultWhitelist, String overrideEnabled,
+            String overrideWhitelist, Collection<String> packageNamesInSameProcess) {
+        // Allow experiment (if exists) to override device configuration.
+        boolean enabled = overrideEnabled != null ? overrideEnabled.equals("true")
+                : isDefaultEnabled;
+        if (!enabled) {
             return false;
         }
-        String oobListProperty = SystemProperties.get(
-                PROPERTY_NAME_PM_DEXOPT_PRIV_APPS_OOB_LIST, "ALL");
-        if ("ALL".equals(oobListProperty)) {
+
+        // Similarly, experiment flag can override the whitelist.
+        String whitelist = overrideWhitelist != null ? overrideWhitelist : defaultWhitelist;
+        if ("ALL".equals(whitelist)) {
             return true;
         }
-        for (String oobPkgName : oobListProperty.split(",")) {
+        for (String oobPkgName : whitelist.split(",")) {
             if (packageNamesInSameProcess.contains(oobPkgName)) {
                 return true;
             }
@@ -768,32 +740,6 @@
     }
 
     /**
-     * Generates package related log if the package has code stored in unexpected way.
-     */
-    public static void maybeLogUnexpectedPackageDetails(PackageParser.Package pkg) {
-        if (!Build.IS_DEBUGGABLE) {
-            return;
-        }
-
-        if (pkg.isPrivileged() && isPackageSelectedToRunOob(pkg.packageName)) {
-            logIfPackageHasUncompressedCode(pkg);
-        }
-    }
-
-    /**
-     * Generates log if the APKs in the given package have uncompressed dex file and so
-     * files that can be direclty mapped.
-     */
-    private static void logIfPackageHasUncompressedCode(PackageParser.Package pkg) {
-        auditUncompressedCodeInApk(pkg.baseCodePath);
-        if (!ArrayUtils.isEmpty(pkg.splitCodePaths)) {
-            for (int i = 0; i < pkg.splitCodePaths.length; i++) {
-                auditUncompressedCodeInApk(pkg.splitCodePaths[i]);
-            }
-        }
-    }
-
-    /**
      * Generates log if the archive located at {@code fileName} has uncompressed dex file and so
      * files that can be direclty mapped.
      */
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/PhoneWindowManager.java b/services/core/java/com/android/server/policy/PhoneWindowManager.java
index 41cab2d..13c4d88 100644
--- a/services/core/java/com/android/server/policy/PhoneWindowManager.java
+++ b/services/core/java/com/android/server/policy/PhoneWindowManager.java
@@ -1901,9 +1901,8 @@
 
         mWindowManagerInternal.registerAppTransitionListener(new AppTransitionListener() {
             @Override
-            public int onAppTransitionStartingLocked(int transit, IBinder openToken,
-                    IBinder closeToken, long duration, long statusBarAnimationStartTime,
-                    long statusBarAnimationDuration) {
+            public int onAppTransitionStartingLocked(int transit, long duration,
+                    long statusBarAnimationStartTime, long statusBarAnimationDuration) {
                 return handleStartTransitionForKeyguardLw(transit, duration);
             }
 
@@ -2570,6 +2569,7 @@
     }
 
     private static final int[] WINDOW_TYPES_WHERE_HOME_DOESNT_WORK = {
+            WindowManager.LayoutParams.TYPE_APPLICATION_OVERLAY,
             WindowManager.LayoutParams.TYPE_SYSTEM_ALERT,
             WindowManager.LayoutParams.TYPE_SYSTEM_ERROR,
         };
diff --git a/services/core/java/com/android/server/policy/WindowManagerPolicy.java b/services/core/java/com/android/server/policy/WindowManagerPolicy.java
index e1a911e..1d82970 100644
--- a/services/core/java/com/android/server/policy/WindowManagerPolicy.java
+++ b/services/core/java/com/android/server/policy/WindowManagerPolicy.java
@@ -825,16 +825,16 @@
                 // like the ANR / app crashed dialogs
                 return  canAddInternalSystemWindow ? 11 : 10;
             case TYPE_APPLICATION_OVERLAY:
-                return  12;
+                return  canAddInternalSystemWindow ? 13 : 12;
             case TYPE_DREAM:
                 // used for Dreams (screensavers with TYPE_DREAM windows)
-                return  13;
+                return  14;
             case TYPE_INPUT_METHOD:
                 // on-screen keyboards and other such input method user interfaces go here.
-                return  14;
+                return  15;
             case TYPE_INPUT_METHOD_DIALOG:
                 // on-screen keyboards and other such input method user interfaces go here.
-                return  15;
+                return  16;
             case TYPE_STATUS_BAR:
                 return  17;
             case TYPE_STATUS_BAR_PANEL:
diff --git a/services/core/java/com/android/server/policy/role/LegacyRoleResolutionPolicy.java b/services/core/java/com/android/server/policy/role/LegacyRoleResolutionPolicy.java
index 055c941..7f2dedb 100644
--- a/services/core/java/com/android/server/policy/role/LegacyRoleResolutionPolicy.java
+++ b/services/core/java/com/android/server/policy/role/LegacyRoleResolutionPolicy.java
@@ -18,6 +18,7 @@
 
 import android.annotation.NonNull;
 import android.app.role.RoleManager;
+import android.content.ComponentName;
 import android.content.Context;
 import android.os.Debug;
 import android.provider.Settings;
@@ -90,6 +91,17 @@
 
                 return CollectionUtils.singletonOrEmpty(result);
             }
+            case RoleManager.ROLE_ASSISTANT: {
+                String legacyAssistant = Settings.Secure.getStringForUser(
+                        mContext.getContentResolver(), Settings.Secure.ASSISTANT, userId);
+
+                if (legacyAssistant == null || legacyAssistant.isEmpty()) {
+                    return Collections.emptyList();
+                } else {
+                    return Collections.singletonList(
+                            ComponentName.unflattenFromString(legacyAssistant).getPackageName());
+                }
+            }
             default: {
                 Slog.e(LOG_TAG, "Don't know how to find legacy role holders for " + roleName);
                 return Collections.emptyList();
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/power/batterysaver/BatterySaverController.java b/services/core/java/com/android/server/power/batterysaver/BatterySaverController.java
index 1d74350..ab2807a 100644
--- a/services/core/java/com/android/server/power/batterysaver/BatterySaverController.java
+++ b/services/core/java/com/android/server/power/batterysaver/BatterySaverController.java
@@ -104,6 +104,7 @@
     public static final int REASON_SETTING_CHANGED = 8;
     public static final int REASON_DYNAMIC_POWER_SAVINGS_AUTOMATIC_ON = 9;
     public static final int REASON_DYNAMIC_POWER_SAVINGS_AUTOMATIC_OFF = 10;
+    public static final int REASON_STICKY_RESTORE_OFF = 13;
 
     /**
      * Plugin interface. All methods are guaranteed to be called on the same (handler) thread.
diff --git a/services/core/java/com/android/server/power/batterysaver/BatterySaverStateMachine.java b/services/core/java/com/android/server/power/batterysaver/BatterySaverStateMachine.java
index b7f28da..404e450 100644
--- a/services/core/java/com/android/server/power/batterysaver/BatterySaverStateMachine.java
+++ b/services/core/java/com/android/server/power/batterysaver/BatterySaverStateMachine.java
@@ -28,7 +28,6 @@
 import android.os.PowerManager;
 import android.os.UserHandle;
 import android.provider.Settings;
-import android.provider.Settings.Global;
 import android.util.Slog;
 import android.util.proto.ProtoOutputStream;
 
@@ -85,42 +84,56 @@
     @GuardedBy("mLock")
     private boolean mIsBatteryLevelLow;
 
-    /** Previously known value of Global.LOW_POWER_MODE. */
+    /** Previously known value of Settings.Global.LOW_POWER_MODE. */
     @GuardedBy("mLock")
     private boolean mSettingBatterySaverEnabled;
 
-    /** Previously known value of Global.LOW_POWER_MODE_STICKY. */
+    /** Previously known value of Settings.Global.LOW_POWER_MODE_STICKY. */
     @GuardedBy("mLock")
     private boolean mSettingBatterySaverEnabledSticky;
 
     /** Config flag to track if battery saver's sticky behaviour is disabled. */
     private final boolean mBatterySaverStickyBehaviourDisabled;
 
+    /**
+     * Whether or not to end sticky battery saver upon reaching a level specified by
+     * {@link #mSettingBatterySaverStickyAutoDisableThreshold}.
+     */
+    @GuardedBy("mLock")
+    private boolean mSettingBatterySaverStickyAutoDisableEnabled;
+
+    /**
+     * The battery level at which to end sticky battery saver. Only useful if
+     * {@link #mSettingBatterySaverStickyAutoDisableEnabled} is {@code true}.
+     */
+    @GuardedBy("mLock")
+    private int mSettingBatterySaverStickyAutoDisableThreshold;
+
     /** Config flag to track default disable threshold for Dynamic Power Savings enabled battery
      * saver. */
     @GuardedBy("mLock")
     private final int mDynamicPowerSavingsDefaultDisableThreshold;
 
     /**
-     * Previously known value of Global.LOW_POWER_MODE_TRIGGER_LEVEL.
+     * Previously known value of Settings.Global.LOW_POWER_MODE_TRIGGER_LEVEL.
      * (Currently only used in dumpsys.)
      */
     @GuardedBy("mLock")
     private int mSettingBatterySaverTriggerThreshold;
 
-    /** Previously known value of Global.AUTOMATIC_POWER_SAVER_MODE. */
+    /** Previously known value of Settings.Global.AUTOMATIC_POWER_SAVER_MODE. */
     @GuardedBy("mLock")
     private int mSettingAutomaticBatterySaver;
 
     /** When to disable battery saver again if it was enabled due to an external suggestion.
-     *  Corresponds to Global.DYNAMIC_POWER_SAVINGS_DISABLE_THRESHOLD.
+     *  Corresponds to Settings.Global.DYNAMIC_POWER_SAVINGS_DISABLE_THRESHOLD.
      */
     @GuardedBy("mLock")
     private int mDynamicPowerSavingsDisableThreshold;
 
     /**
      * Whether we've received a suggestion that battery saver should be on from an external app.
-     * Updates when Global.DYNAMIC_POWER_SAVINGS_ENABLED changes.
+     * Updates when Settings.Global.DYNAMIC_POWER_SAVINGS_ENABLED changes.
      */
     @GuardedBy("mLock")
     private boolean mDynamicPowerSavingsBatterySaver;
@@ -181,7 +194,7 @@
             Slog.d(TAG, "onBootCompleted");
         }
         // Just booted. We don't want LOW_POWER_MODE to be persisted, so just always clear it.
-        putGlobalSetting(Global.LOW_POWER_MODE, 0);
+        putGlobalSetting(Settings.Global.LOW_POWER_MODE, 0);
 
         // This is called with the power manager lock held. Don't do anything that may call to
         // upper services. (e.g. don't call into AM directly)
@@ -199,13 +212,19 @@
                     Settings.Global.LOW_POWER_MODE_TRIGGER_LEVEL),
                     false, mSettingsObserver, UserHandle.USER_SYSTEM);
             cr.registerContentObserver(Settings.Global.getUriFor(
-                    Global.AUTOMATIC_POWER_SAVER_MODE),
+                    Settings.Global.AUTOMATIC_POWER_SAVER_MODE),
                     false, mSettingsObserver, UserHandle.USER_SYSTEM);
             cr.registerContentObserver(Settings.Global.getUriFor(
-                    Global.DYNAMIC_POWER_SAVINGS_ENABLED),
+                    Settings.Global.DYNAMIC_POWER_SAVINGS_ENABLED),
                     false, mSettingsObserver, UserHandle.USER_SYSTEM);
             cr.registerContentObserver(Settings.Global.getUriFor(
-                    Global.DYNAMIC_POWER_SAVINGS_DISABLE_THRESHOLD),
+                    Settings.Global.DYNAMIC_POWER_SAVINGS_DISABLE_THRESHOLD),
+                    false, mSettingsObserver, UserHandle.USER_SYSTEM);
+            cr.registerContentObserver(Settings.Global.getUriFor(
+                    Settings.Global.LOW_POWER_MODE_STICKY_AUTO_DISABLE_ENABLED),
+                    false, mSettingsObserver, UserHandle.USER_SYSTEM);
+            cr.registerContentObserver(Settings.Global.getUriFor(
+                    Settings.Global.LOW_POWER_MODE_STICKY_AUTO_DISABLE_LEVEL),
                     false, mSettingsObserver, UserHandle.USER_SYSTEM);
 
             synchronized (mLock) {
@@ -239,25 +258,31 @@
     }
 
     @GuardedBy("mLock")
-    void refreshSettingsLocked() {
+    private void refreshSettingsLocked() {
         final boolean lowPowerModeEnabled = getGlobalSetting(
                 Settings.Global.LOW_POWER_MODE, 0) != 0;
         final boolean lowPowerModeEnabledSticky = getGlobalSetting(
                 Settings.Global.LOW_POWER_MODE_STICKY, 0) != 0;
         final boolean dynamicPowerSavingsBatterySaver = getGlobalSetting(
-                Global.DYNAMIC_POWER_SAVINGS_ENABLED, 0) != 0;
+                Settings.Global.DYNAMIC_POWER_SAVINGS_ENABLED, 0) != 0;
         final int lowPowerModeTriggerLevel = getGlobalSetting(
                 Settings.Global.LOW_POWER_MODE_TRIGGER_LEVEL, 0);
-        final int automaticBatterySaver = getGlobalSetting(
-                Global.AUTOMATIC_POWER_SAVER_MODE,
+        final int automaticBatterySaverMode = getGlobalSetting(
+                Settings.Global.AUTOMATIC_POWER_SAVER_MODE,
                 PowerManager.POWER_SAVER_MODE_PERCENTAGE);
         final int dynamicPowerSavingsDisableThreshold = getGlobalSetting(
-                Global.DYNAMIC_POWER_SAVINGS_DISABLE_THRESHOLD,
+                Settings.Global.DYNAMIC_POWER_SAVINGS_DISABLE_THRESHOLD,
                 mDynamicPowerSavingsDefaultDisableThreshold);
+        final boolean isStickyAutoDisableEnabled = getGlobalSetting(
+                Settings.Global.LOW_POWER_MODE_STICKY_AUTO_DISABLE_ENABLED, 0) != 0;
+        final int stickyAutoDisableThreshold = getGlobalSetting(
+                Settings.Global.LOW_POWER_MODE_STICKY_AUTO_DISABLE_LEVEL, 90);
 
         setSettingsLocked(lowPowerModeEnabled, lowPowerModeEnabledSticky,
-                lowPowerModeTriggerLevel, automaticBatterySaver, dynamicPowerSavingsBatterySaver,
-                dynamicPowerSavingsDisableThreshold);
+                lowPowerModeTriggerLevel,
+                isStickyAutoDisableEnabled, stickyAutoDisableThreshold,
+                automaticBatterySaverMode,
+                dynamicPowerSavingsBatterySaver, dynamicPowerSavingsDisableThreshold);
     }
 
     /**
@@ -269,12 +294,16 @@
     @GuardedBy("mLock")
     @VisibleForTesting
     void setSettingsLocked(boolean batterySaverEnabled, boolean batterySaverEnabledSticky,
-            int batterySaverTriggerThreshold, int automaticBatterySaver,
+            int batterySaverTriggerThreshold,
+            boolean isStickyAutoDisableEnabled, int stickyAutoDisableThreshold,
+            int automaticBatterySaver,
             boolean dynamicPowerSavingsBatterySaver, int dynamicPowerSavingsDisableThreshold) {
         if (DEBUG) {
             Slog.d(TAG, "setSettings: enabled=" + batterySaverEnabled
                     + " sticky=" + batterySaverEnabledSticky
                     + " threshold=" + batterySaverTriggerThreshold
+                    + " stickyAutoDisableEnabled=" + isStickyAutoDisableEnabled
+                    + " stickyAutoDisableThreshold=" + stickyAutoDisableThreshold
                     + " automaticBatterySaver=" + automaticBatterySaver
                     + " dynamicPowerSavingsBatterySaver=" + dynamicPowerSavingsBatterySaver
                     + " dynamicPowerSavingsDisableThreshold="
@@ -283,11 +312,19 @@
 
         mSettingsLoaded = true;
 
+        // Set sensible limits.
+        stickyAutoDisableThreshold = Math.max(stickyAutoDisableThreshold,
+                batterySaverTriggerThreshold);
+
         final boolean enabledChanged = mSettingBatterySaverEnabled != batterySaverEnabled;
         final boolean stickyChanged =
                 mSettingBatterySaverEnabledSticky != batterySaverEnabledSticky;
         final boolean thresholdChanged
                 = mSettingBatterySaverTriggerThreshold != batterySaverTriggerThreshold;
+        final boolean stickyAutoDisableEnabledChanged =
+                mSettingBatterySaverStickyAutoDisableEnabled != isStickyAutoDisableEnabled;
+        final boolean stickyAutoDisableThresholdChanged =
+                mSettingBatterySaverStickyAutoDisableThreshold != stickyAutoDisableThreshold;
         final boolean automaticModeChanged = mSettingAutomaticBatterySaver != automaticBatterySaver;
         final boolean dynamicPowerSavingsThresholdChanged =
                 mDynamicPowerSavingsDisableThreshold != dynamicPowerSavingsDisableThreshold;
@@ -295,6 +332,7 @@
                 mDynamicPowerSavingsBatterySaver != dynamicPowerSavingsBatterySaver;
 
         if (!(enabledChanged || stickyChanged || thresholdChanged || automaticModeChanged
+                || stickyAutoDisableEnabledChanged || stickyAutoDisableThresholdChanged
                 || dynamicPowerSavingsThresholdChanged || dynamicPowerSavingsBatterySaverChanged)) {
             return;
         }
@@ -302,6 +340,8 @@
         mSettingBatterySaverEnabled = batterySaverEnabled;
         mSettingBatterySaverEnabledSticky = batterySaverEnabledSticky;
         mSettingBatterySaverTriggerThreshold = batterySaverTriggerThreshold;
+        mSettingBatterySaverStickyAutoDisableEnabled = isStickyAutoDisableEnabled;
+        mSettingBatterySaverStickyAutoDisableThreshold = stickyAutoDisableThreshold;
         mSettingAutomaticBatterySaver = automaticBatterySaver;
         mDynamicPowerSavingsDisableThreshold = dynamicPowerSavingsDisableThreshold;
         mDynamicPowerSavingsBatterySaver = dynamicPowerSavingsBatterySaver;
@@ -376,7 +416,9 @@
                     + " mBatterySaverSnoozing=" + mBatterySaverSnoozing
                     + " mIsPowered=" + mIsPowered
                     + " mSettingAutomaticBatterySaver=" + mSettingAutomaticBatterySaver
-                    + " mSettingBatterySaverEnabledSticky=" + mSettingBatterySaverEnabledSticky);
+                    + " mSettingBatterySaverEnabledSticky=" + mSettingBatterySaverEnabledSticky
+                    + " mSettingBatterySaverStickyAutoDisableEnabled="
+                    + mSettingBatterySaverStickyAutoDisableEnabled);
         }
         if (!(mBootCompleted && mSettingsLoaded && mBatteryStatusSet)) {
             return; // Not fully initialized yet.
@@ -392,10 +434,15 @@
                     "Plugged in");
 
         } else if (mSettingBatterySaverEnabledSticky && !mBatterySaverStickyBehaviourDisabled) {
-            // Re-enable BS.
-            enableBatterySaverLocked(/*enable=*/ true, /*manual=*/ true,
-                    BatterySaverController.REASON_STICKY_RESTORE,
-                    "Sticky restore");
+            if (mSettingBatterySaverStickyAutoDisableEnabled
+                    && mBatteryLevel >= mSettingBatterySaverStickyAutoDisableThreshold) {
+                setStickyActive(false);
+            } else {
+                // Re-enable BS.
+                enableBatterySaverLocked(/*enable=*/ true, /*manual=*/ true,
+                        BatterySaverController.REASON_STICKY_RESTORE,
+                        "Sticky restore");
+            }
 
         } else if (mSettingAutomaticBatterySaver
                 == PowerManager.POWER_SAVER_MODE_PERCENTAGE
@@ -483,12 +530,10 @@
         }
 
         mSettingBatterySaverEnabled = enable;
-        putGlobalSetting(Global.LOW_POWER_MODE, enable ? 1 : 0);
+        putGlobalSetting(Settings.Global.LOW_POWER_MODE, enable ? 1 : 0);
 
         if (manual) {
-            mSettingBatterySaverEnabledSticky = !mBatterySaverStickyBehaviourDisabled && enable;
-            putGlobalSetting(Global.LOW_POWER_MODE_STICKY,
-                    mSettingBatterySaverEnabledSticky ? 1 : 0);
+            setStickyActive(!mBatterySaverStickyBehaviourDisabled && enable);
         }
         mBatterySaverController.enableBatterySaver(enable, intReason);
 
@@ -506,7 +551,8 @@
         }
     }
 
-    private void triggerDynamicModeNotification() {
+    @VisibleForTesting
+    void triggerDynamicModeNotification() {
         NotificationManager manager = mContext.getSystemService(NotificationManager.class);
         ensureNotificationChannelExists(manager);
 
@@ -553,14 +599,20 @@
         mBatterySaverSnoozing = snoozing;
     }
 
+    private void setStickyActive(boolean active) {
+        mSettingBatterySaverEnabledSticky = active;
+        putGlobalSetting(Settings.Global.LOW_POWER_MODE_STICKY,
+                mSettingBatterySaverEnabledSticky ? 1 : 0);
+    }
+
     @VisibleForTesting
     protected void putGlobalSetting(String key, int value) {
-        Global.putInt(mContext.getContentResolver(), key, value);
+        Settings.Global.putInt(mContext.getContentResolver(), key, value);
     }
 
     @VisibleForTesting
     protected int getGlobalSetting(String key, int defValue) {
-        return Global.getInt(mContext.getContentResolver(), key, defValue);
+        return Settings.Global.getInt(mContext.getContentResolver(), key, defValue);
     }
 
     public void dump(PrintWriter pw) {
@@ -597,6 +649,10 @@
             pw.println(mSettingBatterySaverEnabled);
             pw.print("  mSettingBatterySaverEnabledSticky=");
             pw.println(mSettingBatterySaverEnabledSticky);
+            pw.print("  mSettingBatterySaverStickyAutoDisableEnabled=");
+            pw.println(mSettingBatterySaverStickyAutoDisableEnabled);
+            pw.print("  mSettingBatterySaverStickyAutoDisableThreshold=");
+            pw.println(mSettingBatterySaverStickyAutoDisableThreshold);
             pw.print("  mSettingBatterySaverTriggerThreshold=");
             pw.println(mSettingBatterySaverTriggerThreshold);
             pw.print("  mBatterySaverStickyBehaviourDisabled=");
@@ -628,6 +684,13 @@
                     mSettingBatterySaverEnabledSticky);
             proto.write(BatterySaverStateMachineProto.SETTING_BATTERY_SAVER_TRIGGER_THRESHOLD,
                     mSettingBatterySaverTriggerThreshold);
+            proto.write(
+                    BatterySaverStateMachineProto.SETTING_BATTERY_SAVER_STICKY_AUTO_DISABLE_ENABLED,
+                    mSettingBatterySaverStickyAutoDisableEnabled);
+            proto.write(
+                    BatterySaverStateMachineProto
+                            .SETTING_BATTERY_SAVER_STICKY_AUTO_DISABLE_THRESHOLD,
+                    mSettingBatterySaverStickyAutoDisableThreshold);
 
             proto.end(token);
         }
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 c0517fd..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;
@@ -198,6 +206,7 @@
             // Make sure to implement LegacyRoleResolutionPolicy#getRoleHolders
             // for a given role before adding a migration statement for it here
             migrateRoleIfNecessary(RoleManager.ROLE_SMS, userId);
+            migrateRoleIfNecessary(RoleManager.ROLE_ASSISTANT, userId);
 
             // Some vital packages state has changed since last role grant
             // Run grants again
@@ -619,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/LocalIntentReceiver.java b/services/core/java/com/android/server/rollback/LocalIntentReceiver.java
new file mode 100644
index 0000000..504a349
--- /dev/null
+++ b/services/core/java/com/android/server/rollback/LocalIntentReceiver.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.android.server.rollback;
+
+import android.content.IIntentReceiver;
+import android.content.IIntentSender;
+import android.content.Intent;
+import android.content.IntentSender;
+import android.os.Bundle;
+import android.os.IBinder;
+
+import java.util.function.Consumer;
+
+/** {@code IntentSender} implementation for RollbackManager internal use. */
+class LocalIntentReceiver {
+    final Consumer<Intent> mConsumer;
+
+    LocalIntentReceiver(Consumer<Intent> consumer) {
+        mConsumer = consumer;
+    }
+
+    private IIntentSender.Stub mLocalSender = new IIntentSender.Stub() {
+        @Override
+        public void send(int code, Intent intent, String resolvedType, IBinder whitelistToken,
+                IIntentReceiver finishedReceiver, String requiredPermission, Bundle options) {
+            mConsumer.accept(intent);
+        }
+    };
+
+    public IntentSender getIntentSender() {
+        return new IntentSender((IIntentSender) mLocalSender);
+    }
+}
diff --git a/services/core/java/com/android/server/rollback/RollbackData.java b/services/core/java/com/android/server/rollback/RollbackData.java
index 4015c4f..a4f3064 100644
--- a/services/core/java/com/android/server/rollback/RollbackData.java
+++ b/services/core/java/com/android/server/rollback/RollbackData.java
@@ -49,6 +49,14 @@
      */
     public Instant timestamp;
 
+    /**
+     * Whether this Rollback is currently in progress. This field is true from the point
+     * we commit a {@code PackageInstaller} session containing these packages to the point the
+     * {@code PackageInstaller} calls into the {@code onFinished} callback.
+     */
+    // NOTE: All accesses to this field are from the RollbackManager handler thread.
+    public boolean inProgress = false;
+
     RollbackData(int rollbackId, File backupDir) {
         this.rollbackId = rollbackId;
         this.backupDir = backupDir;
diff --git a/services/core/java/com/android/server/rollback/RollbackManagerServiceImpl.java b/services/core/java/com/android/server/rollback/RollbackManagerServiceImpl.java
index 7f515bf..e59228a 100644
--- a/services/core/java/com/android/server/rollback/RollbackManagerServiceImpl.java
+++ b/services/core/java/com/android/server/rollback/RollbackManagerServiceImpl.java
@@ -19,8 +19,6 @@
 import android.app.AppOpsManager;
 import android.content.BroadcastReceiver;
 import android.content.Context;
-import android.content.IIntentReceiver;
-import android.content.IIntentSender;
 import android.content.Intent;
 import android.content.IntentFilter;
 import android.content.IntentSender;
@@ -30,17 +28,14 @@
 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.os.Binder;
-import android.os.Bundle;
 import android.os.Environment;
 import android.os.Handler;
 import android.os.HandlerThread;
-import android.os.IBinder;
 import android.os.ParcelFileDescriptor;
 import android.os.Process;
 import android.os.storage.StorageManager;
@@ -60,13 +55,10 @@
 import java.time.temporal.ChronoUnit;
 import java.util.ArrayList;
 import java.util.HashMap;
-import java.util.HashSet;
 import java.util.Iterator;
 import java.util.List;
 import java.util.Map;
 import java.util.Random;
-import java.util.Set;
-import java.util.function.Consumer;
 
 /**
  * Implementation of service that manages APK level rollbacks.
@@ -116,6 +108,7 @@
     private final Context mContext;
     private final HandlerThread mHandlerThread;
     private final Installer mInstaller;
+    private final RollbackPackageHealthObserver mPackageHealthObserver;
 
     RollbackManagerServiceImpl(Context context) {
         mContext = context;
@@ -128,6 +121,8 @@
 
         mRollbackStore = new RollbackStore(new File(Environment.getDataDirectory(), "rollback"));
 
+        mPackageHealthObserver = new RollbackPackageHealthObserver(mContext);
+
         // Kick off loading of the rollback data from strorage in a background
         // thread.
         // TODO: Consider loading the rollback data directly here instead, to
@@ -202,48 +197,20 @@
     }
 
     @Override
-    public RollbackInfo getAvailableRollback(String packageName) {
+    public ParceledListSlice getAvailableRollbacks() {
         mContext.enforceCallingOrSelfPermission(
                 android.Manifest.permission.MANAGE_ROLLBACKS,
-                "getAvailableRollback");
+                "getAvailableRollbacks");
 
-        RollbackData data = getRollbackForPackage(packageName);
-        if (data == null) {
-            return null;
-        }
-
-        // Note: The rollback for the package ought to be for the currently
-        // installed version, otherwise the rollback data is out of date. In
-        // that rare case, we'll check when we execute the rollback whether
-        // it's out of date or not, so no need to check package versions here.
-
-        for (PackageRollbackInfo info : data.packages) {
-            if (info.getPackageName().equals(packageName)) {
-                // TODO: Once the RollbackInfo API supports info about
-                // dependant packages, add that info here.
-                return new RollbackInfo(data.rollbackId, info);
-            }
-        }
-        return null;
-    }
-
-    @Override
-    public StringParceledListSlice getPackagesWithAvailableRollbacks() {
-        mContext.enforceCallingOrSelfPermission(
-                android.Manifest.permission.MANAGE_ROLLBACKS,
-                "getPackagesWithAvailableRollbacks");
-
-        final Set<String> packageNames = new HashSet<>();
         synchronized (mLock) {
             ensureRollbackDataLoadedLocked();
+            List<RollbackInfo> rollbacks = new ArrayList<>();
             for (int i = 0; i < mAvailableRollbacks.size(); ++i) {
                 RollbackData data = mAvailableRollbacks.get(i);
-                for (PackageRollbackInfo info : data.packages) {
-                    packageNames.add(info.getPackageName());
-                }
+                rollbacks.add(new RollbackInfo(data.rollbackId, data.packages));
             }
+            return new ParceledListSlice<>(rollbacks);
         }
-        return new StringParceledListSlice(new ArrayList<>(packageNames));
     }
 
     @Override
@@ -260,7 +227,7 @@
     }
 
     @Override
-    public void executeRollback(RollbackInfo rollback, String callerPackageName,
+    public void commitRollback(int rollbackId, String callerPackageName,
             IntentSender statusReceiver) {
         mContext.enforceCallingOrSelfPermission(
                 android.Manifest.permission.MANAGE_ROLLBACKS,
@@ -271,28 +238,26 @@
         appOps.checkPackage(callingUid, callerPackageName);
 
         getHandler().post(() ->
-                executeRollbackInternal(rollback, callerPackageName, statusReceiver));
+                commitRollbackInternal(rollbackId, callerPackageName, statusReceiver));
     }
 
     /**
-     * Performs the actual work to execute a rollback.
+     * Performs the actual work to commit a rollback.
      * The work is done on the current thread. This may be a long running
      * operation.
      */
-    private void executeRollbackInternal(RollbackInfo rollback,
+    private void commitRollbackInternal(int rollbackId,
             String callerPackageName, IntentSender statusReceiver) {
-        String targetPackageName = rollback.targetPackage.getPackageName();
-        Log.i(TAG, "Initiating rollback of " + targetPackageName);
+        Log.i(TAG, "Initiating rollback");
 
-        // Get the latest RollbackData for the target package.
-        RollbackData data = getRollbackForPackage(targetPackageName);
+        RollbackData data = getRollbackForId(rollbackId);
         if (data == null) {
-            sendFailure(statusReceiver, "No rollback available for package.");
+            sendFailure(statusReceiver, "Rollback unavailable");
             return;
         }
 
-        if (data.rollbackId != rollback.getRollbackId()) {
-            sendFailure(statusReceiver, "Rollback for package is out of date");
+        if (data.inProgress) {
+            sendFailure(statusReceiver, "Rollback for package is already in progress.");
             return;
         }
 
@@ -332,14 +297,8 @@
         PackageManager pm = context.getPackageManager();
         try {
             PackageInstaller packageInstaller = pm.getPackageInstaller();
-            String installerPackageName = pm.getInstallerPackageName(targetPackageName);
-            if (installerPackageName == null) {
-                sendFailure(statusReceiver, "Cannot find installer package");
-                return;
-            }
             PackageInstaller.SessionParams parentParams = new PackageInstaller.SessionParams(
                     PackageInstaller.SessionParams.MODE_FULL_INSTALL);
-            parentParams.setInstallerPackageName(installerPackageName);
             parentParams.setAllowDowngrade(true);
             parentParams.setMultiPackage();
             int parentSessionId = packageInstaller.createSession(parentParams);
@@ -348,6 +307,11 @@
             for (PackageRollbackInfo info : data.packages) {
                 PackageInstaller.SessionParams params = new PackageInstaller.SessionParams(
                         PackageInstaller.SessionParams.MODE_FULL_INSTALL);
+                String installerPackageName = pm.getInstallerPackageName(info.getPackageName());
+                if (installerPackageName == null) {
+                    sendFailure(statusReceiver, "Cannot find installer package");
+                    return;
+                }
                 params.setInstallerPackageName(installerPackageName);
                 params.setAllowDowngrade(true);
                 int sessionId = packageInstaller.createSession(params);
@@ -371,30 +335,40 @@
 
             final LocalIntentReceiver receiver = new LocalIntentReceiver(
                     (Intent result) -> {
-                        int status = result.getIntExtra(PackageInstaller.EXTRA_STATUS,
-                                PackageInstaller.STATUS_FAILURE);
-                        if (status != PackageInstaller.STATUS_SUCCESS) {
-                            sendFailure(statusReceiver, "Rollback downgrade install failed: "
-                                    + result.getStringExtra(PackageInstaller.EXTRA_STATUS_MESSAGE));
-                            return;
-                        }
+                        getHandler().post(() -> {
+                            // We've now completed the rollback, so we mark it as no longer in
+                            // progress.
+                            data.inProgress = false;
 
-                        addRecentlyExecutedRollback(rollback);
-                        sendSuccess(statusReceiver);
+                            int status = result.getIntExtra(PackageInstaller.EXTRA_STATUS,
+                                    PackageInstaller.STATUS_FAILURE);
+                            if (status != PackageInstaller.STATUS_SUCCESS) {
+                                sendFailure(statusReceiver,
+                                        "Rollback downgrade install failed: "
+                                        + result.getStringExtra(
+                                                PackageInstaller.EXTRA_STATUS_MESSAGE));
+                                return;
+                            }
 
-                        Intent broadcast = new Intent(Intent.ACTION_PACKAGE_ROLLBACK_EXECUTED);
+                            addRecentlyExecutedRollback(
+                                    new RollbackInfo(data.rollbackId, data.packages));
+                            sendSuccess(statusReceiver);
 
-                        // TODO: This call emits the warning "Calling a method in the
-                        // system process without a qualified user". Fix that.
-                        // TODO: Limit this to receivers holding the
-                        // MANAGE_ROLLBACKS permission?
-                        mContext.sendBroadcast(broadcast);
+                            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.
+                            // TODO: Limit this to receivers holding the
+                            // MANAGE_ROLLBACKS permission?
+                            mContext.sendBroadcast(broadcast);
+                        });
                     }
             );
 
+            data.inProgress = true;
             parentSession.commit(receiver.getIntentSender());
         } catch (IOException e) {
-            Log.e(TAG, "Unable to roll back " + targetPackageName, e);
+            Log.e(TAG, "Rollback failed", e);
             sendFailure(statusReceiver, "IOException: " + e.toString());
             return;
         }
@@ -525,9 +499,12 @@
             boolean changed = false;
             while (iter.hasNext()) {
                 RollbackInfo rollback = iter.next();
-                if (packageName.equals(rollback.targetPackage.getPackageName())) {
-                    iter.remove();
-                    changed = true;
+                for (PackageRollbackInfo info : rollback.getPackages()) {
+                    if (packageName.equals(info.getPackageName())) {
+                        iter.remove();
+                        changed = true;
+                        break;
+                    }
                 }
             }
 
@@ -774,10 +751,15 @@
 
         getHandler().post(() -> {
             PackageManagerInternal pmi = LocalServices.getService(PackageManagerInternal.class);
-            // TODO(narayan): Should we make sure we're in the middle of a session commit for a
-            // a package with this package name ? Otherwise it's possible we may roll back data
-            // for some other downgrade.
-            if (getRollbackForPackage(packageName) == null) {
+            final RollbackData rollbackData = getRollbackForPackage(packageName);
+            if (rollbackData == null) {
+                pmi.finishPackageInstall(token, false);
+                return;
+            }
+
+            if (!rollbackData.inProgress) {
+                Log.e(TAG, "Request to restore userData for: " + packageName
+                        + ", but no rollback in progress.");
                 pmi.finishPackageInstall(token, false);
                 return;
             }
@@ -805,26 +787,6 @@
         });
     }
 
-    private class LocalIntentReceiver {
-        final Consumer<Intent> mConsumer;
-
-        LocalIntentReceiver(Consumer<Intent> consumer) {
-            mConsumer = consumer;
-        }
-
-        private IIntentSender.Stub mLocalSender = new IIntentSender.Stub() {
-            @Override
-            public void send(int code, Intent intent, String resolvedType, IBinder whitelistToken,
-                    IIntentReceiver finishedReceiver, String requiredPermission, Bundle options) {
-                getHandler().post(() -> mConsumer.accept(intent));
-            }
-        };
-
-        public IntentSender getIntentSender() {
-            return new IntentSender((IIntentSender) mLocalSender);
-        }
-    }
-
     /**
      * Gets the version of the package currently installed.
      * Returns null if the package is not currently installed.
@@ -891,7 +853,17 @@
                             ensureRollbackDataLoadedLocked();
                             mAvailableRollbacks.add(data);
                         }
-
+                        // TODO(zezeozue): Provide API to explicitly start observing instead
+                        // of doing this for all rollbacks. If we do this for all rollbacks,
+                        // should document in PackageInstaller.SessionParams#setEnableRollback
+                        // After enabling and commiting any rollback, observe packages and
+                        // prepare to rollback if packages crashes too frequently.
+                        List<String> packages = new ArrayList<>();
+                        for (int i = 0; i < data.packages.size(); i++) {
+                            packages.add(data.packages.get(i).getPackageName());
+                        }
+                        mPackageHealthObserver.startObservingHealth(packages,
+                                ROLLBACK_LIFETIME_DURATION_MILLIS);
                         scheduleExpiration(ROLLBACK_LIFETIME_DURATION_MILLIS);
                     } catch (IOException e) {
                         Log.e(TAG, "Unable to enable rollback", e);
@@ -928,6 +900,25 @@
         return null;
     }
 
+    /*
+     * Returns the RollbackData, if any, for an available rollback with the
+     * given rollbackId.
+     */
+    private RollbackData getRollbackForId(int rollbackId) {
+        synchronized (mLock) {
+            // TODO: Have ensureRollbackDataLoadedLocked return the list of
+            // available rollbacks, to hopefully avoid forgetting to call it?
+            ensureRollbackDataLoadedLocked();
+            for (int i = 0; i < mAvailableRollbacks.size(); ++i) {
+                RollbackData data = mAvailableRollbacks.get(i);
+                if (data.rollbackId == rollbackId) {
+                    return data;
+                }
+            }
+        }
+        return null;
+    }
+
     @GuardedBy("mLock")
     private int allocateRollbackIdLocked() throws IOException {
         int n = 0;
diff --git a/services/core/java/com/android/server/rollback/RollbackPackageHealthObserver.java b/services/core/java/com/android/server/rollback/RollbackPackageHealthObserver.java
new file mode 100644
index 0000000..fcae618
--- /dev/null
+++ b/services/core/java/com/android/server/rollback/RollbackPackageHealthObserver.java
@@ -0,0 +1,119 @@
+/*
+ * 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.rollback;
+
+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;
+import android.os.HandlerThread;
+
+import com.android.server.PackageWatchdog;
+import com.android.server.PackageWatchdog.PackageHealthObserver;
+import com.android.server.PackageWatchdog.PackageHealthObserverImpact;
+
+import java.util.List;
+
+/**
+ * {@code PackageHealthObserver} for {@code RollbackManagerService}.
+ *
+ * @hide
+ */
+public final class RollbackPackageHealthObserver implements PackageHealthObserver {
+    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();
+        PackageWatchdog.getInstance(mContext).registerHealthObserver(this);
+    }
+
+    @Override
+    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;
+        }
+        // Rollback is available, we may get a callback into #execute
+        return PackageHealthObserverImpact.USER_IMPACT_MEDIUM;
+    }
+
+    @Override
+    public boolean execute(String packageName) {
+        RollbackInfo rollback = getAvailableRollback(packageName);
+        if (rollback == null) {
+            // Expected a rollback to be available, what happened?
+            return false;
+        }
+
+        // TODO(zezeozue): Only rollback if rollback version == failed package version
+        LocalIntentReceiver rollbackReceiver = new LocalIntentReceiver((Intent result) -> {
+            int status = result.getIntExtra(PackageInstaller.EXTRA_STATUS,
+                    PackageInstaller.STATUS_FAILURE);
+            if (status == PackageInstaller.STATUS_SUCCESS) {
+                // TODO(zezeozue); Log success metrics
+                // Rolledback successfully, no action required by other observers
+            } else {
+                // TODO(zezeozue); Log failure metrics
+                // Rollback failed other observers should have a shot
+            }
+        });
+
+        // TODO(zezeozue): Log initiated metrics
+        mHandler.post(() ->
+                mRollbackManager.commitRollback(rollback.getRollbackId(),
+                    rollbackReceiver.getIntentSender()));
+        // Assume rollback executed successfully
+        return true;
+    }
+
+    @Override
+    public String getName() {
+        return NAME;
+    }
+
+    /**
+     * Start observing health of {@code packages} for {@code durationMs}.
+     * This may cause {@code packages} to be rolled back if they crash too freqeuntly.
+     */
+    public void startObservingHealth(List<String> packages, long durationMs) {
+        PackageWatchdog.getInstance(mContext).startObservingHealth(this, packages, durationMs);
+    }
+
+    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;
+                }
+            }
+        }
+        return null;
+    }
+}
diff --git a/services/core/java/com/android/server/rollback/RollbackStore.java b/services/core/java/com/android/server/rollback/RollbackStore.java
index 7738be9..3b24b3e 100644
--- a/services/core/java/com/android/server/rollback/RollbackStore.java
+++ b/services/core/java/com/android/server/rollback/RollbackStore.java
@@ -114,13 +114,9 @@
                 for (int i = 0; i < array.length(); ++i) {
                     JSONObject element = array.getJSONObject(i);
                     int rollbackId = element.getInt("rollbackId");
-                    String packageName = element.getString("packageName");
-                    long higherVersionCode = element.getLong("higherVersionCode");
-                    long lowerVersionCode = element.getLong("lowerVersionCode");
-                    PackageRollbackInfo target = new PackageRollbackInfo(
-                            new VersionedPackage(packageName, higherVersionCode),
-                            new VersionedPackage(packageName, lowerVersionCode));
-                    RollbackInfo rollback = new RollbackInfo(rollbackId, target);
+                    List<PackageRollbackInfo> packages = packageRollbackInfosFromJson(
+                            element.getJSONArray("packages"));
+                    RollbackInfo rollback = new RollbackInfo(rollbackId, packages);
                     recentlyExecutedRollbacks.add(rollback);
                 }
             } catch (IOException | JSONException e) {
@@ -155,18 +151,8 @@
     void saveAvailableRollback(RollbackData data) throws IOException {
         try {
             JSONObject dataJson = new JSONObject();
-            JSONArray packagesJson = new JSONArray();
-            for (PackageRollbackInfo info : data.packages) {
-                JSONObject infoJson = new JSONObject();
-                infoJson.put("packageName", info.getPackageName());
-                infoJson.put("higherVersionCode",
-                        info.getVersionRolledBackFrom().getLongVersionCode());
-                infoJson.put("lowerVersionCode",
-                        info.getVersionRolledBackTo().getVersionCode());
-                packagesJson.put(infoJson);
-            }
             dataJson.put("rollbackId", data.rollbackId);
-            dataJson.put("packages", packagesJson);
+            dataJson.put("packages", toJson(data.packages));
             dataJson.put("timestamp", data.timestamp.toString());
 
             PrintWriter pw = new PrintWriter(new File(data.backupDir, "rollback.json"));
@@ -200,11 +186,7 @@
                 RollbackInfo rollback = recentlyExecutedRollbacks.get(i);
                 JSONObject element = new JSONObject();
                 element.put("rollbackId", rollback.getRollbackId());
-                element.put("packageName", rollback.targetPackage.getPackageName());
-                element.put("higherVersionCode",
-                        rollback.targetPackage.getVersionRolledBackFrom().getLongVersionCode());
-                element.put("lowerVersionCode",
-                        rollback.targetPackage.getVersionRolledBackTo().getLongVersionCode());
+                element.put("packages", toJson(rollback.getPackages()));
                 array.put(element);
             }
 
@@ -229,18 +211,7 @@
 
             int rollbackId = dataJson.getInt("rollbackId");
             RollbackData data = new RollbackData(rollbackId, backupDir);
-
-            JSONArray packagesJson = dataJson.getJSONArray("packages");
-            for (int i = 0; i < packagesJson.length(); ++i) {
-                JSONObject infoJson = packagesJson.getJSONObject(i);
-                String packageName = infoJson.getString("packageName");
-                long higherVersionCode = infoJson.getLong("higherVersionCode");
-                long lowerVersionCode = infoJson.getLong("lowerVersionCode");
-                data.packages.add(new PackageRollbackInfo(
-                        new VersionedPackage(packageName, higherVersionCode),
-                        new VersionedPackage(packageName, lowerVersionCode)));
-            }
-
+            data.packages.addAll(packageRollbackInfosFromJson(dataJson.getJSONArray("packages")));
             data.timestamp = Instant.parse(dataJson.getString("timestamp"));
             return data;
         } catch (JSONException | DateTimeParseException e) {
@@ -248,6 +219,40 @@
         }
     }
 
+    private JSONObject toJson(PackageRollbackInfo info) throws JSONException {
+        JSONObject json = new JSONObject();
+        json.put("packageName", info.getPackageName());
+        json.put("higherVersionCode", info.getVersionRolledBackFrom().getLongVersionCode());
+        json.put("lowerVersionCode", info.getVersionRolledBackTo().getLongVersionCode());
+        return json;
+    }
+
+    private PackageRollbackInfo packageRollbackInfoFromJson(JSONObject json) throws JSONException {
+        String packageName = json.getString("packageName");
+        long higherVersionCode = json.getLong("higherVersionCode");
+        long lowerVersionCode = json.getLong("lowerVersionCode");
+        return new PackageRollbackInfo(
+                new VersionedPackage(packageName, higherVersionCode),
+                new VersionedPackage(packageName, lowerVersionCode));
+    }
+
+    private JSONArray toJson(List<PackageRollbackInfo> infos) throws JSONException {
+        JSONArray json = new JSONArray();
+        for (PackageRollbackInfo info : infos) {
+            json.put(toJson(info));
+        }
+        return json;
+    }
+
+    private List<PackageRollbackInfo> packageRollbackInfosFromJson(JSONArray json)
+            throws JSONException {
+        List<PackageRollbackInfo> infos = new ArrayList<>();
+        for (int i = 0; i < json.length(); ++i) {
+            infos.add(packageRollbackInfoFromJson(json.getJSONObject(i)));
+        }
+        return infos;
+    }
+
     /**
      * Deletes a file completely.
      * If the file is a directory, its contents are deleted as well.
diff --git a/services/core/java/com/android/server/signedconfig/SignatureVerifier.java b/services/core/java/com/android/server/signedconfig/SignatureVerifier.java
index 56db32a..146c516 100644
--- a/services/core/java/com/android/server/signedconfig/SignatureVerifier.java
+++ b/services/core/java/com/android/server/signedconfig/SignatureVerifier.java
@@ -41,8 +41,8 @@
     private static final boolean DBG = false;
 
     private static final String DEBUG_KEY =
-            "MFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAEaAn2XVifsLTHg616nTsOMVmlhBoECGbTEBTKKvdd2hO60"
-            + "pj1pnU8SMkhYfaNxZuKgw9LNvOwlFwStboIYeZ3lQ==";
+            "MFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAEmJKs4lSn+XRhMQmMid+Zbhbu13YrU1haIhVC5296InRu1"
+            + "x7A8PV1ejQyisBODGgRY6pqkAHRncBCYcgg5wIIJg==";
     private static final String PROD_KEY =
             "MFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAE+lky6wKyGL6lE1VrD0YTMHwb0Xwc+tzC8MvnrzVxodvTp"
             + "VY/jV7V+Zktcx+pry43XPABFRXtbhTo+qykhyBA1g==";
diff --git a/services/core/java/com/android/server/stats/StatsCompanionService.java b/services/core/java/com/android/server/stats/StatsCompanionService.java
index 40664fe..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;
@@ -1705,6 +1709,77 @@
         }
     }
 
+    private void pullTemperature(int tagId, long elapsedNanos, long wallClockNanos,
+            List<StatsLogEventWrapper> pulledData) {
+        long callingToken = Binder.clearCallingIdentity();
+        try {
+            List<Temperature> temperatures = sThermalService.getCurrentTemperatures();
+            for (Temperature temp : temperatures) {
+                StatsLogEventWrapper e =
+                        new StatsLogEventWrapper(tagId, elapsedNanos, wallClockNanos);
+                e.writeInt(temp.getType());
+                e.writeString(temp.getName());
+                e.writeInt((int) (temp.getValue() * 10));
+                pulledData.add(e);
+            }
+        } catch (RemoteException e) {
+            // Should not happen.
+            Slog.e(TAG, "Disconnected from thermal service. Cannot pull temperatures.");
+        } finally {
+            Binder.restoreCallingIdentity(callingToken);
+        }
+    }
+
+    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.
      */
@@ -1822,7 +1897,7 @@
                 pullCategorySize(tagId, elapsedNanos, wallClockNanos, ret);
                 break;
             }
-            case StatsLog.NUM_FINGERPRINTS: {
+            case StatsLog.NUM_FINGERPRINTS_ENROLLED: {
                 pullNumFingerprints(tagId, elapsedNanos, wallClockNanos, ret);
                 break;
             }
@@ -1867,6 +1942,18 @@
                 pullDeviceCalculatedPowerBlameOther(tagId, elapsedNanos, wallClockNanos, ret);
                 break;
             }
+            case StatsLog.TEMPERATURE: {
+                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 23c042a5..4adce58 100644
--- a/services/core/java/com/android/server/testharness/TestHarnessModeService.java
+++ b/services/core/java/com/android/server/testharness/TestHarnessModeService.java
@@ -66,6 +66,7 @@
     private static final String TEST_HARNESS_MODE_PROPERTY = "persist.sys.test_harness";
 
     private PersistentDataBlockManagerInternal mPersistentDataBlockManagerInternal;
+    private boolean mShouldSetUpTestHarnessMode;
 
     public TestHarnessModeService(Context context) {
         super(context);
@@ -96,6 +97,7 @@
             // There's no data to apply, so leave it as-is.
             return;
         }
+        mShouldSetUpTestHarnessMode = true;
         PersistentData persistentData = PersistentData.fromBytes(testHarnessModeData);
 
         SystemProperties.set(TEST_HARNESS_MODE_PROPERTY, persistentData.mEnabled ? "1" : "0");
@@ -124,6 +126,9 @@
     }
 
     private void disableAutoSync() {
+        if (!mShouldSetUpTestHarnessMode) {
+            return;
+        }
         UserInfo primaryUser = UserManager.get(getContext()).getPrimaryUser();
         ContentResolver
             .setMasterSyncAutomaticallyAsUser(false, primaryUser.getUserHandle().getIdentifier());
diff --git a/services/core/java/com/android/server/wallpaper/WallpaperManagerService.java b/services/core/java/com/android/server/wallpaper/WallpaperManagerService.java
index 69040a2..b0ef8a0 100644
--- a/services/core/java/com/android/server/wallpaper/WallpaperManagerService.java
+++ b/services/core/java/com/android/server/wallpaper/WallpaperManagerService.java
@@ -469,6 +469,7 @@
      */
     private void extractColors(WallpaperData wallpaper) {
         String cropFile = null;
+        boolean defaultImageWallpaper = false;
         int wallpaperId;
 
         if (wallpaper.equals(mFallbackWallpaper)) {
@@ -482,6 +483,8 @@
                     || wallpaper.wallpaperComponent == null;
             if (imageWallpaper && wallpaper.cropFile != null && wallpaper.cropFile.exists()) {
                 cropFile = wallpaper.cropFile.getAbsolutePath();
+            } else if (imageWallpaper && !wallpaper.cropExists() && !wallpaper.sourceExists()) {
+                defaultImageWallpaper = true;
             }
             wallpaperId = wallpaper.wallpaperId;
         }
@@ -493,6 +496,25 @@
                 colors = WallpaperColors.fromBitmap(bitmap);
                 bitmap.recycle();
             }
+        } else if (defaultImageWallpaper) {
+            // There is no crop and source file because this is default image wallpaper.
+            try (final InputStream is =
+                         WallpaperManager.openDefaultWallpaper(mContext, FLAG_SYSTEM)) {
+                if (is != null) {
+                    try {
+                        final BitmapFactory.Options options = new BitmapFactory.Options();
+                        final Bitmap bitmap = BitmapFactory.decodeStream(is, null, options);
+                        if (bitmap != null) {
+                            colors = WallpaperColors.fromBitmap(bitmap);
+                            bitmap.recycle();
+                        }
+                    } catch (OutOfMemoryError e) {
+                        Slog.w(TAG, "Can't decode default wallpaper stream", e);
+                    }
+                }
+            } catch (IOException e) {
+                Slog.w(TAG, "Can't close default wallpaper stream", e);
+            }
         }
 
         if (colors == null) {
diff --git a/services/core/java/com/android/server/wm/ActivityDisplay.java b/services/core/java/com/android/server/wm/ActivityDisplay.java
index 65d66f4..e817dd4 100644
--- a/services/core/java/com/android/server/wm/ActivityDisplay.java
+++ b/services/core/java/com/android/server/wm/ActivityDisplay.java
@@ -558,22 +558,26 @@
     }
 
     /**
-     * Pause all activities in either all of the stacks or just the back stacks.
+     * Pause all activities in either all of the stacks or just the back stacks. This is done before
+     * resuming a new activity and to make sure that previously active activities are
+     * paused in stacks that are no longer visible or in pinned windowing mode. This does not
+     * pause activities in visible stacks, so if an activity is launched within the same stack/task,
+     * then we should explicitly pause that stack's top activity.
      * @param userLeaving Passed to pauseActivity() to indicate whether to call onUserLeaving().
      * @param resuming The resuming activity.
      * @param dontWait The resuming activity isn't going to wait for all activities to be paused
      *                 before resuming.
-     * @return true if any activity was paused as a result of this call.
+     * @return {@code true} if any activity was paused as a result of this call.
      */
     boolean pauseBackStacks(boolean userLeaving, ActivityRecord resuming, boolean dontWait) {
         boolean someActivityPaused = false;
         for (int stackNdx = mStacks.size() - 1; stackNdx >= 0; --stackNdx) {
             final ActivityStack stack = mStacks.get(stackNdx);
-            // TODO(b/111541062): Check if resumed activity on this display instead
-            if (!mRootActivityContainer.isTopDisplayFocusedStack(stack)
-                    && stack.getResumedActivity() != null) {
+            final ActivityRecord resumedActivity = stack.getResumedActivity();
+            if (resumedActivity != null
+                    && (!stack.shouldBeVisible(resuming) || !stack.isFocusable())) {
                 if (DEBUG_STATES) Slog.d(TAG_STATES, "pauseBackStacks: stack=" + stack +
-                        " mResumedActivity=" + stack.getResumedActivity());
+                        " mResumedActivity=" + resumedActivity);
                 someActivityPaused |= stack.startPausingLocked(userLeaving, false, resuming,
                         dontWait);
             }
diff --git a/services/core/java/com/android/server/wm/ActivityRecord.java b/services/core/java/com/android/server/wm/ActivityRecord.java
index b8634d8..b7c35c0 100644
--- a/services/core/java/com/android/server/wm/ActivityRecord.java
+++ b/services/core/java/com/android/server/wm/ActivityRecord.java
@@ -1946,30 +1946,90 @@
         try {
             mAtmService.getLifecycleManager().scheduleTransaction(app.getThread(), appToken,
                     WindowVisibilityItem.obtain(true /* showWindow */));
-            if (shouldPauseWhenBecomingVisible()) {
-                // An activity must be in the {@link PAUSING} state for the system to validate
-                // the move to {@link PAUSED}.
-                setState(PAUSING, "makeVisibleIfNeeded");
-                mAtmService.getLifecycleManager().scheduleTransaction(app.getThread(), appToken,
-                        PauseActivityItem.obtain(finishing, false /* userLeaving */,
-                                configChangeFlags, false /* dontReport */));
-            }
+            makeActiveIfNeeded(null /* activeActivity*/);
         } catch (Exception e) {
             Slog.w(TAG, "Exception thrown sending visibility update: " + intent.getComponent(), e);
         }
     }
 
-    /** Check if activity should be moved to PAUSED state when it becomes visible. */
-    private boolean shouldPauseWhenBecomingVisible() {
-        // If the activity is stopped or stopping, cycle to the paused state. We avoid doing
+    /**
+     * Make activity resumed or paused if needed.
+     * @param activeActivity an activity that is resumed or just completed pause action.
+     *                       We won't change the state of this activity.
+     */
+    boolean makeActiveIfNeeded(ActivityRecord activeActivity) {
+        if (shouldResumeActivity(activeActivity)) {
+            if (DEBUG_VISIBILITY) {
+                Slog.v("TAG_VISIBILITY", "Resume visible activity, " + this);
+            }
+            return getActivityStack().resumeTopActivityUncheckedLocked(activeActivity /* prev */,
+                    null /* options */);
+        } else if (shouldPauseActivity(activeActivity)) {
+            if (DEBUG_VISIBILITY) {
+                Slog.v("TAG_VISIBILITY", "Pause visible activity, " + this);
+            }
+            // An activity must be in the {@link PAUSING} state for the system to validate
+            // the move to {@link PAUSED}.
+            setState(PAUSING, "makeVisibleIfNeeded");
+            try {
+                mAtmService.getLifecycleManager().scheduleTransaction(app.getThread(), appToken,
+                        PauseActivityItem.obtain(finishing, false /* userLeaving */,
+                                configChangeFlags, false /* dontReport */));
+            } catch (Exception e) {
+                Slog.w(TAG, "Exception thrown sending pause: " + intent.getComponent(), e);
+            }
+        }
+        return false;
+    }
+
+    /**
+     * Check if activity should be moved to PAUSED state. The activity:
+     * - should be eligible to be made active (see {@link #shouldMakeActive(ActivityRecord)})
+     * - should be non-focusable
+     * - should not be currently pausing or paused
+     * @param activeActivity the activity that is active or just completed pause action. We won't
+     *                       resume if this activity is active.
+     */
+    private boolean shouldPauseActivity(ActivityRecord activeActivity) {
+        return shouldMakeActive(activeActivity) && !isFocusable() && !isState(PAUSING, PAUSED);
+    }
+
+    /**
+     * Check if activity should be moved to RESUMED state. The activity:
+     * - should be eligible to be made active (see {@link #shouldMakeActive(ActivityRecord)})
+     * - should be focusable
+     * @param activeActivity the activity that is active or just completed pause action. We won't
+     *                       resume if this activity is active.
+     */
+    private boolean shouldResumeActivity(ActivityRecord activeActivity) {
+        return shouldMakeActive(activeActivity) && isFocusable() && !isState(RESUMED);
+    }
+
+    /**
+     * Check if activity is eligible to be made active (resumed of paused). The activity:
+     * - should be paused, stopped or stopping
+     * - should not be the currently active one or launching behind other tasks
+     * - should be either the topmost in task, or right below the top activity that is finishing
+     * If all of these conditions are not met at the same time, the activity cannot be made active.
+     */
+    private boolean shouldMakeActive(ActivityRecord activeActivity) {
+        // If the activity is stopped, stopping, cycle to an active state. We avoid doing
         // this when there is an activity waiting to become translucent as the extra binder
         // calls will lead to noticeable jank. A later call to
-        // ActivityStack#ensureActivitiesVisibleLocked will bring the activity to the proper
-        // paused state. We also avoid doing this for the activity the stack supervisor
-        // considers the resumed activity, as normal means will bring the activity from STOPPED
-        // to RESUMED. Adding PAUSING in this scenario will lead to double lifecycles.
-        if (!isState(STOPPED, STOPPING) || getActivityStack().mTranslucentActivityWaiting != null
-                || isResumedActivityOnDisplay()) {
+        // ActivityStack#ensureActivitiesVisibleLocked will bring the activity to a proper
+        // active state.
+        if (!isState(RESUMED, PAUSED, STOPPED, STOPPING)
+                || getActivityStack().mTranslucentActivityWaiting != null) {
+            return false;
+        }
+
+        if (this == activeActivity) {
+            return false;
+        }
+
+        if (this.mLaunchTaskBehind) {
+            // This activity is being launched from behind, which means that it's not intended to be
+            // presented to user right now, even if it's set to be visible.
             return false;
         }
 
@@ -1979,14 +2039,14 @@
             throw new IllegalStateException("Activity not found in its task");
         }
         if (positionInTask == task.mActivities.size() - 1) {
-            // It's the topmost activity in the task - should become paused now
+            // It's the topmost activity in the task - should become resumed now
             return true;
         }
         // Check if activity above is finishing now and this one becomes the topmost in task.
         final ActivityRecord activityAbove = task.mActivities.get(positionInTask + 1);
         if (activityAbove.finishing && results == null) {
-            // We will only allow pausing if activity above wasn't launched for result. Otherwise it
-            // will cause this activity to resume before getting result.
+            // We will only allow making active if activity above wasn't launched for result.
+            // Otherwise it will cause this activity to resume before getting result.
             return true;
         }
         return false;
diff --git a/services/core/java/com/android/server/wm/ActivityStack.java b/services/core/java/com/android/server/wm/ActivityStack.java
index 891c3da..c97e4e8 100644
--- a/services/core/java/com/android/server/wm/ActivityStack.java
+++ b/services/core/java/com/android/server/wm/ActivityStack.java
@@ -357,6 +357,11 @@
      */
     boolean mForceHidden = false;
 
+    /**
+     * Used to keep resumeTopActivityUncheckedLocked() from being entered recursively
+     */
+    boolean mInResumeTopActivity = false;
+
     private boolean mUpdateBoundsDeferred;
     private boolean mUpdateBoundsDeferredCalled;
     private boolean mUpdateDisplayedBoundsDeferredCalled;
@@ -1732,6 +1737,7 @@
             "Activity paused: token=" + token + ", timeout=" + timeout);
 
         final ActivityRecord r = isInStackLocked(token);
+
         if (r != null) {
             mHandler.removeMessages(PAUSE_TIMEOUT_MSG, r);
             if (mPausingActivity == r) {
@@ -2088,8 +2094,7 @@
             boolean aboveTop = top != null;
             final boolean stackShouldBeVisible = shouldBeVisible(starting);
             boolean behindFullscreenActivity = !stackShouldBeVisible;
-            boolean resumeNextActivity = mRootActivityContainer.isTopDisplayFocusedStack(this)
-                    && (isInStackLocked(starting) == null);
+            boolean resumeNextActivity = isFocusable() && isInStackLocked(starting) == null;
             final boolean isTopNotPinnedStack =
                     isAttached() && getDisplay().isTopNotPinnedStack(this);
             for (int taskNdx = mTaskHistory.size() - 1; taskNdx >= 0; --taskNdx) {
@@ -2150,6 +2155,10 @@
                             if (r.handleAlreadyVisible()) {
                                 resumeNextActivity = false;
                             }
+
+                            if (notifyClients) {
+                                r.makeActiveIfNeeded(starting);
+                            }
                         } else {
                             r.makeVisibleIfNeeded(starting, notifyClients);
                         }
@@ -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,7 +2522,7 @@
         boolean result = false;
         try {
             // Protect against recursion.
-            mStackSupervisor.inResumeTopActivity = true;
+            mInResumeTopActivity = true;
             result = resumeTopActivityInnerLocked(prev, options);
 
             // When resuming the top activity, it may be necessary to pause the top activity (for
@@ -2528,7 +2537,7 @@
                 checkReadyForSleep();
             }
         } finally {
-            mStackSupervisor.inResumeTopActivity = false;
+            mInResumeTopActivity = false;
         }
 
         return result;
@@ -2561,7 +2570,7 @@
         // Find the next top-most activity to resume in this stack that is not finishing and is
         // focusable. If it is not focusable, we will fall into the case below to resume the
         // top activity in the next focusable task.
-        final ActivityRecord next = topRunningActivityLocked(true /* focusableOnly */);
+        ActivityRecord next = topRunningActivityLocked(true /* focusableOnly */);
 
         final boolean hasRunningActivity = next != null;
 
@@ -2649,6 +2658,12 @@
         if (!mRootActivityContainer.allPausedActivitiesComplete()) {
             if (DEBUG_SWITCH || DEBUG_PAUSE || DEBUG_STATES) Slog.v(TAG_PAUSE,
                     "resumeTopActivityLocked: Skip resume: some activity pausing.");
+
+            // Adding previous activity to the waiting visible list, or it would be stopped
+            // before top activity being visible.
+            if (prev != null && !next.nowVisible) {
+                mStackSupervisor.mActivitiesWaitingForVisibleActivity.add(prev);
+            }
             return false;
         }
 
@@ -2858,7 +2873,9 @@
             // the screen based on the new activity order.
             boolean notUpdated = true;
 
-            if (isFocusedStackOnDisplay()) {
+            // Activity should also be visible if set mLaunchTaskBehind to true (see
+            // ActivityRecord#shouldBeVisibleIgnoringKeyguard()).
+            if (shouldBeVisible(next)) {
                 // We have special rotation behavior when here is some active activity that
                 // requests specific orientation or Keyguard is locked. Make sure all activity
                 // visibilities are set correctly as well as the transition is updated if needed
@@ -4087,6 +4104,12 @@
         mStackSupervisor.mFinishingActivities.add(r);
         r.resumeKeyDispatchingLocked();
         mRootActivityContainer.resumeFocusedStacksTopActivities();
+        // If activity was not paused at this point - explicitly pause it to start finishing
+        // process. Finishing will be completed once it reports pause back.
+        if (r.isState(RESUMED) && mPausingActivity != null) {
+            startPausingLocked(false /* userLeaving */, false /* uiSleeping */, next /* resuming */,
+                    false /* dontWait */);
+        }
         return r;
     }
 
diff --git a/services/core/java/com/android/server/wm/ActivityStackSupervisor.java b/services/core/java/com/android/server/wm/ActivityStackSupervisor.java
index 3a288ca..a83ef34 100644
--- a/services/core/java/com/android/server/wm/ActivityStackSupervisor.java
+++ b/services/core/java/com/android/server/wm/ActivityStackSupervisor.java
@@ -327,9 +327,6 @@
      */
     PowerManager.WakeLock mGoingToSleep;
 
-    /** Used to keep resumeTopActivityUncheckedLocked() from being entered recursively */
-    boolean inResumeTopActivity;
-
     /**
      * Temporary rect used during docked stack resize calculation so we don't need to create a new
      * object each time.
diff --git a/services/core/java/com/android/server/wm/ActivityStartController.java b/services/core/java/com/android/server/wm/ActivityStartController.java
index 2807094..43c1206 100644
--- a/services/core/java/com/android/server/wm/ActivityStartController.java
+++ b/services/core/java/com/android/server/wm/ActivityStartController.java
@@ -179,7 +179,10 @@
                 .setActivityOptions(options.toBundle())
                 .execute();
         mLastHomeActivityStartRecord = tmpOutRecord[0];
-        if (mSupervisor.inResumeTopActivity) {
+        final ActivityDisplay display =
+                mService.mRootActivityContainer.getActivityDisplay(displayId);
+        final ActivityStack homeStack = display != null ? display.getHomeStack() : null;
+        if (homeStack != null && homeStack.mInResumeTopActivity) {
             // If we are in resume section already, home activity will be initialized, but not
             // resumed (to avoid recursive resume) and will stay that way until something pokes it
             // again. We need to schedule another resume.
diff --git a/services/core/java/com/android/server/wm/ActivityStarter.java b/services/core/java/com/android/server/wm/ActivityStarter.java
index d36e545..b0e5811 100644
--- a/services/core/java/com/android/server/wm/ActivityStarter.java
+++ b/services/core/java/com/android/server/wm/ActivityStarter.java
@@ -747,12 +747,23 @@
         abort |= !mService.mIntentFirewall.checkStartActivity(intent, callingUid,
                 callingPid, resolvedType, aInfo.applicationInfo);
 
-        // not sure if we need to create START_ABORTED_BACKGROUND so for now piggybacking
-        // on START_ABORTED
+        boolean abortBackgroundStart = false;
         if (!abort) {
-            abort |= shouldAbortBackgroundActivityStart(callingUid, callingPid, callingPackage,
-                    realCallingUid, callerApp, originatingPendingIntent,
+            abortBackgroundStart = shouldAbortBackgroundActivityStart(callingUid, callingPid,
+                    callingPackage, realCallingUid, callerApp, originatingPendingIntent,
                     allowBackgroundActivityStart, intent);
+            abort |= (abortBackgroundStart && !mService.isBackgroundActivityStartsEnabled());
+            // TODO: remove this toast after feature development is done
+            if (abortBackgroundStart) {
+                final String toastMsg = abort
+                        ? "Background activity start from " + callingPackage
+                                + " blocked. See go/q-bg-block."
+                        : "This background activity start from " + callingPackage
+                                + " will be blocked in future Q builds. See go/q-bg-block.";
+                mService.mUiHandler.post(() -> {
+                    Toast.makeText(mService.mContext, toastMsg, Toast.LENGTH_LONG).show();
+                });
+            }
         }
 
         // Merge the two options bundles, while realCallerOptions takes precedence.
@@ -798,8 +809,6 @@
             // We pretend to the caller that it was really started, but
             // they will just get a cancel result.
             ActivityOptions.abort(checkedOptions);
-            maybeLogActivityStart(callingUid, callingPackage, realCallingUid, intent, callerApp,
-                    null /*r*/, originatingPendingIntent, true /*abortedStart*/);
             return START_ABORTED;
         }
 
@@ -818,6 +827,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));
@@ -892,8 +903,11 @@
         mService.onStartActivitySetDidAppSwitch();
         mController.doPendingActivityLaunches(false);
 
-        maybeLogActivityStart(callingUid, callingPackage, realCallingUid, intent, callerApp, r,
-                originatingPendingIntent, false /*abortedStart*/);
+        // 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);
@@ -903,11 +917,9 @@
             final String callingPackage, int realCallingUid, WindowProcessController callerApp,
             PendingIntentRecord originatingPendingIntent, boolean allowBackgroundActivityStart,
             Intent intent) {
-        if (mService.isBackgroundActivityStartsEnabled()) {
-            return false;
-        }
         // don't abort for the most important UIDs
-        if (callingUid == Process.ROOT_UID || callingUid == Process.SYSTEM_UID) {
+        if (callingUid == Process.ROOT_UID || callingUid == Process.SYSTEM_UID
+                || callingUid == Process.NFC_UID) {
             return false;
         }
         // don't abort if the callerApp has any visible activity
@@ -915,14 +927,15 @@
             return false;
         }
         // don't abort if the callingUid is in the foreground or is a persistent system process
-        final boolean isCallingUidForeground = isUidForeground(callingUid);
+        final boolean isCallingUidForeground = mService.isUidForeground(callingUid);
         final boolean isCallingUidPersistentSystemProcess = isUidPersistentSystemProcess(
                 callingUid);
         if (isCallingUidForeground || isCallingUidPersistentSystemProcess) {
             return false;
         }
         // take realCallingUid into consideration
-        final boolean isRealCallingUidForeground = isUidForeground(realCallingUid);
+        final boolean isRealCallingUidForeground = mService.isUidForeground(
+                realCallingUid);
         final boolean isRealCallingUidPersistentSystemProcess = isUidPersistentSystemProcess(
                 realCallingUid);
         if (realCallingUid != callingUid) {
@@ -949,8 +962,8 @@
         if (mSupervisor.mRecentTasks.isCallerRecents(callingUid)) {
             return false;
         }
-        // anything that has fallen through will currently be aborted
-        Slog.w(TAG, "Blocking background activity start [callingPackage: " + callingPackage
+        // anything that has fallen through would currently be aborted
+        Slog.w(TAG, "Background activity start [callingPackage: " + callingPackage
                 + "; callingUid: " + callingUid
                 + "; isCallingUidForeground: " + isCallingUidForeground
                 + "; isCallingUidPersistentSystemProcess: " + isCallingUidPersistentSystemProcess
@@ -962,24 +975,15 @@
                 + "; isBgStartWhitelisted: " + allowBackgroundActivityStart
                 + "; intent: " + intent
                 + "]");
-        // TODO: remove this toast after feature development is done
-        mService.mUiHandler.post(() -> {
-            Toast.makeText(mService.mContext,
-                    "Blocking background activity start for " + callingPackage,
-                    Toast.LENGTH_SHORT).show();
-        });
+        maybeLogActivityStart(callingUid, callingPackage, realCallingUid, intent, callerApp,
+                null /*r*/, originatingPendingIntent, true /*abortedStart*/);
         return true;
     }
 
-    /** Returns true if uid has a visible window or its process is in a top state. */
-    private boolean isUidForeground(int uid) {
-        return (mService.getUidStateLocked(uid) == ActivityManager.PROCESS_STATE_TOP)
-            || mService.mWindowManager.mRoot.isAnyNonToastWindowVisibleForUid(uid);
-    }
-
     /** Returns true if uid is in a persistent state. */
     private boolean isUidPersistentSystemProcess(int uid) {
-        return (mService.getUidStateLocked(uid) <= ActivityManager.PROCESS_STATE_PERSISTENT_UI);
+        return (uid == Process.SYSTEM_UID)
+                || (mService.getUidStateLocked(uid) <= ActivityManager.PROCESS_STATE_PERSISTENT_UI);
     }
 
     private void maybeLogActivityStart(int callingUid, String callingPackage, int realCallingUid,
@@ -1629,7 +1633,7 @@
                 // Also, we don't want to resume activities in a task that currently has an overlay
                 // as the starting activity just needs to be in the visible paused state until the
                 // over is removed.
-                mTargetStack.ensureActivitiesVisibleLocked(null, 0, !PRESERVE_WINDOWS);
+                mTargetStack.ensureActivitiesVisibleLocked(mStartActivity, 0, !PRESERVE_WINDOWS);
                 // Go ahead and tell window manager to execute app transition for this activity
                 // since the app transition will not be triggered through the resume channel.
                 mTargetStack.getDisplay().mDisplayContent.executeAppTransition();
diff --git a/services/core/java/com/android/server/wm/ActivityTaskManagerInternal.java b/services/core/java/com/android/server/wm/ActivityTaskManagerInternal.java
index 67b00b2..1a5e6a1 100644
--- a/services/core/java/com/android/server/wm/ActivityTaskManagerInternal.java
+++ b/services/core/java/com/android/server/wm/ActivityTaskManagerInternal.java
@@ -489,4 +489,7 @@
      */
     public abstract ActivityManager.TaskSnapshot getTaskSnapshot(int taskId,
             boolean reducedResolution);
+
+    /** Returns true if uid has a visible window or its process is in a top state. */
+    public abstract boolean isUidForeground(int uid);
 }
diff --git a/services/core/java/com/android/server/wm/ActivityTaskManagerService.java b/services/core/java/com/android/server/wm/ActivityTaskManagerService.java
index 2affa97..5fabde4 100644
--- a/services/core/java/com/android/server/wm/ActivityTaskManagerService.java
+++ b/services/core/java/com/android/server/wm/ActivityTaskManagerService.java
@@ -4399,6 +4399,27 @@
         }
     }
 
+    @Override
+    public void registerRemoteAnimationsForDisplay(int displayId,
+            RemoteAnimationDefinition definition) {
+        mAmInternal.enforceCallingPermission(CONTROL_REMOTE_APP_TRANSITION_ANIMATIONS,
+                "registerRemoteAnimations");
+        definition.setCallingPid(Binder.getCallingPid());
+        synchronized (mGlobalLock) {
+            final ActivityDisplay display = mRootActivityContainer.getActivityDisplay(displayId);
+            if (display == null) {
+                Slog.e(TAG, "Couldn't find display with id: " + displayId);
+                return;
+            }
+            final long origId = Binder.clearCallingIdentity();
+            try {
+                display.mDisplayContent.registerRemoteAnimations(definition);
+            } finally {
+                Binder.restoreCallingIdentity(origId);
+            }
+        }
+    }
+
     /** @see android.app.ActivityManager#alwaysShowUnsupportedCompileSdkWarning */
     @Override
     public void alwaysShowUnsupportedCompileSdkWarning(ComponentName activity) {
@@ -5632,6 +5653,11 @@
         return mActiveUids.get(uid, PROCESS_STATE_NONEXISTENT);
     }
 
+    boolean isUidForeground(int uid) {
+        return (getUidStateLocked(uid) == ActivityManager.PROCESS_STATE_TOP)
+                || mWindowManager.mRoot.isAnyNonToastWindowVisibleForUid(uid);
+    }
+
     /**
      * @return whitelist tag for a uid from mPendingTempWhitelist, null if not currently on
      * the whitelist
@@ -6222,30 +6248,27 @@
                 }
                 return;
             }
-            mH.post(() -> {
-                synchronized (mGlobalLock) {
-                    final ActivityDisplay activityDisplay =
-                            mRootActivityContainer.getActivityDisplay(displayId);
-                    if (activityDisplay == null) {
-                        // Call might come when display is not yet added or has been removed.
-                        if (DEBUG_CONFIGURATION) {
-                            Slog.w(TAG, "Trying to update display configuration for non-existing "
-                                    + "displayId=" + displayId);
-                        }
-                        return;
+            synchronized (mGlobalLock) {
+                final ActivityDisplay activityDisplay =
+                        mRootActivityContainer.getActivityDisplay(displayId);
+                if (activityDisplay == null) {
+                    // Call might come when display is not yet added or has been removed.
+                    if (DEBUG_CONFIGURATION) {
+                        Slog.w(TAG, "Trying to update display configuration for non-existing "
+                                + "displayId=" + displayId);
                     }
-                    final WindowProcessController process = mPidMap.get(pid);
-                    if (process == null) {
-                        if (DEBUG_CONFIGURATION) {
-                            Slog.w(TAG, "Trying to update display configuration for invalid "
-                                    + "process, pid=" + pid);
-                        }
-                        return;
-                    }
-                    process.registerDisplayConfigurationListenerLocked(activityDisplay);
+                    return;
                 }
-            });
-
+                final WindowProcessController process = mPidMap.get(pid);
+                if (process == null) {
+                    if (DEBUG_CONFIGURATION) {
+                        Slog.w(TAG, "Trying to update display configuration for invalid "
+                                + "process, pid=" + pid);
+                    }
+                    return;
+                }
+                process.registerDisplayConfigurationListenerLocked(activityDisplay);
+            }
         }
 
         @Override
@@ -7044,5 +7067,12 @@
                 return ActivityTaskManagerService.this.getTaskSnapshot(taskId, reducedResolution);
             }
         }
+
+        @Override
+        public boolean isUidForeground(int uid) {
+            synchronized (mGlobalLock) {
+                return ActivityTaskManagerService.this.isUidForeground(uid);
+            }
+        }
     }
 }
diff --git a/services/core/java/com/android/server/wm/AppTransition.java b/services/core/java/com/android/server/wm/AppTransition.java
index 5f393ef..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;
@@ -174,6 +175,7 @@
     private int mLastUsedAppTransition = TRANSIT_UNSET;
     private String mLastOpeningApp;
     private String mLastClosingApp;
+    private String mLastChangingApp;
 
     private static final int NEXT_TRANSIT_TYPE_NONE = 0;
     private static final int NEXT_TRANSIT_TYPE_CUSTOM = 1;
@@ -318,14 +320,16 @@
     private void setAppTransition(int transit, int flags) {
         mNextAppTransition = transit;
         mNextAppTransitionFlags |= flags;
-        setLastAppTransition(TRANSIT_UNSET, null, null);
+        setLastAppTransition(TRANSIT_UNSET, null, null, null);
         updateBooster();
     }
 
-    void setLastAppTransition(int transit, AppWindowToken openingApp, AppWindowToken closingApp) {
+    void setLastAppTransition(int transit, AppWindowToken openingApp, AppWindowToken closingApp,
+            AppWindowToken changingApp) {
         mLastUsedAppTransition = transit;
         mLastOpeningApp = "" + openingApp;
         mLastClosingApp = "" + closingApp;
+        mLastChangingApp = "" + changingApp;
     }
 
     boolean isReady() {
@@ -412,9 +416,7 @@
      * @return bit-map of WindowManagerPolicy#FINISH_LAYOUT_REDO_* to indicate whether another
      *         layout pass needs to be done
      */
-    int goodToGo(int transit, AppWindowToken topOpeningApp,
-            AppWindowToken topClosingApp, ArraySet<AppWindowToken> openingApps,
-            ArraySet<AppWindowToken> closingApps) {
+    int goodToGo(int transit, AppWindowToken topOpeningApp, ArraySet<AppWindowToken> openingApps) {
         mNextAppTransition = TRANSIT_UNSET;
         mNextAppTransitionFlags = 0;
         setAppTransitionState(APP_STATE_RUNNING);
@@ -422,8 +424,6 @@
                 ? topOpeningApp.getAnimation()
                 : null;
         int redoLayout = notifyAppTransitionStartingLocked(transit,
-                topOpeningApp != null ? topOpeningApp.token : null,
-                topClosingApp != null ? topClosingApp.token : null,
                 topOpeningAnim != null ? topOpeningAnim.getDurationHint() : 0,
                 topOpeningAnim != null
                         ? topOpeningAnim.getStatusBarTransitionsStartTime()
@@ -500,13 +500,12 @@
         }
     }
 
-    private int notifyAppTransitionStartingLocked(int transit, IBinder openToken,
-            IBinder closeToken, long duration, long statusBarAnimationStartTime,
-            long statusBarAnimationDuration) {
+    private int notifyAppTransitionStartingLocked(int transit, long duration,
+            long statusBarAnimationStartTime, long statusBarAnimationDuration) {
         int redoLayout = 0;
         for (int i = 0; i < mListeners.size(); i++) {
-            redoLayout |= mListeners.get(i).onAppTransitionStartingLocked(transit, openToken,
-                    closeToken, duration, statusBarAnimationStartTime, statusBarAnimationDuration);
+            redoLayout |= mListeners.get(i).onAppTransitionStartingLocked(transit, duration,
+                    statusBarAnimationStartTime, statusBarAnimationDuration);
         }
         return redoLayout;
     }
@@ -1664,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) {
@@ -2128,6 +2136,8 @@
                     pw.println(mLastOpeningApp);
             pw.print(prefix); pw.print("mLastClosingApp=");
                     pw.println(mLastClosingApp);
+            pw.print(prefix); pw.print("mLastChangingApp=");
+            pw.println(mLastChangingApp);
         }
     }
 
@@ -2226,14 +2236,16 @@
             if (dc == null) {
                 return;
             }
-            if (isTransitionSet() || !dc.mOpeningApps.isEmpty() || !dc.mClosingApps.isEmpty()) {
+            if (isTransitionSet() || !dc.mOpeningApps.isEmpty() || !dc.mClosingApps.isEmpty()
+                    || !dc.mChangingApps.isEmpty()) {
                 if (DEBUG_APP_TRANSITIONS) {
                     Slog.v(TAG_WM, "*** APP TRANSITION TIMEOUT."
                             + " displayId=" + dc.getDisplayId()
                             + " isTransitionSet()="
                             + dc.mAppTransition.isTransitionSet()
                             + " mOpeningApps.size()=" + dc.mOpeningApps.size()
-                            + " mClosingApps.size()=" + dc.mClosingApps.size());
+                            + " mClosingApps.size()=" + dc.mClosingApps.size()
+                            + " mChangingApps.size()=" + dc.mChangingApps.size());
                 }
                 setTimeout();
                 mService.mWindowPlacerLocked.performSurfacePlacement();
diff --git a/services/core/java/com/android/server/wm/AppTransitionController.java b/services/core/java/com/android/server/wm/AppTransitionController.java
index bf00ffb..8f0a7c0 100644
--- a/services/core/java/com/android/server/wm/AppTransitionController.java
+++ b/services/core/java/com/android/server/wm/AppTransitionController.java
@@ -49,8 +49,8 @@
 import static com.android.server.wm.WindowManagerDebugConfig.SHOW_LIGHT_TRANSACTIONS;
 import static com.android.server.wm.WindowManagerDebugConfig.TAG_WITH_CLASS_NAME;
 import static com.android.server.wm.WindowManagerDebugConfig.TAG_WM;
-import static com.android.server.wm.WindowManagerService.H.NOTIFY_APP_TRANSITION_STARTING;
 
+import android.os.SystemClock;
 import android.os.Trace;
 import android.util.ArraySet;
 import android.util.Slog;
@@ -76,6 +76,7 @@
     private final WindowManagerService mService;
     private final DisplayContent mDisplayContent;
     private final WallpaperController mWallpaperControllerLocked;
+    private RemoteAnimationDefinition mRemoteAnimationDefinition = null;
 
     private final SparseIntArray mTempTransitionReasons = new SparseIntArray();
 
@@ -85,12 +86,17 @@
         mWallpaperControllerLocked = mDisplayContent.mWallpaperController;
     }
 
+    void registerRemoteAnimations(RemoteAnimationDefinition definition) {
+        mRemoteAnimationDefinition = definition;
+    }
+
     /**
      * Handle application transition for given display.
      */
     void handleAppTransitionReady() {
-        final int appsCount = mDisplayContent.mOpeningApps.size();
-        if (!transitionGoodToGo(appsCount, mTempTransitionReasons)) {
+        mTempTransitionReasons.clear();
+        if (!transitionGoodToGo(mDisplayContent.mOpeningApps, mTempTransitionReasons)
+                || !transitionGoodToGo(mDisplayContent.mChangingApps, mTempTransitionReasons)) {
             return;
         }
         Trace.traceBegin(Trace.TRACE_TAG_WINDOW_MANAGER, "AppTransitionReady");
@@ -108,21 +114,25 @@
 
         mDisplayContent.mWallpaperMayChange = false;
 
-        int i;
-        for (i = 0; i < appsCount; i++) {
-            final AppWindowToken wtoken = mDisplayContent.mOpeningApps.valueAt(i);
+        int appCount = mDisplayContent.mOpeningApps.size();
+        for (int i = 0; i < appCount; ++i) {
             // Clearing the mAnimatingExit flag before entering animation. It's set to true if app
             // window is removed, or window relayout to invisible. This also affects window
             // visibility. We need to clear it *before* maybeUpdateTransitToWallpaper() as the
             // transition selection depends on wallpaper target visibility.
-            wtoken.clearAnimatingFlags();
+            mDisplayContent.mOpeningApps.valueAtUnchecked(i).clearAnimatingFlags();
+        }
+        appCount = mDisplayContent.mChangingApps.size();
+        for (int i = 0; i < appCount; ++i) {
+            // Clearing for same reason as above.
+            mDisplayContent.mChangingApps.valueAtUnchecked(i).clearAnimatingFlags();
         }
 
         // Adjust wallpaper before we pull the lower/upper target, since pending changes
         // (like the clearAnimatingFlags() above) might affect wallpaper target result.
         // Or, the opening app window should be a wallpaper target.
         mWallpaperControllerLocked.adjustWallpaperWindowsForAppTransitionIfNeeded(
-                mDisplayContent.mOpeningApps);
+                mDisplayContent.mOpeningApps, mDisplayContent.mChangingApps);
 
         // Determine if closing and opening app token sets are wallpaper targets, in which case
         // special animations are needed.
@@ -141,7 +151,7 @@
         // no need to do an animation. This is the case, for example, when this transition is being
         // done behind a dream window.
         final ArraySet<Integer> activityTypes = collectActivityTypes(mDisplayContent.mOpeningApps,
-                mDisplayContent.mClosingApps);
+                mDisplayContent.mClosingApps, mDisplayContent.mChangingApps);
         final boolean allowAnimations = mDisplayContent.getDisplayPolicy().allowAppAnimationsLw();
         final AppWindowToken animLpToken = allowAnimations
                 ? findAnimLayoutParamsToken(transit, activityTypes)
@@ -152,11 +162,15 @@
         final AppWindowToken topClosingApp = allowAnimations
                 ? getTopApp(mDisplayContent.mClosingApps, false /* ignoreHidden */)
                 : null;
+        final AppWindowToken topChangingApp = allowAnimations
+                ? getTopApp(mDisplayContent.mChangingApps, false /* ignoreHidden */)
+                : null;
         final WindowManager.LayoutParams animLp = getAnimLp(animLpToken);
         overrideWithRemoteAnimationIfSet(animLpToken, transit, activityTypes);
 
         final boolean voiceInteraction = containsVoiceInteraction(mDisplayContent.mOpeningApps)
-                || containsVoiceInteraction(mDisplayContent.mOpeningApps);
+                || containsVoiceInteraction(mDisplayContent.mOpeningApps)
+                || containsVoiceInteraction(mDisplayContent.mChangingApps);
 
         final int layoutRedo;
         mService.mSurfaceAnimationRunner.deferStartingAnimations();
@@ -165,13 +179,14 @@
 
             handleClosingApps(transit, animLp, voiceInteraction);
             handleOpeningApps(transit, animLp, voiceInteraction);
+            handleChangingApps(transit, animLp, voiceInteraction);
 
             appTransition.setLastAppTransition(transit, topOpeningApp,
-                    topClosingApp);
+                    topClosingApp, topChangingApp);
 
             final int flags = appTransition.getTransitFlags();
             layoutRedo = appTransition.goodToGo(transit, topOpeningApp,
-                    topClosingApp, mDisplayContent.mOpeningApps, mDisplayContent.mClosingApps);
+                    mDisplayContent.mOpeningApps);
             handleNonAppWindowsInTransition(transit, flags);
             appTransition.postAnimationCallback();
             appTransition.clear();
@@ -183,6 +198,7 @@
 
         mDisplayContent.mOpeningApps.clear();
         mDisplayContent.mClosingApps.clear();
+        mDisplayContent.mChangingApps.clear();
         mDisplayContent.mUnknownAppVisibilityController.clear();
 
         // This has changed the visibility of windows, so perform
@@ -191,8 +207,8 @@
 
         mDisplayContent.computeImeTarget(true /* updateImeTarget */);
 
-        mService.mH.obtainMessage(NOTIFY_APP_TRANSITION_STARTING,
-                mTempTransitionReasons.clone()).sendToTarget();
+        mService.mAtmInternal.notifyAppTransitionStarting(mTempTransitionReasons.clone(),
+                SystemClock.uptimeMillis());
 
         Trace.traceEnd(Trace.TRACE_TAG_WINDOW_MANAGER);
 
@@ -205,6 +221,21 @@
         return mainWindow != null ? mainWindow.mAttrs : null;
     }
 
+    RemoteAnimationAdapter getRemoteAnimationOverride(AppWindowToken animLpToken, int transit,
+            ArraySet<Integer> activityTypes) {
+        final RemoteAnimationDefinition definition = animLpToken.getRemoteAnimationDefinition();
+        if (definition != null) {
+            final RemoteAnimationAdapter adapter = definition.getAdapter(transit, activityTypes);
+            if (adapter != null) {
+                return adapter;
+            }
+        }
+        if (mRemoteAnimationDefinition == null) {
+            return null;
+        }
+        return mRemoteAnimationDefinition.getAdapter(transit, activityTypes);
+    }
+
     /**
      * Overrides the pending transition with the remote animation defined for the transition in the
      * set of defined remote animations in the app window token.
@@ -218,11 +249,8 @@
         if (animLpToken == null) {
             return;
         }
-        final RemoteAnimationDefinition definition = animLpToken.getRemoteAnimationDefinition();
-        if (definition == null) {
-            return;
-        }
-        final RemoteAnimationAdapter adapter = definition.getAdapter(transit, activityTypes);
+        final RemoteAnimationAdapter adapter =
+                getRemoteAnimationOverride(animLpToken, transit, activityTypes);
         if (adapter != null) {
             animLpToken.getDisplayContent().mAppTransition.overridePendingAppTransitionRemote(
                     adapter);
@@ -237,29 +265,30 @@
         AppWindowToken result;
         final ArraySet<AppWindowToken> closingApps = mDisplayContent.mClosingApps;
         final ArraySet<AppWindowToken> openingApps = mDisplayContent.mOpeningApps;
+        final ArraySet<AppWindowToken> changingApps = mDisplayContent.mChangingApps;
 
         // Remote animations always win, but fullscreen tokens override non-fullscreen tokens.
-        result = lookForHighestTokenWithFilter(closingApps, openingApps,
+        result = lookForHighestTokenWithFilter(closingApps, openingApps, changingApps,
                 w -> w.getRemoteAnimationDefinition() != null
                         && w.getRemoteAnimationDefinition().hasTransition(transit, activityTypes));
         if (result != null) {
             return result;
         }
-        result = lookForHighestTokenWithFilter(closingApps, openingApps,
+        result = lookForHighestTokenWithFilter(closingApps, openingApps, changingApps,
                 w -> w.fillsParent() && w.findMainWindow() != null);
         if (result != null) {
             return result;
         }
-        return lookForHighestTokenWithFilter(closingApps, openingApps,
+        return lookForHighestTokenWithFilter(closingApps, openingApps, changingApps,
                 w -> w.findMainWindow() != null);
     }
 
     /**
      * @return The set of {@link android.app.WindowConfiguration.ActivityType}s contained in the set
-     *         of apps in {@code array1} and {@code array2}.
+     *         of apps in {@code array1}, {@code array2}, and {@code array3}.
      */
     private static ArraySet<Integer> collectActivityTypes(ArraySet<AppWindowToken> array1,
-            ArraySet<AppWindowToken> array2) {
+            ArraySet<AppWindowToken> array2, ArraySet<AppWindowToken> array3) {
         final ArraySet<Integer> result = new ArraySet<>();
         for (int i = array1.size() - 1; i >= 0; i--) {
             result.add(array1.valueAt(i).getActivityType());
@@ -267,19 +296,26 @@
         for (int i = array2.size() - 1; i >= 0; i--) {
             result.add(array2.valueAt(i).getActivityType());
         }
+        for (int i = array3.size() - 1; i >= 0; i--) {
+            result.add(array3.valueAt(i).getActivityType());
+        }
         return result;
     }
 
     private static AppWindowToken lookForHighestTokenWithFilter(ArraySet<AppWindowToken> array1,
-            ArraySet<AppWindowToken> array2, Predicate<AppWindowToken> filter) {
-        final int array1count = array1.size();
-        final int count = array1count + array2.size();
+            ArraySet<AppWindowToken> array2, ArraySet<AppWindowToken> array3,
+            Predicate<AppWindowToken> filter) {
+        final int array2base = array1.size();
+        final int array3base = array2.size() + array2base;
+        final int count = array3base + array3.size();
         int bestPrefixOrderIndex = Integer.MIN_VALUE;
         AppWindowToken bestToken = null;
         for (int i = 0; i < count; i++) {
-            final AppWindowToken wtoken = i < array1count
+            final AppWindowToken wtoken = i < array2base
                     ? array1.valueAt(i)
-                    : array2.valueAt(i - array1count);
+                    : (i < array3base
+                            ? array2.valueAt(i - array2base)
+                            : array3.valueAt(i - array3base));
             final int prefixOrderIndex = wtoken.getPrefixOrderIndex();
             if (filter.test(wtoken) && prefixOrderIndex > bestPrefixOrderIndex) {
                 bestPrefixOrderIndex = prefixOrderIndex;
@@ -360,6 +396,24 @@
         }
     }
 
+    private void handleChangingApps(int transit, LayoutParams animLp, boolean voiceInteraction) {
+        final ArraySet<AppWindowToken> apps = mDisplayContent.mChangingApps;
+        final int appsCount = apps.size();
+        for (int i = 0; i < appsCount; i++) {
+            AppWindowToken wtoken = apps.valueAt(i);
+            if (DEBUG_APP_TRANSITIONS) Slog.v(TAG, "Now changing app" + wtoken);
+            wtoken.cancelAnimationOnly();
+            wtoken.applyAnimationLocked(null, transit, true, false);
+            wtoken.updateReportedVisibilityLocked();
+            mService.openSurfaceTransaction();
+            try {
+                wtoken.showAllWindowsLocked();
+            } finally {
+                mService.closeSurfaceTransaction("handleChangingApps");
+            }
+        }
+    }
+
     private void handleNonAppWindowsInTransition(int transit, int flags) {
         if (transit == TRANSIT_KEYGUARD_GOING_AWAY) {
             if ((flags & TRANSIT_FLAG_KEYGUARD_GOING_AWAY_WITH_WALLPAPER) != 0
@@ -379,16 +433,15 @@
         }
     }
 
-    private boolean transitionGoodToGo(int appsCount, SparseIntArray outReasons) {
+    private boolean transitionGoodToGo(ArraySet<AppWindowToken> apps, SparseIntArray outReasons) {
         if (DEBUG_APP_TRANSITIONS) Slog.v(TAG,
-                "Checking " + appsCount + " opening apps (frozen="
+                "Checking " + apps.size() + " opening apps (frozen="
                         + mService.mDisplayFrozen + " timeout="
                         + mDisplayContent.mAppTransition.isTimeout() + ")...");
         final ScreenRotationAnimation screenRotationAnimation =
                 mService.mAnimator.getScreenRotationAnimationLocked(
                         Display.DEFAULT_DISPLAY);
 
-        outReasons.clear();
         if (!mDisplayContent.mAppTransition.isTimeout()) {
             // Imagine the case where we are changing orientation due to an app transition, but a
             // previous orientation change is still in progress. We won't process the orientation
@@ -404,8 +457,8 @@
                 }
                 return false;
             }
-            for (int i = 0; i < appsCount; i++) {
-                AppWindowToken wtoken = mDisplayContent.mOpeningApps.valueAt(i);
+            for (int i = 0; i < apps.size(); i++) {
+                AppWindowToken wtoken = apps.valueAt(i);
                 if (DEBUG_APP_TRANSITIONS) Slog.v(TAG,
                         "Check opening app=" + wtoken + ": allDrawn="
                                 + wtoken.allDrawn + " startingDisplayed="
diff --git a/services/core/java/com/android/server/wm/AppWindowThumbnail.java b/services/core/java/com/android/server/wm/AppWindowThumbnail.java
index bb38f30..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;
@@ -50,9 +53,23 @@
     private final SurfaceAnimator mSurfaceAnimator;
     private final int mWidth;
     private final int mHeight;
+    private final boolean mRelative;
 
     AppWindowThumbnail(Transaction t, AppWindowToken appToken, GraphicBuffer thumbnailHeader) {
+        this(t, appToken, thumbnailHeader, false /* relative */);
+    }
+
+    /**
+     * @param t Transaction to create the thumbnail in.
+     * @param appToken {@link AppWindowToken} to associate this thumbnail with.
+     * @param thumbnailHeader A thumbnail or placeholder for thumbnail to initialize with.
+     * @param relative Whether this thumbnail will be a child of appToken (and thus positioned
+     *                 relative to it) or not.
+     */
+    AppWindowThumbnail(Transaction t, AppWindowToken appToken, GraphicBuffer thumbnailHeader,
+            boolean relative) {
         mAppToken = appToken;
+        mRelative = relative;
         mSurfaceAnimator =
                 new SurfaceAnimator(this, this::onAnimationFinished, appToken.mWmService);
         mWidth = thumbnailHeader.getWidth();
@@ -68,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();
 
@@ -86,6 +104,9 @@
         // We parent the thumbnail to the task, and just place it on top of anything else in the
         // task.
         t.setLayer(mSurfaceControl, Integer.MAX_VALUE);
+        if (relative) {
+            t.reparent(mSurfaceControl, appToken.getSurfaceControl());
+        }
     }
 
     void startAnimation(Transaction t, Animation anim) {
@@ -101,6 +122,13 @@
                 mAppToken.mWmService.mSurfaceAnimationRunner), false /* hidden */);
     }
 
+    /**
+     * Start animation with existing adapter.
+     */
+    void startAnimation(Transaction t, AnimationAdapter anim, boolean hidden) {
+        mSurfaceAnimator.startAnimation(t, anim, hidden);
+    }
+
     private void onAnimationFinished() {
     }
 
@@ -147,6 +175,9 @@
     @Override
     public void onAnimationLeashCreated(Transaction t, SurfaceControl leash) {
         t.setLayer(leash, Integer.MAX_VALUE);
+        if (mRelative) {
+            t.reparent(leash, mAppToken.getSurfaceControl());
+        }
     }
 
     @Override
diff --git a/services/core/java/com/android/server/wm/AppWindowToken.java b/services/core/java/com/android/server/wm/AppWindowToken.java
index 750c5ca..a52f1af 100644
--- a/services/core/java/com/android/server/wm/AppWindowToken.java
+++ b/services/core/java/com/android/server/wm/AppWindowToken.java
@@ -16,6 +16,7 @@
 
 package com.android.server.wm;
 
+import static android.app.WindowConfiguration.WINDOWING_MODE_FREEFORM;
 import static android.app.WindowConfiguration.WINDOWING_MODE_PINNED;
 import static android.app.WindowConfiguration.WINDOWING_MODE_SPLIT_SCREEN_PRIMARY;
 import static android.app.WindowConfiguration.WINDOWING_MODE_UNDEFINED;
@@ -33,6 +34,7 @@
 import static android.view.WindowManager.LayoutParams.TYPE_APPLICATION_STARTING;
 import static android.view.WindowManager.LayoutParams.TYPE_BASE_APPLICATION;
 import static android.view.WindowManager.TRANSIT_DOCK_TASK_FROM_RECENTS;
+import static android.view.WindowManager.TRANSIT_TASK_CHANGE_WINDOWING_MODE;
 import static android.view.WindowManager.TRANSIT_UNSET;
 import static android.view.WindowManager.TRANSIT_WALLPAPER_OPEN;
 
@@ -97,11 +99,13 @@
 import android.os.RemoteException;
 import android.os.SystemClock;
 import android.os.Trace;
+import android.util.ArraySet;
 import android.util.Slog;
 import android.util.proto.ProtoOutputStream;
 import android.view.DisplayInfo;
 import android.view.IApplicationToken;
 import android.view.InputApplicationHandle;
+import android.view.RemoteAnimationAdapter;
 import android.view.RemoteAnimationDefinition;
 import android.view.SurfaceControl;
 import android.view.SurfaceControl.Transaction;
@@ -117,6 +121,7 @@
 import com.android.server.display.ColorDisplayService;
 import com.android.server.policy.WindowManagerPolicy;
 import com.android.server.policy.WindowManagerPolicy.StartingSurface;
+import com.android.server.wm.RemoteAnimationController.RemoteAnimationRecord;
 import com.android.server.wm.WindowManagerService.H;
 
 import java.io.PrintWriter;
@@ -261,7 +266,18 @@
     /** Whether our surface was set to be showing in the last call to {@link #prepareSurfaces} */
     private boolean mLastSurfaceShowing = true;
 
+    /**
+     * This gets used during some open/close transitions as well as during a change transition
+     * where it represents the starting-state snapshot.
+     */
     private AppWindowThumbnail mThumbnail;
+    private final Rect mTransitStartRect = new Rect();
+
+    /**
+     * This leash is used to "freeze" the app surface in place after the state change, but before
+     * the animation is ready to start.
+     */
+    private SurfaceControl mTransitChangeLeash = null;
 
     /** Have we been asked to have this token keep the screen frozen? */
     private boolean mFreezingScreen;
@@ -272,6 +288,7 @@
 
     private final Point mTmpPoint = new Point();
     private final Rect mTmpRect = new Rect();
+    private final Rect mTmpPrevBounds = new Rect();
     private RemoteAnimationDefinition mRemoteAnimationDefinition;
     private AnimatingAppWindowTokenRegistry mAnimatingAppWindowTokenRegistry;
 
@@ -810,6 +827,7 @@
         boolean delayed = commitVisibility(null, false, TRANSIT_UNSET, true, mVoiceInteraction);
 
         getDisplayContent().mOpeningApps.remove(this);
+        getDisplayContent().mChangingApps.remove(this);
         getDisplayContent().mUnknownAppVisibilityController.appRemovedOrHidden(this);
         mWmService.mTaskSnapshotController.onAppRemoved(this);
         waitingToShow = false;
@@ -1528,6 +1546,7 @@
     @Override
     public void onConfigurationChanged(Configuration newParentConfig) {
         final int prevWinMode = getWindowingMode();
+        mTmpPrevBounds.set(getBounds());
         super.onConfigurationChanged(newParentConfig);
         final int winMode = getWindowingMode();
 
@@ -1559,9 +1578,84 @@
                 mDisplayContent.mPinnedStackControllerLocked.saveReentrySnapFraction(this,
                         stackBounds);
             }
+        } else if (shouldStartChangeTransition(prevWinMode, winMode)) {
+            initializeChangeTransition(mTmpPrevBounds);
         }
     }
 
+    private boolean shouldStartChangeTransition(int prevWinMode, int newWinMode) {
+        if (!isVisible() || getDisplayContent().mAppTransition.isTransitionSet()) {
+            return false;
+        }
+        // Only do an animation into and out-of freeform mode for now. Other mode
+        // transition animations are currently handled by system-ui.
+        return (prevWinMode == WINDOWING_MODE_FREEFORM) != (newWinMode == WINDOWING_MODE_FREEFORM);
+    }
+
+    /**
+     * Initializes a change transition. Because the app is visible already, there is a small period
+     * of time where the user can see the app content/window update before the transition starts.
+     * To prevent this, we immediately take a snapshot and place the app/snapshot into a leash which
+     * "freezes" the location/crop until the transition starts.
+     * <p>
+     * Here's a walk-through of the process:
+     * 1. Create a temporary leash ("interim-change-leash") and reparent the app to it.
+     * 2. Set the temporary leash's position/crop to the current state.
+     * 3. Create a snapshot and place that at the top of the leash to cover up content changes.
+     * 4. Once the transition is ready, it will reparent the app to the animation leash.
+     * 5. Detach the interim-change-leash.
+     */
+    private void initializeChangeTransition(Rect startBounds) {
+        mDisplayContent.prepareAppTransition(TRANSIT_TASK_CHANGE_WINDOWING_MODE,
+                false /* alwaysKeepCurrent */, 0, false /* forceOverride */);
+        mDisplayContent.mChangingApps.add(this);
+        mTransitStartRect.set(startBounds);
+
+        final SurfaceControl.Builder builder = makeAnimationLeash()
+                .setParent(getAnimationLeashParent())
+                .setName(getSurfaceControl() + " - interim-change-leash");
+        mTransitChangeLeash = builder.build();
+        Transaction t = getPendingTransaction();
+        t.setWindowCrop(mTransitChangeLeash, startBounds.width(), startBounds.height());
+        t.setPosition(mTransitChangeLeash, startBounds.left, startBounds.top);
+        t.show(mTransitChangeLeash);
+        t.reparent(getSurfaceControl(), mTransitChangeLeash);
+        onAnimationLeashCreated(t, mTransitChangeLeash);
+
+        // Skip creating snapshot if this transition is controlled by a remote animator which
+        // doesn't need it.
+        ArraySet<Integer> activityTypes = new ArraySet<>();
+        activityTypes.add(getActivityType());
+        RemoteAnimationAdapter adapter =
+                mDisplayContent.mAppTransitionController.getRemoteAnimationOverride(
+                        this, TRANSIT_TASK_CHANGE_WINDOWING_MODE, activityTypes);
+        if (adapter != null && !adapter.getChangeNeedsSnapshot()) {
+            return;
+        }
+
+        if (mThumbnail == null && getTask() != null) {
+            final TaskSnapshotController snapshotCtrl = mWmService.mTaskSnapshotController;
+            final ArraySet<Task> tasks = new ArraySet<>();
+            tasks.add(getTask());
+            snapshotCtrl.snapshotTasks(tasks);
+            snapshotCtrl.addSkipClosingAppSnapshotTasks(tasks);
+            final ActivityManager.TaskSnapshot snapshot = snapshotCtrl.getSnapshot(
+                    getTask().mTaskId, getTask().mUserId, false /* restoreFromDisk */,
+                    false /* reducedResolution */);
+            mThumbnail = new AppWindowThumbnail(t, this, snapshot.getSnapshot(),
+                    true /* relative */);
+        }
+    }
+
+    boolean isInChangeTransition() {
+        return mTransitChangeLeash != null || isChangeTransition(mTransit);
+    }
+
+    @VisibleForTesting
+    AppWindowThumbnail getThumbnail() {
+        return mThumbnail;
+    }
+
     @Override
     void checkAppWindowsReadyToShow() {
         if (allDrawn == mLastAllDrawn) {
@@ -2242,6 +2336,10 @@
         return getBounds();
     }
 
+    private static boolean isChangeTransition(int transit) {
+        return transit == TRANSIT_TASK_CHANGE_WINDOWING_MODE;
+    }
+
     boolean applyAnimationLocked(WindowManager.LayoutParams lp, int transit, boolean enter,
             boolean isVoiceInteraction) {
 
@@ -2260,13 +2358,37 @@
         Trace.traceBegin(TRACE_TAG_WINDOW_MANAGER, "AWT#applyAnimationLocked");
         if (okToAnimate()) {
             final AnimationAdapter adapter;
+            AnimationAdapter thumbnailAdapter = null;
             getAnimationBounds(mTmpPoint, mTmpRect);
 
+            boolean isChanging = isChangeTransition(transit) && enter;
+
             // Delaying animation start isn't compatible with remote animations at all.
             if (getDisplayContent().mAppTransition.getRemoteAnimationController() != null
                     && !mSurfaceAnimator.isAnimationStartDelayed()) {
-                adapter = getDisplayContent().mAppTransition.getRemoteAnimationController()
-                        .createAnimationAdapter(this, mTmpPoint, mTmpRect);
+                RemoteAnimationRecord adapters =
+                        getDisplayContent().mAppTransition.getRemoteAnimationController()
+                                .createRemoteAnimationRecord(this, mTmpPoint, mTmpRect,
+                                        (isChanging ? mTransitStartRect : null));
+                adapter = adapters.mAdapter;
+                thumbnailAdapter = adapters.mThumbnailAdapter;
+            } else if (isChanging) {
+                final float durationScale = mWmService.getTransitionAnimationScaleLocked();
+                mTmpRect.offsetTo(mTmpPoint.x, mTmpPoint.y);
+                adapter = new LocalAnimationAdapter(
+                        new WindowChangeAnimationSpec(mTransitStartRect, mTmpRect,
+                                getDisplayContent().getDisplayInfo(), durationScale,
+                                true /* isAppAnimation */, false /* isThumbnail */),
+                        mWmService.mSurfaceAnimationRunner);
+                if (mThumbnail != null) {
+                    thumbnailAdapter = new LocalAnimationAdapter(
+                            new WindowChangeAnimationSpec(mTransitStartRect, mTmpRect,
+                                    getDisplayContent().getDisplayInfo(), durationScale,
+                                    true /* isAppAnimation */, true /* isThumbnail */),
+                            mWmService.mSurfaceAnimationRunner);
+                }
+                mTransit = transit;
+                mTransitFlags = getDisplayContent().mAppTransition.getTransitFlags();
             } else {
                 final int appStackClipMode =
                         getDisplayContent().mAppTransition.getAppStackClipMode();
@@ -2294,6 +2416,10 @@
                 if (adapter.getShowWallpaper()) {
                     mDisplayContent.pendingLayoutChanges |= FINISH_LAYOUT_REDO_WALLPAPER;
                 }
+                if (thumbnailAdapter != null) {
+                    mThumbnail.startAnimation(
+                            getPendingTransaction(), thumbnailAdapter, !isVisible());
+                }
             }
         } else {
             cancelAnimation();
@@ -2429,6 +2555,17 @@
 
         final DisplayContent dc = getDisplayContent();
         dc.assignStackOrdering();
+
+        if (leash == mTransitChangeLeash) {
+            // This is a temporary state so skip any animation notifications
+            return;
+        } else if (mTransitChangeLeash != null) {
+            // unparent mTransitChangeLeash for clean-up
+            t.hide(mTransitChangeLeash);
+            t.reparent(mTransitChangeLeash, null);
+            mTransitChangeLeash = null;
+        }
+
         if (mAnimatingAppWindowTokenRegistry != null) {
             mAnimatingAppWindowTokenRegistry.notifyStarting(this);
         }
@@ -2487,6 +2624,12 @@
                 + " okToAnimate=" + okToAnimate()
                 + " startingDisplayed=" + startingDisplayed);
 
+        // clean up thumbnail window
+        if (mThumbnail != null) {
+            mThumbnail.destroy();
+            mThumbnail = null;
+        }
+
         // WindowState.onExitAnimationDone might modify the children list, so make a copy and then
         // traverse the copy.
         final ArrayList<WindowState> children = new ArrayList<>(mChildren);
@@ -2518,14 +2661,30 @@
 
     @Override
     void cancelAnimation() {
-        super.cancelAnimation();
+        cancelAnimationOnly();
         clearThumbnail();
+        if (mTransitChangeLeash != null) {
+            getPendingTransaction().hide(mTransitChangeLeash);
+            getPendingTransaction().reparent(mTransitChangeLeash, null);
+            mTransitChangeLeash = null;
+        }
+    }
+
+    /**
+     * This only cancels the animation. It doesn't do other teardown like cleaning-up thumbnail
+     * or interim leashes.
+     * <p>
+     * Used when canceling in preparation for starting a new animation.
+     */
+    void cancelAnimationOnly() {
+        super.cancelAnimation();
     }
 
     boolean isWaitingForTransitionStart() {
         return getDisplayContent().mAppTransition.isTransitionSet()
                 && (getDisplayContent().mOpeningApps.contains(this)
-                    || getDisplayContent().mClosingApps.contains(this));
+                    || getDisplayContent().mClosingApps.contains(this)
+                    || getDisplayContent().mChangingApps.contains(this));
     }
 
     public int getTransit() {
@@ -2835,6 +2994,7 @@
     void removeFromPendingTransition() {
         if (isWaitingForTransitionStart() && mDisplayContent != null) {
             mDisplayContent.mOpeningApps.remove(this);
+            mDisplayContent.mChangingApps.remove(this);
             mDisplayContent.mClosingApps.remove(this);
         }
     }
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 8fefd35..7a9ff52 100644
--- a/services/core/java/com/android/server/wm/DisplayContent.java
+++ b/services/core/java/com/android/server/wm/DisplayContent.java
@@ -162,6 +162,7 @@
 import android.view.InputDevice;
 import android.view.InsetsState.InternalInsetType;
 import android.view.MagnificationSpec;
+import android.view.RemoteAnimationDefinition;
 import android.view.Surface;
 import android.view.SurfaceControl;
 import android.view.SurfaceControl.Transaction;
@@ -250,6 +251,7 @@
 
     final ArraySet<AppWindowToken> mOpeningApps = new ArraySet<>();
     final ArraySet<AppWindowToken> mClosingApps = new ArraySet<>();
+    final ArraySet<AppWindowToken> mChangingApps = new ArraySet<>();
     final UnknownAppVisibilityController mUnknownAppVisibilityController;
     BoundsAnimationController mBoundsAnimationController;
 
@@ -1055,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);
     }
 
@@ -1090,6 +1087,10 @@
         return mLastWindowForcedOrientation;
     }
 
+    void registerRemoteAnimations(RemoteAnimationDefinition definition) {
+        mAppTransitionController.registerRemoteAnimations(definition);
+    }
+
     /**
      * Temporarily pauses rotation changes until resumed.
      *
@@ -2454,6 +2455,7 @@
             // Clear all transitions & screen frozen states when removing display.
             mOpeningApps.clear();
             mClosingApps.clear();
+            mChangingApps.clear();
             mUnknownAppVisibilityController.clear();
             mAppTransition.removeAppTransitionTimeoutCallbacks();
             handleAnimatingStoppedAndTransition();
@@ -3284,7 +3286,7 @@
             }
         }
 
-        if (!mOpeningApps.isEmpty() || !mClosingApps.isEmpty()) {
+        if (!mOpeningApps.isEmpty() || !mClosingApps.isEmpty() || !mChangingApps.isEmpty()) {
             pw.println();
             if (mOpeningApps.size() > 0) {
                 pw.print("  mOpeningApps="); pw.println(mOpeningApps);
@@ -3292,6 +3294,9 @@
             if (mClosingApps.size() > 0) {
                 pw.print("  mClosingApps="); pw.println(mClosingApps);
             }
+            if (mChangingApps.size() > 0) {
+                pw.print("  mChangingApps="); pw.println(mChangingApps);
+            }
         }
 
         mUnknownAppVisibilityController.dump(pw, "  ");
diff --git a/services/core/java/com/android/server/wm/DisplayPolicy.java b/services/core/java/com/android/server/wm/DisplayPolicy.java
index 6d3c693..bbf115f 100644
--- a/services/core/java/com/android/server/wm/DisplayPolicy.java
+++ b/services/core/java/com/android/server/wm/DisplayPolicy.java
@@ -58,6 +58,7 @@
 import static android.view.WindowManager.LayoutParams.SOFT_INPUT_ADJUST_NOTHING;
 import static android.view.WindowManager.LayoutParams.SOFT_INPUT_ADJUST_RESIZE;
 import static android.view.WindowManager.LayoutParams.SOFT_INPUT_MASK_ADJUST;
+import static android.view.WindowManager.LayoutParams.TYPE_APPLICATION_OVERLAY;
 import static android.view.WindowManager.LayoutParams.TYPE_BASE_APPLICATION;
 import static android.view.WindowManager.LayoutParams.TYPE_BOOT_PROGRESS;
 import static android.view.WindowManager.LayoutParams.TYPE_DOCK_DIVIDER;
@@ -2029,7 +2030,8 @@
                     of.set(displayFrames.mRestricted);
                     df.set(displayFrames.mRestricted);
                     pf.set(displayFrames.mRestricted);
-                } else if (type == TYPE_TOAST || type == TYPE_SYSTEM_ALERT) {
+                } else if (type == TYPE_TOAST || type == TYPE_SYSTEM_ALERT
+                        || type == TYPE_APPLICATION_OVERLAY) {
                     // These dialogs are stable to interim decor changes.
                     cf.set(displayFrames.mStable);
                     of.set(displayFrames.mStable);
@@ -2572,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/DockedStackDividerController.java b/services/core/java/com/android/server/wm/DockedStackDividerController.java
index 7ea88bb..ea65dd9 100644
--- a/services/core/java/com/android/server/wm/DockedStackDividerController.java
+++ b/services/core/java/com/android/server/wm/DockedStackDividerController.java
@@ -36,7 +36,6 @@
 import static com.android.server.wm.DockedStackDividerControllerProto.MINIMIZED_DOCK;
 import static com.android.server.wm.WindowManagerDebugConfig.TAG_WITH_CLASS_NAME;
 import static com.android.server.wm.WindowManagerDebugConfig.TAG_WM;
-import static com.android.server.wm.WindowManagerService.H.NOTIFY_DOCKED_STACK_MINIMIZED_CHANGED;
 import static com.android.server.wm.WindowManagerService.LAYER_OFFSET_DIM;
 
 import android.content.Context;
@@ -566,9 +565,7 @@
             mMaximizeMeetFraction = getClipRevealMeetFraction(stack);
             animDuration = (long) (mAnimationDuration * mMaximizeMeetFraction);
         }
-        mService.mH.removeMessages(NOTIFY_DOCKED_STACK_MINIMIZED_CHANGED);
-        mService.mH.obtainMessage(NOTIFY_DOCKED_STACK_MINIMIZED_CHANGED,
-                minimizedDock ? 1 : 0, 0).sendToTarget();
+        mService.mAtmInternal.notifyDockedStackMinimizedChanged(minimizedDock);
         final int size = mDockedStackListeners.beginBroadcast();
         for (int i = 0; i < size; ++i) {
             final IDockedStackListener listener = mDockedStackListeners.getBroadcastItem(i);
diff --git a/services/core/java/com/android/server/wm/InputMonitor.java b/services/core/java/com/android/server/wm/InputMonitor.java
index 632db38..3c5d911 100644
--- a/services/core/java/com/android/server/wm/InputMonitor.java
+++ b/services/core/java/com/android/server/wm/InputMonitor.java
@@ -46,7 +46,6 @@
 import android.view.InputEventReceiver;
 import android.view.InputWindowHandle;
 import android.view.SurfaceControl;
-import android.view.animation.Animation;
 
 import com.android.server.AnimationThread;
 import com.android.server.policy.WindowManagerPolicy;
@@ -70,8 +69,7 @@
 
     private boolean mDisableWallpaperTouchEvents;
     private final Rect mTmpRect = new Rect();
-    private final UpdateInputForAllWindowsConsumer mUpdateInputForAllWindowsConsumer =
-            new UpdateInputForAllWindowsConsumer();
+    private final UpdateInputForAllWindowsConsumer mUpdateInputForAllWindowsConsumer;
 
     private final int mDisplayId;
     private final DisplayContent mDisplayContent;
@@ -165,6 +163,7 @@
         mDisplayId = displayId;
         mInputTransaction = mDisplayContent.getPendingTransaction();
         mHandler = AnimationThread.getHandler();
+        mUpdateInputForAllWindowsConsumer = new UpdateInputForAllWindowsConsumer();
     }
 
     void onDisplayRemoved() {
@@ -407,6 +406,9 @@
         boolean inDrag;
         WallpaperController wallpaperController;
 
+        // An invalid window handle that tells SurfaceFlinger not update the input info.
+        final InputWindowHandle mInvalidInputWindow = new InputWindowHandle(null, null, mDisplayId);
+
         private void updateInputWindows(boolean inDrag) {
             Trace.traceBegin(Trace.TRACE_TAG_WINDOW_MANAGER, "updateInputWindows");
 
@@ -445,6 +447,10 @@
             final InputWindowHandle inputWindowHandle = w.mInputWindowHandle;
             if (inputChannel == null || inputWindowHandle == null || w.mRemoved
                     || w.cantReceiveTouchInput()) {
+                if (w.mWinAnimator.hasSurface()) {
+                    mInputTransaction.setInputWindowInfo(
+                            w.mWinAnimator.mSurfaceController.mSurfaceControl, mInvalidInputWindow);
+                }
                 // Skip this window because it cannot possibly receive input.
                 return;
             }
diff --git a/services/core/java/com/android/server/wm/InsetsSourceProvider.java b/services/core/java/com/android/server/wm/InsetsSourceProvider.java
index e49e4c0..e798203 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,6 +65,7 @@
      */
     private boolean mServerVisible;
 
+    private final boolean mControllable;
 
     InsetsSourceProvider(InsetsSource source, InsetsStateController stateController,
             DisplayContent displayContent) {
@@ -66,6 +73,15 @@
         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) {
@@ -161,7 +191,7 @@
     }
 
     boolean isClientVisible() {
-        return ViewRootImpl.sNewInsetsMode == NEW_INSETS_MODE_NONE || mClientVisible;
+        return sNewInsetsMode == NEW_INSETS_MODE_NONE || mClientVisible;
     }
 
     private class ControlAdapter implements AnimationAdapter {
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/RecentsAnimationController.java b/services/core/java/com/android/server/wm/RecentsAnimationController.java
index 83ba384..105ff06 100644
--- a/services/core/java/com/android/server/wm/RecentsAnimationController.java
+++ b/services/core/java/com/android/server/wm/RecentsAnimationController.java
@@ -29,7 +29,6 @@
 import static com.android.server.wm.AnimationAdapterProto.REMOTE;
 import static com.android.server.wm.RemoteAnimationAdapterWrapperProto.TARGET;
 import static com.android.server.wm.WindowManagerDebugConfig.DEBUG_RECENTS_ANIMATIONS;
-import static com.android.server.wm.WindowManagerService.H.NOTIFY_APP_TRANSITION_STARTING;
 
 import android.annotation.IntDef;
 import android.app.ActivityManager.TaskSnapshot;
@@ -413,8 +412,7 @@
         }
         final SparseIntArray reasons = new SparseIntArray();
         reasons.put(WINDOWING_MODE_FULLSCREEN, APP_TRANSITION_RECENTS_ANIM);
-        mService.mH.obtainMessage(NOTIFY_APP_TRANSITION_STARTING,
-                reasons).sendToTarget();
+        mService.mAtmInternal.notifyAppTransitionStarting(reasons, SystemClock.uptimeMillis());
     }
 
     void cancelAnimation(@ReorderMode int reorderMode, String reason) {
@@ -626,7 +624,7 @@
             mTarget = new RemoteAnimationTarget(mTask.mTaskId, mode, mCapturedLeash,
                     !topApp.fillsParent(), mainWindow.mWinAnimator.mLastClipRect,
                     insets, mTask.getPrefixOrderIndex(), mPosition, mBounds,
-                    mTask.getWindowConfiguration(), mIsRecentTaskInvisible);
+                    mTask.getWindowConfiguration(), mIsRecentTaskInvisible, null, null);
             return mTarget;
         }
 
diff --git a/services/core/java/com/android/server/wm/RemoteAnimationController.java b/services/core/java/com/android/server/wm/RemoteAnimationController.java
index f5acdcc..5f95691 100644
--- a/services/core/java/com/android/server/wm/RemoteAnimationController.java
+++ b/services/core/java/com/android/server/wm/RemoteAnimationController.java
@@ -57,7 +57,7 @@
 
     private final WindowManagerService mService;
     private final RemoteAnimationAdapter mRemoteAnimationAdapter;
-    private final ArrayList<RemoteAnimationAdapterWrapper> mPendingAnimations = new ArrayList<>();
+    private final ArrayList<RemoteAnimationRecord> mPendingAnimations = new ArrayList<>();
     private final Rect mTmpRect = new Rect();
     private final Handler mHandler;
     private final Runnable mTimeoutRunnable = () -> cancelAnimation("timeoutRunnable");
@@ -74,21 +74,22 @@
     }
 
     /**
-     * Creates an animation for each individual {@link AppWindowToken}.
+     * Creates an animation record for each individual {@link AppWindowToken}.
      *
      * @param appWindowToken The app to animate.
      * @param position The position app bounds, in screen coordinates.
-     * @param stackBounds The stack bounds of the app.
-     * @return The adapter to be run on the app.
+     * @param stackBounds The stack bounds of the app relative to position.
+     * @param startBounds The stack bounds before the transition, in screen coordinates
+     * @return The record representing animation(s) to run on the app.
      */
-    AnimationAdapter createAnimationAdapter(AppWindowToken appWindowToken, Point position,
-            Rect stackBounds) {
+    RemoteAnimationRecord createRemoteAnimationRecord(AppWindowToken appWindowToken,
+            Point position, Rect stackBounds, Rect startBounds) {
         if (DEBUG_REMOTE_ANIMATIONS) Slog.d(TAG, "createAnimationAdapter(): token="
                 + appWindowToken);
-        final RemoteAnimationAdapterWrapper adapter = new RemoteAnimationAdapterWrapper(
-                appWindowToken, position, stackBounds);
-        mPendingAnimations.add(adapter);
-        return adapter;
+        final RemoteAnimationRecord adapters =
+                new RemoteAnimationRecord(appWindowToken, position, stackBounds, startBounds);
+        mPendingAnimations.add(adapters);
+        return adapters;
     }
 
     /**
@@ -148,7 +149,7 @@
         final StringWriter sw = new StringWriter();
         final FastPrintWriter pw = new FastPrintWriter(sw);
         for (int i = mPendingAnimations.size() - 1; i >= 0; i--) {
-            mPendingAnimations.get(i).dump(pw, "");
+            mPendingAnimations.get(i).mAdapter.dump(pw, "");
         }
         pw.close();
         Slog.i(TAG, sw.toString());
@@ -158,19 +159,26 @@
         if (DEBUG_REMOTE_ANIMATIONS) Slog.d(TAG, "createAnimations()");
         final ArrayList<RemoteAnimationTarget> targets = new ArrayList<>();
         for (int i = mPendingAnimations.size() - 1; i >= 0; i--) {
-            final RemoteAnimationAdapterWrapper wrapper = mPendingAnimations.get(i);
-            final RemoteAnimationTarget target = wrapper.createRemoteAppAnimation();
+            final RemoteAnimationRecord wrappers = mPendingAnimations.get(i);
+            final RemoteAnimationTarget target = wrappers.createRemoteAnimationTarget();
             if (target != null) {
-                if (DEBUG_REMOTE_ANIMATIONS) Slog.d(TAG, "\tAdd token=" + wrapper.mAppWindowToken);
+                if (DEBUG_REMOTE_ANIMATIONS) Slog.d(TAG, "\tAdd token=" + wrappers.mAppWindowToken);
                 targets.add(target);
             } else {
                 if (DEBUG_REMOTE_ANIMATIONS) Slog.d(TAG, "\tRemove token="
-                        + wrapper.mAppWindowToken);
+                        + wrappers.mAppWindowToken);
 
                 // We can't really start an animation but we still need to make sure to finish the
                 // pending animation that was started by SurfaceAnimator
-                if (wrapper.mCapturedFinishCallback != null) {
-                    wrapper.mCapturedFinishCallback.onAnimationFinished(wrapper);
+                if (wrappers.mAdapter != null
+                        && wrappers.mAdapter.mCapturedFinishCallback != null) {
+                    wrappers.mAdapter.mCapturedFinishCallback
+                            .onAnimationFinished(wrappers.mAdapter);
+                }
+                if (wrappers.mThumbnailAdapter != null
+                        && wrappers.mThumbnailAdapter.mCapturedFinishCallback != null) {
+                    wrappers.mThumbnailAdapter.mCapturedFinishCallback
+                            .onAnimationFinished(wrappers.mThumbnailAdapter);
                 }
                 mPendingAnimations.remove(i);
             }
@@ -190,9 +198,16 @@
                 if (DEBUG_REMOTE_ANIMATIONS) Slog.d(TAG,
                         "onAnimationFinished(): Notify animation finished:");
                 for (int i = mPendingAnimations.size() - 1; i >= 0; i--) {
-                    final RemoteAnimationAdapterWrapper adapter = mPendingAnimations.get(i);
-                    adapter.mCapturedFinishCallback.onAnimationFinished(adapter);
-                    if (DEBUG_REMOTE_ANIMATIONS) Slog.d(TAG, "\t" + adapter.mAppWindowToken);
+                    final RemoteAnimationRecord adapters = mPendingAnimations.get(i);
+                    if (adapters.mAdapter != null) {
+                        adapters.mAdapter.mCapturedFinishCallback
+                                .onAnimationFinished(adapters.mAdapter);
+                    }
+                    if (adapters.mThumbnailAdapter != null) {
+                        adapters.mThumbnailAdapter.mCapturedFinishCallback
+                                .onAnimationFinished(adapters.mThumbnailAdapter);
+                    }
+                    if (DEBUG_REMOTE_ANIMATIONS) Slog.d(TAG, "\t" + adapters.mAppWindowToken);
                 }
             } catch (Exception e) {
                 Slog.e(TAG, "Failed to finish remote animation", e);
@@ -282,47 +297,86 @@
         }
     };
 
-    private class RemoteAnimationAdapterWrapper implements AnimationAdapter {
+    /**
+     * Contains information about a remote-animation for one AppWindowToken. This keeps track of,
+     * potentially, multiple animating surfaces (AdapterWrappers) associated with one
+     * Window/Transition. For example, a change transition has an adapter controller for the
+     * main window and an adapter controlling the start-state snapshot.
+     * <p>
+     * This can be thought of as a bridge between the information that the remote animator sees (via
+     * {@link RemoteAnimationTarget}) and what the server sees (the
+     * {@link RemoteAnimationAdapterWrapper}(s) interfacing with the moving surfaces).
+     */
+    public class RemoteAnimationRecord {
+        RemoteAnimationAdapterWrapper mAdapter;
+        RemoteAnimationAdapterWrapper mThumbnailAdapter = null;
+        RemoteAnimationTarget mTarget;
+        final AppWindowToken mAppWindowToken;
+        final Rect mStartBounds;
 
-        private final AppWindowToken mAppWindowToken;
-        private SurfaceControl mCapturedLeash;
-        private OnAnimationFinishedCallback mCapturedFinishCallback;
-        private final Point mPosition = new Point();
-        private final Rect mStackBounds = new Rect();
-        private RemoteAnimationTarget mTarget;
-
-        RemoteAnimationAdapterWrapper(AppWindowToken appWindowToken, Point position,
-                Rect stackBounds) {
+        RemoteAnimationRecord(AppWindowToken appWindowToken, Point endPos, Rect endBounds,
+                Rect startBounds) {
             mAppWindowToken = appWindowToken;
-            mPosition.set(position.x, position.y);
-            mStackBounds.set(stackBounds);
+            mAdapter = new RemoteAnimationAdapterWrapper(this, endPos, endBounds);
+            if (startBounds != null) {
+                mStartBounds = new Rect(startBounds);
+                mTmpRect.set(startBounds);
+                mTmpRect.offsetTo(0, 0);
+                if (mRemoteAnimationAdapter.getChangeNeedsSnapshot()) {
+                    mThumbnailAdapter =
+                            new RemoteAnimationAdapterWrapper(this, new Point(0, 0), mTmpRect);
+                }
+            } else {
+                mStartBounds = null;
+            }
         }
 
-        RemoteAnimationTarget createRemoteAppAnimation() {
+        RemoteAnimationTarget createRemoteAnimationTarget() {
             final Task task = mAppWindowToken.getTask();
             final WindowState mainWindow = mAppWindowToken.findMainWindow();
-            if (task == null || mainWindow == null || mCapturedFinishCallback == null
-                    || mCapturedLeash == null) {
+            if (task == null || mainWindow == null || mAdapter == null
+                    || mAdapter.mCapturedFinishCallback == null
+                    || mAdapter.mCapturedLeash == null) {
                 return null;
             }
             final Rect insets = new Rect();
             mainWindow.getContentInsets(insets);
             InsetUtils.addInsets(insets, mAppWindowToken.getLetterboxInsets());
             mTarget = new RemoteAnimationTarget(task.mTaskId, getMode(),
-                    mCapturedLeash, !mAppWindowToken.fillsParent(),
+                    mAdapter.mCapturedLeash, !mAppWindowToken.fillsParent(),
                     mainWindow.mWinAnimator.mLastClipRect, insets,
-                    mAppWindowToken.getPrefixOrderIndex(), mPosition, mStackBounds,
-                    task.getWindowConfiguration(), false /*isNotInRecents*/);
+                    mAppWindowToken.getPrefixOrderIndex(), mAdapter.mPosition,
+                    mAdapter.mStackBounds, task.getWindowConfiguration(), false /*isNotInRecents*/,
+                    mThumbnailAdapter != null ? mThumbnailAdapter.mCapturedLeash : null,
+                    mStartBounds);
             return mTarget;
         }
 
         private int getMode() {
-            if (mAppWindowToken.getDisplayContent().mOpeningApps.contains(mAppWindowToken)) {
+            final DisplayContent dc = mAppWindowToken.getDisplayContent();
+            if (dc.mOpeningApps.contains(mAppWindowToken)) {
                 return RemoteAnimationTarget.MODE_OPENING;
+            } else if (dc.mChangingApps.contains(mAppWindowToken)) {
+                return RemoteAnimationTarget.MODE_CHANGING;
             } else {
                 return RemoteAnimationTarget.MODE_CLOSING;
             }
         }
+    }
+
+    private class RemoteAnimationAdapterWrapper implements AnimationAdapter {
+        private final RemoteAnimationRecord mRecord;
+        SurfaceControl mCapturedLeash;
+        private OnAnimationFinishedCallback mCapturedFinishCallback;
+        private final Point mPosition = new Point();
+        private final Rect mStackBounds = new Rect();
+
+        RemoteAnimationAdapterWrapper(RemoteAnimationRecord record, Point position,
+                Rect stackBounds) {
+            mRecord = record;
+            mPosition.set(position.x, position.y);
+            mStackBounds.set(stackBounds);
+        }
 
         @Override
         public boolean getShowWallpaper() {
@@ -340,7 +394,7 @@
             if (DEBUG_REMOTE_ANIMATIONS) Slog.d(TAG, "startAnimation");
 
             // Restore z-layering, position and stack crop until client has a chance to modify it.
-            t.setLayer(animationLeash, mAppWindowToken.getPrefixOrderIndex());
+            t.setLayer(animationLeash, mRecord.mAppWindowToken.getPrefixOrderIndex());
             t.setPosition(animationLeash, mPosition.x, mPosition.y);
             mTmpRect.set(mStackBounds);
             mTmpRect.offsetTo(0, 0);
@@ -351,7 +405,14 @@
 
         @Override
         public void onAnimationCancelled(SurfaceControl animationLeash) {
-            mPendingAnimations.remove(this);
+            if (mRecord.mAdapter == this) {
+                mRecord.mAdapter = null;
+            } else {
+                mRecord.mThumbnailAdapter = null;
+            }
+            if (mRecord.mAdapter == null && mRecord.mThumbnailAdapter == null) {
+                mPendingAnimations.remove(mRecord);
+            }
             if (mPendingAnimations.isEmpty()) {
                 mHandler.removeCallbacks(mTimeoutRunnable);
                 releaseFinishedCallback();
@@ -373,10 +434,10 @@
 
         @Override
         public void dump(PrintWriter pw, String prefix) {
-            pw.print(prefix); pw.print("token="); pw.println(mAppWindowToken);
-            if (mTarget != null) {
+            pw.print(prefix); pw.print("token="); pw.println(mRecord.mAppWindowToken);
+            if (mRecord.mTarget != null) {
                 pw.print(prefix); pw.println("Target:");
-                mTarget.dump(pw, prefix + "  ");
+                mRecord.mTarget.dump(pw, prefix + "  ");
             } else {
                 pw.print(prefix); pw.println("Target: null");
             }
@@ -385,8 +446,8 @@
         @Override
         public void writeToProto(ProtoOutputStream proto) {
             final long token = proto.start(REMOTE);
-            if (mTarget != null) {
-                mTarget.writeToProto(proto, TARGET);
+            if (mRecord.mTarget != null) {
+                mRecord.mTarget.writeToProto(proto, TARGET);
             }
             proto.end(token);
         }
diff --git a/services/core/java/com/android/server/wm/RootActivityContainer.java b/services/core/java/com/android/server/wm/RootActivityContainer.java
index 9b72141..e95ac5c 100644
--- a/services/core/java/com/android/server/wm/RootActivityContainer.java
+++ b/services/core/java/com/android/server/wm/RootActivityContainer.java
@@ -1108,28 +1108,41 @@
             return false;
         }
 
+        boolean result = false;
         if (targetStack != null && (targetStack.isTopStackOnDisplay()
                 || getTopDisplayFocusedStack() == targetStack)) {
-            return targetStack.resumeTopActivityUncheckedLocked(target, targetOptions);
+            result = targetStack.resumeTopActivityUncheckedLocked(target, targetOptions);
         }
 
-        // Resume all top activities in focused stacks on all displays.
         for (int displayNdx = mActivityDisplays.size() - 1; displayNdx >= 0; --displayNdx) {
+            boolean resumedOnDisplay = false;
             final ActivityDisplay display = mActivityDisplays.get(displayNdx);
-            final ActivityStack focusedStack = display.getFocusedStack();
-            if (focusedStack == null) {
-                continue;
+            for (int stackNdx = display.getChildCount() - 1; stackNdx >= 0; --stackNdx) {
+                final ActivityStack stack = display.getChildAt(stackNdx);
+                final ActivityRecord topRunningActivity = stack.topRunningActivityLocked();
+                if (!stack.isFocusableAndVisible() || topRunningActivity == null) {
+                    continue;
+                }
+                if (topRunningActivity.isState(RESUMED)) {
+                    // Kick off any lingering app transitions form the MoveTaskToFront operation.
+                    stack.executeAppTransition(targetOptions);
+                } else {
+                    resumedOnDisplay |= topRunningActivity.makeActiveIfNeeded(target);
+                }
             }
-            final ActivityRecord r = focusedStack.topRunningActivityLocked();
-            if (r == null || !r.isState(RESUMED)) {
-                focusedStack.resumeTopActivityUncheckedLocked(null, null);
-            } else if (r.isState(RESUMED)) {
-                // Kick off any lingering app transitions form the MoveTaskToFront operation.
-                focusedStack.executeAppTransition(targetOptions);
+            if (!resumedOnDisplay) {
+                // In cases when there are no valid activities (e.g. device just booted or launcher
+                // crashed) it's possible that nothing was resumed on a display. Requesting resume
+                // of top activity in focused stack explicitly will make sure that at least home
+                // activity is started and resumed, and no recursion occurs.
+                final ActivityStack focusedStack = display.getFocusedStack();
+                if (focusedStack != null) {
+                    focusedStack.resumeTopActivityUncheckedLocked(target, targetOptions);
+                }
             }
         }
 
-        return false;
+        return result;
     }
 
     void applySleepTokens(boolean applyToStacks) {
@@ -1283,16 +1296,21 @@
     public void onDisplayAdded(int displayId) {
         if (DEBUG_STACK) Slog.v(TAG, "Display added displayId=" + displayId);
         synchronized (mService.mGlobalLock) {
-            getActivityDisplayOrCreate(displayId);
+            final ActivityDisplay display = getActivityDisplayOrCreate(displayId);
             // Do not start home before booting, or it may accidentally finish booting before it
             // starts. Instead, we expect home activities to be launched when the system is ready
             // (ActivityManagerService#systemReady).
             if (mService.isBooted() || mService.isBooting()) {
-                startHomeOnDisplay(mCurrentUser, "displayAdded", displayId);
+                startSystemDecorations(display.mDisplayContent);
             }
         }
     }
 
+    private void startSystemDecorations(final DisplayContent displayContent) {
+        startHomeOnDisplay(mCurrentUser, "displayAdded", displayContent.getDisplayId());
+        displayContent.getDisplayPolicy().notifyDisplayReady();
+    }
+
     @Override
     public void onDisplayRemoved(int displayId) {
         if (DEBUG_STACK) Slog.v(TAG, "Display removed displayId=" + displayId);
diff --git a/services/core/java/com/android/server/wm/StatusBarController.java b/services/core/java/com/android/server/wm/StatusBarController.java
index b4de75b..6db606d 100644
--- a/services/core/java/com/android/server/wm/StatusBarController.java
+++ b/services/core/java/com/android/server/wm/StatusBarController.java
@@ -61,9 +61,8 @@
         }
 
         @Override
-        public int onAppTransitionStartingLocked(int transit, IBinder openToken,
-                IBinder closeToken, long duration, long statusBarAnimationStartTime,
-                long statusBarAnimationDuration) {
+        public int onAppTransitionStartingLocked(int transit, long duration,
+                long statusBarAnimationStartTime, long statusBarAnimationDuration) {
             mHandler.post(() -> {
                 StatusBarManagerInternal statusBar = getStatusBarInternal();
                 if (statusBar != null && mWin != null) {
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/WallpaperController.java b/services/core/java/com/android/server/wm/WallpaperController.java
index 15239c7..c15afc5 100644
--- a/services/core/java/com/android/server/wm/WallpaperController.java
+++ b/services/core/java/com/android/server/wm/WallpaperController.java
@@ -661,7 +661,8 @@
      * Adjusts the wallpaper windows if the input display has a pending wallpaper layout or one of
      * the opening apps should be a wallpaper target.
      */
-    void adjustWallpaperWindowsForAppTransitionIfNeeded(ArraySet<AppWindowToken> openingApps) {
+    void adjustWallpaperWindowsForAppTransitionIfNeeded(ArraySet<AppWindowToken> openingApps,
+            ArraySet<AppWindowToken> changingApps) {
         boolean adjust = false;
         if ((mDisplayContent.pendingLayoutChanges & FINISH_LAYOUT_REDO_WALLPAPER) != 0) {
             adjust = true;
@@ -673,6 +674,15 @@
                     break;
                 }
             }
+            if (!adjust) {
+                for (int i = changingApps.size() - 1; i >= 0; --i) {
+                    final AppWindowToken token = changingApps.valueAt(i);
+                    if (token.windowsCanBeWallpaperTarget()) {
+                        adjust = true;
+                        break;
+                    }
+                }
+            }
         }
 
         if (adjust) {
diff --git a/services/core/java/com/android/server/wm/WindowChangeAnimationSpec.java b/services/core/java/com/android/server/wm/WindowChangeAnimationSpec.java
new file mode 100644
index 0000000..775d5b2
--- /dev/null
+++ b/services/core/java/com/android/server/wm/WindowChangeAnimationSpec.java
@@ -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.
+ */
+
+package com.android.server.wm;
+
+import static com.android.server.wm.AnimationAdapter.STATUS_BAR_TRANSITION_DURATION;
+import static com.android.server.wm.AnimationSpecProto.WINDOW;
+import static com.android.server.wm.WindowAnimationSpecProto.ANIMATION;
+
+import android.graphics.Matrix;
+import android.graphics.Rect;
+import android.os.SystemClock;
+import android.util.proto.ProtoOutputStream;
+import android.view.DisplayInfo;
+import android.view.SurfaceControl;
+import android.view.SurfaceControl.Transaction;
+import android.view.animation.AlphaAnimation;
+import android.view.animation.Animation;
+import android.view.animation.AnimationSet;
+import android.view.animation.ClipRectAnimation;
+import android.view.animation.ScaleAnimation;
+import android.view.animation.Transformation;
+import android.view.animation.TranslateAnimation;
+
+import com.android.server.wm.LocalAnimationAdapter.AnimationSpec;
+
+import java.io.PrintWriter;
+
+/**
+ * Animation spec for changing window animations.
+ */
+public class WindowChangeAnimationSpec implements AnimationSpec {
+
+    private final ThreadLocal<TmpValues> mThreadLocalTmps = ThreadLocal.withInitial(TmpValues::new);
+    private final boolean mIsAppAnimation;
+    private final Rect mStartBounds;
+    private final Rect mEndBounds;
+    private final Rect mTmpRect = new Rect();
+
+    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,
+            float durationScale, boolean isAppAnimation, boolean isThumbnail) {
+        mStartBounds = new Rect(startBounds);
+        mEndBounds = new Rect(endBounds);
+        mIsAppAnimation = isAppAnimation;
+        mIsThumbnail = isThumbnail;
+        createBoundsInterpolator((int) (ANIMATION_DURATION * durationScale), displayInfo);
+    }
+
+    @Override
+    public boolean getShowWallpaper() {
+        return false;
+    }
+
+    @Override
+    public int getBackgroundColor() {
+        return 0;
+    }
+
+    @Override
+    public long getDuration() {
+        return mAnimation.getDuration();
+    }
+
+    /**
+     * This animator behaves slightly differently depending on whether the window is growing
+     * or shrinking:
+     * If growing, it will do a clip-reveal after quicker fade-out/scale of the smaller (old)
+     * snapshot.
+     * If shrinking, it will do an opposite clip-reveal on the old snapshot followed by a quicker
+     * fade-out of the bigger (old) snapshot while simultaneously shrinking the new window into
+     * place.
+     * @param duration
+     * @param displayInfo
+     */
+    private void createBoundsInterpolator(long duration, DisplayInfo displayInfo) {
+        boolean growing = mEndBounds.width() - mStartBounds.width()
+                + mEndBounds.height() - mStartBounds.height() >= 0;
+        float scalePart = 0.7f;
+        long scalePeriod = (long) (duration * scalePart);
+        float startScaleX = scalePart * ((float) mStartBounds.width()) / mEndBounds.width()
+                + (1.f - scalePart);
+        float startScaleY = scalePart * ((float) mStartBounds.height()) / mEndBounds.height()
+                + (1.f - scalePart);
+        if (mIsThumbnail) {
+            AnimationSet animSet = new AnimationSet(true);
+            Animation anim = new AlphaAnimation(1.f, 0.f);
+            anim.setDuration(scalePeriod);
+            if (!growing) {
+                anim.setStartOffset(duration - scalePeriod);
+            }
+            animSet.addAnimation(anim);
+            float endScaleX = 1.f / startScaleX;
+            float endScaleY = 1.f / startScaleY;
+            anim = new ScaleAnimation(endScaleX, endScaleX, endScaleY, endScaleY);
+            anim.setDuration(duration);
+            animSet.addAnimation(anim);
+            mAnimation = animSet;
+            mAnimation.initialize(mStartBounds.width(), mStartBounds.height(),
+                    mEndBounds.width(), mEndBounds.height());
+        } else {
+            AnimationSet animSet = new AnimationSet(true);
+            final Animation scaleAnim = new ScaleAnimation(startScaleX, 1, startScaleY, 1);
+            scaleAnim.setDuration(scalePeriod);
+            if (!growing) {
+                scaleAnim.setStartOffset(duration - scalePeriod);
+            }
+            animSet.addAnimation(scaleAnim);
+            final Animation translateAnim = new TranslateAnimation(mStartBounds.left,
+                    mEndBounds.left, mStartBounds.top, mEndBounds.top);
+            translateAnim.setDuration(duration);
+            animSet.addAnimation(translateAnim);
+            Rect startClip = new Rect(mStartBounds);
+            Rect endClip = new Rect(mEndBounds);
+            startClip.offsetTo(0, 0);
+            endClip.offsetTo(0, 0);
+            final Animation clipAnim = new ClipRectAnimation(startClip, endClip);
+            clipAnim.setDuration(duration);
+            animSet.addAnimation(clipAnim);
+            mAnimation = animSet;
+            mAnimation.initialize(mStartBounds.width(), mStartBounds.height(),
+                    displayInfo.appWidth, displayInfo.appHeight);
+        }
+    }
+
+    @Override
+    public void apply(Transaction t, SurfaceControl leash, long currentPlayTime) {
+        final TmpValues tmp = mThreadLocalTmps.get();
+        if (mIsThumbnail) {
+            mAnimation.getTransformation(currentPlayTime, tmp.mTransformation);
+            t.setMatrix(leash, tmp.mTransformation.getMatrix(), tmp.mFloats);
+            t.setAlpha(leash, tmp.mTransformation.getAlpha());
+        } else {
+            mAnimation.getTransformation(currentPlayTime, tmp.mTransformation);
+            final Matrix matrix = tmp.mTransformation.getMatrix();
+            t.setMatrix(leash, matrix, tmp.mFloats);
+
+            // The following applies an inverse scale to the clip-rect so that it crops "after" the
+            // scale instead of before.
+            tmp.mVecs[1] = tmp.mVecs[2] = 0;
+            tmp.mVecs[0] = tmp.mVecs[3] = 1;
+            matrix.mapVectors(tmp.mVecs);
+            tmp.mVecs[0] = 1.f / tmp.mVecs[0];
+            tmp.mVecs[3] = 1.f / tmp.mVecs[3];
+            final Rect clipRect = tmp.mTransformation.getClipRect();
+            mTmpRect.left = (int) (clipRect.left * tmp.mVecs[0] + 0.5f);
+            mTmpRect.right = (int) (clipRect.right * tmp.mVecs[0] + 0.5f);
+            mTmpRect.top = (int) (clipRect.top * tmp.mVecs[3] + 0.5f);
+            mTmpRect.bottom = (int) (clipRect.bottom * tmp.mVecs[3] + 0.5f);
+            t.setWindowCrop(leash, mTmpRect);
+        }
+    }
+
+    @Override
+    public long calculateStatusBarTransitionStartTime() {
+        long uptime = SystemClock.uptimeMillis();
+        return Math.max(uptime, uptime + ((long) (((float) mAnimation.getDuration()) * 0.99f))
+                - STATUS_BAR_TRANSITION_DURATION);
+    }
+
+    @Override
+    public boolean canSkipFirstFrame() {
+        return false;
+    }
+
+    @Override
+    public boolean needsEarlyWakeup() {
+        return mIsAppAnimation;
+    }
+
+    @Override
+    public void dump(PrintWriter pw, String prefix) {
+        pw.print(prefix); pw.println(mAnimation.getDuration());
+    }
+
+    @Override
+    public void writeToProtoInner(ProtoOutputStream proto) {
+        final long token = proto.start(WINDOW);
+        proto.write(ANIMATION, mAnimation.toString());
+        proto.end(token);
+    }
+
+    private static class TmpValues {
+        final Transformation mTransformation = new Transformation();
+        final float[] mFloats = new float[9];
+        final float[] mVecs = new float[4];
+    }
+}
diff --git a/services/core/java/com/android/server/wm/WindowManagerInternal.java b/services/core/java/com/android/server/wm/WindowManagerInternal.java
index 5267e7e..e204697 100644
--- a/services/core/java/com/android/server/wm/WindowManagerInternal.java
+++ b/services/core/java/com/android/server/wm/WindowManagerInternal.java
@@ -118,8 +118,6 @@
          *
          * @param transit transition type indicating what kind of transition gets run, must be one
          *                of AppTransition.TRANSIT_* values
-         * @param openToken the token for the opening app
-         * @param closeToken the token for the closing app
          * @param duration the total duration of the transition
          * @param statusBarAnimationStartTime the desired start time for all visual animations in
          *        the status bar caused by this app transition in uptime millis
@@ -131,8 +129,8 @@
          * {@link WindowManagerPolicy#FINISH_LAYOUT_REDO_WALLPAPER},
          * or {@link WindowManagerPolicy#FINISH_LAYOUT_REDO_ANIM}.
          */
-        public int onAppTransitionStartingLocked(int transit, IBinder openToken, IBinder closeToken,
-                long duration, long statusBarAnimationStartTime, long statusBarAnimationDuration) {
+        public int onAppTransitionStartingLocked(int transit, long duration,
+                long statusBarAnimationStartTime, long statusBarAnimationDuration) {
             return 0;
         }
 
diff --git a/services/core/java/com/android/server/wm/WindowManagerService.java b/services/core/java/com/android/server/wm/WindowManagerService.java
index c6679a9..e6581df 100644
--- a/services/core/java/com/android/server/wm/WindowManagerService.java
+++ b/services/core/java/com/android/server/wm/WindowManagerService.java
@@ -184,7 +184,6 @@
 import android.util.Slog;
 import android.util.SparseArray;
 import android.util.SparseBooleanArray;
-import android.util.SparseIntArray;
 import android.util.TimeUtils;
 import android.util.TypedValue;
 import android.util.proto.ProtoOutputStream;
@@ -857,12 +856,12 @@
 
         @Override
         public void onAppTransitionCancelledLocked(int transit) {
-            mH.sendEmptyMessage(H.NOTIFY_APP_TRANSITION_CANCELLED);
+            mAtmInternal.notifyAppTransitionCancelled();
         }
 
         @Override
         public void onAppTransitionFinishedLocked(IBinder token) {
-            mH.sendEmptyMessage(H.NOTIFY_APP_TRANSITION_FINISHED);
+            mAtmInternal.notifyAppTransitionFinished();
             final AppWindowToken atoken = mRoot.getAppWindowToken(token);
             if (atoken == null) {
                 return;
@@ -2602,7 +2601,7 @@
 
     @Override
     public void notifyKeyguardTrustedChanged() {
-        mH.sendEmptyMessage(H.NOTIFY_KEYGUARD_TRUSTED_CHANGED);
+        mAtmInternal.notifyKeyguardTrustedChanged();
     }
 
     @Override
@@ -2667,11 +2666,7 @@
      * @param callback Runnable to be called when activity manager is done reevaluating visibilities
      */
     void notifyKeyguardFlagsChanged(@Nullable Runnable callback, int displayId) {
-        final Runnable wrappedCallback = callback != null
-                ? () -> { synchronized (mGlobalLock) { callback.run(); } }
-                : null;
-        mH.obtainMessage(H.NOTIFY_KEYGUARD_FLAGS_CHANGED, displayId, 0,
-                wrappedCallback).sendToTarget();
+        mAtmInternal.notifyKeyguardFlagsChanged(callback, displayId);
     }
 
     public boolean isKeyguardTrusted() {
@@ -2822,7 +2817,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();
         }
     }
@@ -4363,16 +4358,10 @@
 
         public static final int WINDOW_REPLACEMENT_TIMEOUT = 46;
 
-        public static final int NOTIFY_APP_TRANSITION_STARTING = 47;
-        public static final int NOTIFY_APP_TRANSITION_CANCELLED = 48;
-        public static final int NOTIFY_APP_TRANSITION_FINISHED = 49;
         public static final int UPDATE_ANIMATION_SCALE = 51;
         public static final int WINDOW_HIDE_TIMEOUT = 52;
-        public static final int NOTIFY_DOCKED_STACK_MINIMIZED_CHANGED = 53;
         public static final int SEAMLESS_ROTATION_TIMEOUT = 54;
         public static final int RESTORE_POINTER_ICON = 55;
-        public static final int NOTIFY_KEYGUARD_FLAGS_CHANGED = 56;
-        public static final int NOTIFY_KEYGUARD_TRUSTED_CHANGED = 57;
         public static final int SET_HAS_OVERLAY_UI = 58;
         public static final int SET_RUNNING_REMOTE_ANIMATION = 59;
         public static final int ANIMATION_FAILSAFE = 60;
@@ -4708,19 +4697,6 @@
                     }
                 }
                 break;
-                case NOTIFY_APP_TRANSITION_STARTING: {
-                    mAtmInternal.notifyAppTransitionStarting((SparseIntArray) msg.obj,
-                            msg.getWhen());
-                }
-                break;
-                case NOTIFY_APP_TRANSITION_CANCELLED: {
-                    mAtmInternal.notifyAppTransitionCancelled();
-                }
-                break;
-                case NOTIFY_APP_TRANSITION_FINISHED: {
-                    mAtmInternal.notifyAppTransitionFinished();
-                }
-                break;
                 case WINDOW_HIDE_TIMEOUT: {
                     final WindowState window = (WindowState) msg.obj;
                     synchronized (mGlobalLock) {
@@ -4742,10 +4718,6 @@
                     }
                 }
                 break;
-                case NOTIFY_DOCKED_STACK_MINIMIZED_CHANGED: {
-                    mAtmInternal.notifyDockedStackMinimizedChanged(msg.arg1 == 1);
-                }
-                break;
                 case RESTORE_POINTER_ICON: {
                     synchronized (mGlobalLock) {
                         restorePointerIconLocked((DisplayContent)msg.obj, msg.arg1, msg.arg2);
@@ -4759,14 +4731,6 @@
                     }
                 }
                 break;
-                case NOTIFY_KEYGUARD_FLAGS_CHANGED: {
-                    mAtmInternal.notifyKeyguardFlagsChanged((Runnable) msg.obj, msg.arg1);
-                }
-                break;
-                case NOTIFY_KEYGUARD_TRUSTED_CHANGED: {
-                    mAtmInternal.notifyKeyguardTrustedChanged();
-                }
-                break;
                 case SET_HAS_OVERLAY_UI: {
                     mAmInternal.setHasOverlayUi(msg.arg1, msg.arg2 == 1);
                 }
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/jni/com_android_server_input_InputManagerService.cpp b/services/core/jni/com_android_server_input_InputManagerService.cpp
index 90c9cc2..ff0b0d6 100644
--- a/services/core/jni/com_android_server_input_InputManagerService.cpp
+++ b/services/core/jni/com_android_server_input_InputManagerService.cpp
@@ -782,7 +782,7 @@
             }
 
             sp<InputWindowHandle> windowHandle =
-                    android_server_InputWindowHandle_getHandle(env, windowHandleObj);
+                    android_view_InputWindowHandle_getHandle(env, windowHandleObj);
             if (windowHandle != nullptr) {
                 windowHandles.push(windowHandle);
             }
@@ -822,7 +822,7 @@
 void NativeInputManager::setFocusedApplication(JNIEnv* env, int32_t displayId,
         jobject applicationHandleObj) {
     sp<InputApplicationHandle> applicationHandle =
-            android_server_InputApplicationHandle_getHandle(env, applicationHandleObj);
+            android_view_InputApplicationHandle_getHandle(env, applicationHandleObj);
     mInputManager->getDispatcher()->setFocusedApplication(displayId, applicationHandle);
 }
 
diff --git a/services/core/jni/com_android_server_lights_LightsService.cpp b/services/core/jni/com_android_server_lights_LightsService.cpp
index c90113f..26f6d74 100644
--- a/services/core/jni/com_android_server_lights_LightsService.cpp
+++ b/services/core/jni/com_android_server_lights_LightsService.cpp
@@ -39,37 +39,7 @@
 template<typename T>
 using Return     = ::android::hardware::Return<T>;
 
-class LightHal {
-private:
-    static sp<ILight> sLight;
-    static bool sLightInit;
-
-    LightHal() {}
-
-public:
-    static void disassociate() {
-        sLightInit = false;
-        sLight = nullptr;
-    }
-
-    static sp<ILight> associate() {
-        if ((sLight == nullptr && !sLightInit) ||
-                (sLight != nullptr && !sLight->ping().isOk())) {
-            // will return the hal if it exists the first time.
-            sLight = ILight::getService();
-            sLightInit = true;
-
-            if (sLight == nullptr) {
-                ALOGE("Unable to get ILight interface.");
-            }
-        }
-
-        return sLight;
-    }
-};
-
-sp<ILight> LightHal::sLight = nullptr;
-bool LightHal::sLightInit = false;
+static bool sLightSupported = true;
 
 static bool validate(jint light, jint flash, jint brightness) {
     bool valid = true;
@@ -134,7 +104,6 @@
         const LightState &state) {
     if (!ret.isOk()) {
         ALOGE("Failed to issue set light command.");
-        LightHal::disassociate();
         return;
     }
 
@@ -164,13 +133,11 @@
         jint offMS,
         jint brightnessMode) {
 
-    if (!validate(light, flashMode, brightnessMode)) {
+    if (!sLightSupported) {
         return;
     }
 
-    sp<ILight> hal = LightHal::associate();
-
-    if (hal == nullptr) {
+    if (!validate(light, flashMode, brightnessMode)) {
         return;
     }
 
@@ -180,6 +147,11 @@
 
     {
         android::base::Timer t;
+        sp<ILight> hal = ILight::getService();
+        if (hal == nullptr) {
+            sLightSupported = false;
+            return;
+        }
         Return<Status> ret = hal->setLight(type, state);
         processReturn(ret, type, state);
         if (t.duration() > 50ms) ALOGD("Excessive delay setting light");
diff --git a/services/core/jni/onload.cpp b/services/core/jni/onload.cpp
index 918f57e..6e31aed 100644
--- a/services/core/jni/onload.cpp
+++ b/services/core/jni/onload.cpp
@@ -26,8 +26,6 @@
 int register_android_server_AlarmManagerService(JNIEnv* env);
 int register_android_server_BatteryStatsService(JNIEnv* env);
 int register_android_server_ConsumerIrService(JNIEnv *env);
-int register_android_server_InputApplicationHandle(JNIEnv* env);
-int register_android_server_InputWindowHandle(JNIEnv* env);
 int register_android_server_InputManager(JNIEnv* env);
 int register_android_server_LightsService(JNIEnv* env);
 int register_android_server_PowerManagerService(JNIEnv* env);
@@ -74,8 +72,6 @@
     register_android_server_broadcastradio_Tuner(vm, env);
     register_android_server_PowerManagerService(env);
     register_android_server_SerialService(env);
-    register_android_server_InputApplicationHandle(env);
-    register_android_server_InputWindowHandle(env);
     register_android_server_InputManager(env);
     register_android_server_LightsService(env);
     register_android_server_AlarmManagerService(env);
diff --git a/services/devicepolicy/java/com/android/server/devicepolicy/BaseIDevicePolicyManager.java b/services/devicepolicy/java/com/android/server/devicepolicy/BaseIDevicePolicyManager.java
index d8225b3..2bf6f35 100644
--- a/services/devicepolicy/java/com/android/server/devicepolicy/BaseIDevicePolicyManager.java
+++ b/services/devicepolicy/java/com/android/server/devicepolicy/BaseIDevicePolicyManager.java
@@ -108,12 +108,7 @@
             ParcelFileDescriptor updateFileDescriptor, StartInstallingUpdateCallback listener) {}
 
     @Override
-    public void addCrossProfileCalendarPackage(ComponentName admin, String packageName) {
-    }
-
-    @Override
-    public boolean removeCrossProfileCalendarPackage(ComponentName admin, String packageName) {
-        return false;
+    public void setCrossProfileCalendarPackages(ComponentName admin, List<String> packageNames) {
     }
 
     @Override
diff --git a/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java b/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java
index 54053a8..f79f9bc 100644
--- a/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java
+++ b/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java
@@ -70,9 +70,11 @@
 import static android.app.admin.DevicePolicyManager.WIPE_EUICC;
 import static android.app.admin.DevicePolicyManager.WIPE_EXTERNAL_STORAGE;
 import static android.app.admin.DevicePolicyManager.WIPE_RESET_PROTECTION_DATA;
+import static android.app.admin.DevicePolicyManager.WIPE_SILENTLY;
 import static android.content.pm.PackageManager.MATCH_UNINSTALLED_PACKAGES;
 import static android.provider.Settings.Global.PRIVATE_DNS_MODE;
 import static android.provider.Settings.Global.PRIVATE_DNS_SPECIFIER;
+
 import static android.provider.Telephony.Carriers.DPC_URI;
 import static android.provider.Telephony.Carriers.ENFORCE_KEY;
 import static android.provider.Telephony.Carriers.ENFORCE_MANAGED_URI;
@@ -81,10 +83,11 @@
         .PROVISIONING_ENTRY_POINT_ADB;
 import static com.android.internal.widget.LockPatternUtils.StrongAuthTracker
         .STRONG_AUTH_REQUIRED_AFTER_DPM_LOCK_NOW;
-import static com.android.server.devicepolicy.TransferOwnershipMetadataManager
-        .ADMIN_TYPE_DEVICE_OWNER;
-import static com.android.server.devicepolicy.TransferOwnershipMetadataManager
-        .ADMIN_TYPE_PROFILE_OWNER;
+
+import static com.android.server.devicepolicy.TransferOwnershipMetadataManager.ADMIN_TYPE_DEVICE_OWNER;
+import static com.android.server.devicepolicy.TransferOwnershipMetadataManager.ADMIN_TYPE_PROFILE_OWNER;
+
+
 import static com.android.server.pm.PackageManagerService.PLATFORM_PACKAGE_NAME;
 
 import static org.xmlpull.v1.XmlPullParser.END_DOCUMENT;
@@ -237,11 +240,11 @@
 import com.android.internal.util.FunctionalUtils.ThrowingRunnable;
 import com.android.internal.util.JournaledFile;
 import com.android.internal.util.Preconditions;
-import com.android.internal.util.StatLogger;
 import com.android.internal.util.XmlUtils;
 import com.android.internal.widget.LockPatternUtils;
 import com.android.server.LocalServices;
 import com.android.server.LockGuard;
+import com.android.internal.util.StatLogger;
 import com.android.server.SystemServerInitThreadPool;
 import com.android.server.SystemService;
 import com.android.server.devicepolicy.DevicePolicyManagerService.ActiveAdmin.TrustAgentInfo;
@@ -949,8 +952,8 @@
                 "metered_data_disabled_packages";
         private static final String TAG_CROSS_PROFILE_CALENDAR_PACKAGES =
                 "cross-profile-calendar-packages";
-        private static final String TAG_PACKAGE = "package";
-
+        private static final String TAG_CROSS_PROFILE_CALENDAR_PACKAGES_NULL =
+                "cross-profile-calendar-packages-null";
 
         DeviceAdminInfo info;
 
@@ -1072,7 +1075,9 @@
         String endUserSessionMessage = null;
 
         // The whitelist of packages that can access cross profile calendar APIs.
-        final Set<String> mCrossProfileCalendarPackages = new ArraySet<>();
+        // This whitelist should be in default an empty list, which indicates that no package
+        // is whitelisted.
+        List<String> mCrossProfileCalendarPackages = Collections.emptyList();
 
         ActiveAdmin(DeviceAdminInfo _info, boolean parent) {
             info = _info;
@@ -1343,11 +1348,12 @@
                 out.text(endUserSessionMessage);
                 out.endTag(null, TAG_END_USER_SESSION_MESSAGE);
             }
-            if (!mCrossProfileCalendarPackages.isEmpty()) {
-                out.startTag(null, TAG_CROSS_PROFILE_CALENDAR_PACKAGES);
-                writeAttributeValuesToXml(
-                        out, TAG_PACKAGE, mCrossProfileCalendarPackages);
-                out.endTag(null, TAG_CROSS_PROFILE_CALENDAR_PACKAGES);
+            if (mCrossProfileCalendarPackages == null) {
+                out.startTag(null, TAG_CROSS_PROFILE_CALENDAR_PACKAGES_NULL);
+                out.endTag(null, TAG_CROSS_PROFILE_CALENDAR_PACKAGES_NULL);
+            } else {
+                writePackageListToXml(out, TAG_CROSS_PROFILE_CALENDAR_PACKAGES,
+                        mCrossProfileCalendarPackages);
             }
         }
 
@@ -1542,8 +1548,9 @@
                         Log.w(LOG_TAG, "Missing text when loading end session message");
                     }
                 } else if (TAG_CROSS_PROFILE_CALENDAR_PACKAGES.equals(tag)) {
-                    readAttributeValues(
-                            parser, TAG_PACKAGE, mCrossProfileCalendarPackages);
+                    mCrossProfileCalendarPackages = readPackageList(parser, tag);
+                } else if (TAG_CROSS_PROFILE_CALENDAR_PACKAGES_NULL.equals(tag)) {
+                    mCrossProfileCalendarPackages = null;
                 } else {
                     Slog.w(LOG_TAG, "Unknown admin tag: " + tag);
                     XmlUtils.skipCurrentTag(parser);
@@ -1759,8 +1766,10 @@
                 pw.print(prefix);  pw.println("parentAdmin:");
                 parentAdmin.dump(prefix + "  ", pw);
             }
-            pw.print(prefix); pw.print("mCrossProfileCalendarPackages=");
-            pw.println(mCrossProfileCalendarPackages);
+            if (mCrossProfileCalendarPackages != null) {
+                pw.print(prefix); pw.print("mCrossProfileCalendarPackages=");
+                pw.println(mCrossProfileCalendarPackages);
+            }
         }
     }
 
@@ -6362,7 +6371,7 @@
         }
     }
 
-    private void forceWipeUser(int userId, String wipeReasonForUser) {
+    private void forceWipeUser(int userId, String wipeReasonForUser, boolean wipeSilently) {
         boolean success = false;
         try {
             IActivityManager am = mInjector.getIActivityManager();
@@ -6373,7 +6382,7 @@
             success = mUserManagerInternal.removeUserEvenWhenDisallowed(userId);
             if (!success) {
                 Slog.w(LOG_TAG, "Couldn't remove user " + userId);
-            } else if (isManagedProfile(userId) && !TextUtils.isEmpty(wipeReasonForUser)) {
+            } else if (isManagedProfile(userId) && !wipeSilently) {
                 sendWipeProfileNotification(wipeReasonForUser);
             }
         } catch (RemoteException re) {
@@ -6388,6 +6397,7 @@
         if (!mHasFeature) {
             return;
         }
+        Preconditions.checkStringNotEmpty(wipeReasonForUser, "wipeReasonForUser is null or empty");
         enforceFullCrossUsersPermission(mInjector.userHandleGetCallingUserId());
 
         final ActiveAdmin admin;
@@ -6447,7 +6457,7 @@
                         internalReason,
                         /*wipeEuicc=*/ (flags & WIPE_EUICC) != 0);
             } else {
-                forceWipeUser(userId, wipeReasonForUser);
+                forceWipeUser(userId, wipeReasonForUser, (flags & WIPE_SILENTLY) != 0);
             }
         } finally {
             mInjector.binderRestoreCallingIdentity(ident);
@@ -13988,55 +13998,27 @@
     }
 
     @Override
-    public void addCrossProfileCalendarPackage(ComponentName who, String packageName) {
+    public void setCrossProfileCalendarPackages(ComponentName who, List<String> packageNames) {
         if (!mHasFeature) {
             return;
         }
         Preconditions.checkNotNull(who, "ComponentName is null");
-        Preconditions.checkStringNotEmpty(packageName, "Package name is null or empty");
 
         synchronized (getLockObject()) {
             final ActiveAdmin admin = getActiveAdminForCallerLocked(
                     who, DeviceAdminInfo.USES_POLICY_PROFILE_OWNER);
-            if (admin.mCrossProfileCalendarPackages.add(packageName)) {
-                saveSettingsLocked(mInjector.userHandleGetCallingUserId());
-            }
+            admin.mCrossProfileCalendarPackages = packageNames;
+            saveSettingsLocked(mInjector.userHandleGetCallingUserId());
         }
         DevicePolicyEventLogger
-                .createEvent(DevicePolicyEnums.ADD_CROSS_PROFILE_CALENDAR_PACKAGE)
+                .createEvent(DevicePolicyEnums.SET_CROSS_PROFILE_CALENDAR_PACKAGES)
                 .setAdmin(who)
-                .setStrings(packageName)
+                .setStrings(packageNames == null ? null
+                        : packageNames.toArray(new String[packageNames.size()]))
                 .write();
     }
 
     @Override
-    public boolean removeCrossProfileCalendarPackage(ComponentName who, String packageName) {
-        if (!mHasFeature) {
-            return false;
-        }
-        Preconditions.checkNotNull(who, "ComponentName is null");
-        Preconditions.checkStringNotEmpty(packageName, "Package name is null or empty");
-
-        boolean isRemoved = false;
-        synchronized (getLockObject()) {
-            final ActiveAdmin admin = getActiveAdminForCallerLocked(
-                    who, DeviceAdminInfo.USES_POLICY_PROFILE_OWNER);
-            isRemoved = admin.mCrossProfileCalendarPackages.remove(packageName);
-            if (isRemoved) {
-                saveSettingsLocked(mInjector.userHandleGetCallingUserId());
-            }
-        }
-        if (isRemoved) {
-            DevicePolicyEventLogger
-                    .createEvent(DevicePolicyEnums.REMOVE_CROSS_PROFILE_CALENDAR_PACKAGE)
-                    .setAdmin(who)
-                    .setStrings(packageName)
-                    .write();
-        }
-        return isRemoved;
-    }
-
-    @Override
     public List<String> getCrossProfileCalendarPackages(ComponentName who) {
         if (!mHasFeature) {
             return Collections.emptyList();
@@ -14046,7 +14028,7 @@
         synchronized (getLockObject()) {
             final ActiveAdmin admin = getActiveAdminForCallerLocked(
                     who, DeviceAdminInfo.USES_POLICY_PROFILE_OWNER);
-            return new ArrayList<String>(admin.mCrossProfileCalendarPackages);
+            return admin.mCrossProfileCalendarPackages;
         }
     }
 
@@ -14062,6 +14044,9 @@
         synchronized (getLockObject()) {
             final ActiveAdmin admin = getProfileOwnerAdminLocked(userHandle);
             if (admin != null) {
+                if (admin.mCrossProfileCalendarPackages == null) {
+                    return true;
+                }
                 return admin.mCrossProfileCalendarPackages.contains(packageName);
             }
         }
@@ -14077,7 +14062,7 @@
         synchronized (getLockObject()) {
             final ActiveAdmin admin = getProfileOwnerAdminLocked(userHandle);
             if (admin != null) {
-                return new ArrayList<String>(admin.mCrossProfileCalendarPackages);
+                return admin.mCrossProfileCalendarPackages;
             }
         }
         return Collections.emptyList();
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/java/com/android/server/SystemServer.java b/services/java/com/android/server/SystemServer.java
index 65eaf554..5861368 100644
--- a/services/java/com/android/server/SystemServer.java
+++ b/services/java/com/android/server/SystemServer.java
@@ -1561,6 +1561,12 @@
                 traceEnd();
             }
 
+            // Grants default permissions and defines roles
+            traceBeginAndSlog("StartRoleManagerService");
+            mSystemServiceManager.startService(new RoleManagerService(
+                    mSystemContext, new LegacyRoleResolutionPolicy(mSystemContext)));
+            traceEnd();
+
             // We need to always start this service, regardless of whether the
             // FEATURE_VOICE_RECOGNIZERS feature is set, because it needs to take care
             // of initializing various settings.  It will internally modify its behavior
@@ -2011,12 +2017,6 @@
             }
             traceEnd();
 
-            // Grants default permissions and defines roles
-            traceBeginAndSlog("StartRoleManagerService");
-            mSystemServiceManager.startService(new RoleManagerService(
-                    mSystemContext, new LegacyRoleResolutionPolicy(mSystemContext)));
-            traceEnd();
-
             // No dependency on Webview preparation in system server. But this should
             // be completed before allowing 3rd party
             final String WEBVIEW_PREPARATION = "WebViewFactoryPreparation";
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/net/java/android/net/shared/NetworkObserverRegistry.java b/services/net/java/android/net/shared/NetworkObserverRegistry.java
new file mode 100644
index 0000000..36945f5
--- /dev/null
+++ b/services/net/java/android/net/shared/NetworkObserverRegistry.java
@@ -0,0 +1,255 @@
+/*
+ * 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.Manifest.permission.NETWORK_STACK;
+
+import android.content.Context;
+import android.net.INetd;
+import android.net.INetdUnsolicitedEventListener;
+import android.net.INetworkManagementEventObserver;
+import android.net.InetAddresses;
+import android.net.IpPrefix;
+import android.net.LinkAddress;
+import android.net.RouteInfo;
+import android.os.Handler;
+import android.os.RemoteCallbackList;
+import android.os.RemoteException;
+import android.os.SystemClock;
+
+/**
+ * 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.
+ *
+ * TODO: Make the notifyXyz methods protected once subclasses (e.g., the NetworkManagementService
+ * subclass) no longer call them directly.
+ *
+ * TODO: change from RemoteCallbackList to direct in-process callbacks.
+ */
+public class NetworkObserverRegistry extends INetdUnsolicitedEventListener.Stub {
+
+    private final Context mContext;
+    private final Handler mDaemonHandler;
+    private static final String TAG = "NetworkObserverRegistry";
+
+    /**
+     * Constructs a new instance and registers it with netd.
+     * This method should only be called once since netd will reject multiple registrations from
+     * the same process.
+     */
+    public NetworkObserverRegistry(Context context, Handler handler, INetd netd)
+            throws RemoteException {
+        mContext = context;
+        mDaemonHandler = handler;
+        netd.registerUnsolicitedEventListener(this);
+    }
+
+    private final RemoteCallbackList<INetworkManagementEventObserver> mObservers =
+            new RemoteCallbackList<>();
+
+    /**
+     * Registers the specified observer and start sending callbacks to it.
+     * This method may be called on any thread.
+     */
+    public void registerObserver(INetworkManagementEventObserver observer) {
+        mContext.enforceCallingOrSelfPermission(NETWORK_STACK, TAG);
+        mObservers.register(observer);
+    }
+
+    /**
+     * Unregisters the specified observer and stop sending callbacks to it.
+     * This method may be called on any thread.
+     */
+    public void unregisterObserver(INetworkManagementEventObserver observer) {
+        mContext.enforceCallingOrSelfPermission(NETWORK_STACK, TAG);
+        mObservers.unregister(observer);
+    }
+
+    @FunctionalInterface
+    private interface NetworkManagementEventCallback {
+        void sendCallback(INetworkManagementEventObserver o) throws RemoteException;
+    }
+
+    private void invokeForAllObservers(NetworkManagementEventCallback eventCallback) {
+        final int length = mObservers.beginBroadcast();
+        try {
+            for (int i = 0; i < length; i++) {
+                try {
+                    eventCallback.sendCallback(mObservers.getBroadcastItem(i));
+                } catch (RemoteException | RuntimeException e) {
+                }
+            }
+        } finally {
+            mObservers.finishBroadcast();
+        }
+    }
+
+    /**
+     * Notify our observers of a change in the data activity state of the interface
+     */
+    public void notifyInterfaceClassActivity(int type, boolean isActive, long tsNanos,
+            int uid, boolean fromRadio) {
+        invokeForAllObservers(o -> o.interfaceClassDataActivityChanged(
+                Integer.toString(type), isActive, tsNanos));
+    }
+
+    @Override
+    public void onInterfaceClassActivityChanged(boolean isActive,
+            int label, long timestamp, int uid) throws RemoteException {
+        final long timestampNanos;
+        if (timestamp <= 0) {
+            timestampNanos = SystemClock.elapsedRealtimeNanos();
+        } else {
+            timestampNanos = timestamp;
+        }
+        mDaemonHandler.post(() -> notifyInterfaceClassActivity(label, isActive,
+                timestampNanos, uid, false));
+    }
+
+    /**
+     * Notify our observers of a limit reached.
+     */
+    @Override
+    public void onQuotaLimitReached(String alertName, String ifName) throws RemoteException {
+        mDaemonHandler.post(() -> notifyLimitReached(alertName, ifName));
+    }
+
+    /**
+     * Notify our observers of a limit reached.
+     */
+    public void notifyLimitReached(String limitName, String iface) {
+        invokeForAllObservers(o -> o.limitReached(limitName, iface));
+    }
+
+    @Override
+    public void onInterfaceDnsServerInfo(String ifName,
+            long lifetime, String[] servers) throws RemoteException {
+        mDaemonHandler.post(() -> notifyInterfaceDnsServerInfo(ifName, lifetime, servers));
+    }
+
+    /**
+     * Notify our observers of DNS server information received.
+     */
+    public void notifyInterfaceDnsServerInfo(String iface, long lifetime, String[] addresses) {
+        invokeForAllObservers(o -> o.interfaceDnsServerInfo(iface, lifetime, addresses));
+    }
+
+    @Override
+    public void onInterfaceAddressUpdated(String addr,
+            String ifName, int flags, int scope) throws RemoteException {
+        final LinkAddress address = new LinkAddress(addr, flags, scope);
+        mDaemonHandler.post(() -> notifyAddressUpdated(ifName, address));
+    }
+
+    /**
+     * Notify our observers of a new or updated interface address.
+     */
+    public void notifyAddressUpdated(String iface, LinkAddress address) {
+        invokeForAllObservers(o -> o.addressUpdated(iface, address));
+    }
+
+    @Override
+    public void onInterfaceAddressRemoved(String addr,
+            String ifName, int flags, int scope) throws RemoteException {
+        final LinkAddress address = new LinkAddress(addr, flags, scope);
+        mDaemonHandler.post(() -> notifyAddressRemoved(ifName, address));
+    }
+
+    /**
+     * Notify our observers of a deleted interface address.
+     */
+    public void notifyAddressRemoved(String iface, LinkAddress address) {
+        invokeForAllObservers(o -> o.addressRemoved(iface, address));
+    }
+
+
+    @Override
+    public void onInterfaceAdded(String ifName) throws RemoteException {
+        mDaemonHandler.post(() -> notifyInterfaceAdded(ifName));
+    }
+
+    /**
+     * Notify our observers of an interface addition.
+     */
+    public void notifyInterfaceAdded(String iface) {
+        invokeForAllObservers(o -> o.interfaceAdded(iface));
+    }
+
+    @Override
+    public void onInterfaceRemoved(String ifName) throws RemoteException {
+        mDaemonHandler.post(() -> notifyInterfaceRemoved(ifName));
+    }
+
+    /**
+     * Notify our observers of an interface removal.
+     */
+    public void notifyInterfaceRemoved(String iface) {
+        invokeForAllObservers(o -> o.interfaceRemoved(iface));
+    }
+
+    @Override
+    public void onInterfaceChanged(String ifName, boolean up) throws RemoteException {
+        mDaemonHandler.post(() -> notifyInterfaceStatusChanged(ifName, up));
+    }
+
+    /**
+     * Notify our observers of an interface status change
+     */
+    public void notifyInterfaceStatusChanged(String iface, boolean up) {
+        invokeForAllObservers(o -> o.interfaceStatusChanged(iface, up));
+    }
+
+    @Override
+    public void onInterfaceLinkStateChanged(String ifName, boolean up) throws RemoteException {
+        mDaemonHandler.post(() -> notifyInterfaceLinkStateChanged(ifName, up));
+    }
+
+    /**
+     * Notify our observers of an interface link state change
+     * (typically, an Ethernet cable has been plugged-in or unplugged).
+     */
+    public void notifyInterfaceLinkStateChanged(String iface, boolean up) {
+        invokeForAllObservers(o -> o.interfaceLinkStateChanged(iface, up));
+    }
+
+    @Override
+    public void onRouteChanged(boolean updated,
+            String route, String gateway, String ifName) throws RemoteException {
+        final RouteInfo processRoute = new RouteInfo(new IpPrefix(route),
+                ("".equals(gateway)) ? null : InetAddresses.parseNumericAddress(gateway),
+                ifName);
+        mDaemonHandler.post(() -> notifyRouteChange(updated, processRoute));
+    }
+
+    /**
+     * Notify our observers of a route change.
+     */
+    public void notifyRouteChange(boolean updated, RouteInfo route) {
+        if (updated) {
+            invokeForAllObservers(o -> o.routeUpdated(route));
+        } else {
+            invokeForAllObservers(o -> o.routeRemoved(route));
+        }
+    }
+
+    @Override
+    public void onStrictCleartextDetected(int uid, String hex) throws RemoteException {
+        // Don't do anything here because this is not a method of INetworkManagementEventObserver.
+        // Only the NMS subclass will implement this.
+    }
+}
diff --git a/services/tests/mockingservicestests/src/com/android/server/AlarmManagerServiceTest.java b/services/tests/mockingservicestests/src/com/android/server/AlarmManagerServiceTest.java
index 6a153d5..6386b3b3 100644
--- a/services/tests/mockingservicestests/src/com/android/server/AlarmManagerServiceTest.java
+++ b/services/tests/mockingservicestests/src/com/android/server/AlarmManagerServiceTest.java
@@ -28,14 +28,18 @@
 import static com.android.dx.mockito.inline.extended.ExtendedMockito.spyOn;
 import static com.android.dx.mockito.inline.extended.ExtendedMockito.verify;
 import static com.android.dx.mockito.inline.extended.ExtendedMockito.when;
+import static com.android.server.AlarmManagerService.ACTIVE_INDEX;
+import static com.android.server.AlarmManagerService.AlarmHandler.APP_STANDBY_BUCKET_CHANGED;
+import static com.android.server.AlarmManagerService.AlarmHandler.APP_STANDBY_PAROLE_CHANGED;
 import static com.android.server.AlarmManagerService.Constants.KEY_ALLOW_WHILE_IDLE_LONG_TIME;
 import static com.android.server.AlarmManagerService.Constants.KEY_ALLOW_WHILE_IDLE_SHORT_TIME;
-import static com.android.server.AlarmManagerService.Constants
-        .KEY_ALLOW_WHILE_IDLE_WHITELIST_DURATION;
+import static com.android.server.AlarmManagerService.Constants.KEY_ALLOW_WHILE_IDLE_WHITELIST_DURATION;
+import static com.android.server.AlarmManagerService.Constants.KEY_APP_STANDBY_QUOTAS_ENABLED;
 import static com.android.server.AlarmManagerService.Constants.KEY_LISTENER_TIMEOUT;
 import static com.android.server.AlarmManagerService.Constants.KEY_MAX_INTERVAL;
 import static com.android.server.AlarmManagerService.Constants.KEY_MIN_FUTURITY;
 import static com.android.server.AlarmManagerService.Constants.KEY_MIN_INTERVAL;
+import static com.android.server.AlarmManagerService.WORKING_INDEX;
 
 import static org.junit.Assert.assertEquals;
 import static org.junit.Assert.assertNotNull;
@@ -58,6 +62,7 @@
 import android.content.Intent;
 import android.os.Handler;
 import android.os.Looper;
+import android.os.Message;
 import android.os.PowerManager;
 import android.os.UserHandle;
 import android.platform.test.annotations.Presubmit;
@@ -65,7 +70,6 @@
 import android.util.Log;
 import android.util.SparseArray;
 
-import androidx.test.filters.SmallTest;
 import androidx.test.runner.AndroidJUnit4;
 
 import com.android.internal.annotations.GuardedBy;
@@ -83,7 +87,6 @@
 import java.util.ArrayList;
 
 @Presubmit
-@SmallTest
 @RunWith(AndroidJUnit4.class)
 public class AlarmManagerServiceTest {
     private static final String TAG = AlarmManagerServiceTest.class.getSimpleName();
@@ -91,7 +94,9 @@
     private static final int SYSTEM_UI_UID = 123456789;
     private static final int TEST_CALLING_UID = 12345;
 
+    private long mAppStandbyWindow;
     private AlarmManagerService mService;
+    private UsageStatsManagerInternal.AppIdleStateChangeListener mAppStandbyListener;
     @Mock
     private ContentResolver mMockResolver;
     @Mock
@@ -229,16 +234,23 @@
         mService = new AlarmManagerService(mMockContext, mInjector);
         spyOn(mService);
         doNothing().when(mService).publishBinderService(any(), any());
-        mService.onStart();
-        mService.onBootPhase(SystemService.PHASE_SYSTEM_SERVICES_READY);
-        spyOn(mService.mHandler);
 
-        assertEquals(0, mService.mConstants.MIN_FUTURITY);
+        mService.onStart();
+        spyOn(mService.mHandler);
         assertEquals(mService.mSystemUiUid, SYSTEM_UI_UID);
         assertEquals(mService.mClockReceiver, mClockReceiver);
         assertEquals(mService.mWakeLock, mWakeLock);
         verify(mIActivityManager).registerUidObserver(any(IUidObserver.class), anyInt(), anyInt(),
                 isNull());
+
+        // Other boot phases don't matter
+        mService.onBootPhase(SystemService.PHASE_SYSTEM_SERVICES_READY);
+        assertEquals(0, mService.mConstants.MIN_FUTURITY);
+        mAppStandbyWindow = mService.mConstants.APP_STANDBY_WINDOW;
+        ArgumentCaptor<UsageStatsManagerInternal.AppIdleStateChangeListener> captor =
+                ArgumentCaptor.forClass(UsageStatsManagerInternal.AppIdleStateChangeListener.class);
+        verify(mUsageStatsManagerInternal).addAppIdleStateChangeListener(captor.capture());
+        mAppStandbyListener = captor.getValue();
     }
 
     private void setTestAlarm(int type, long triggerTime, PendingIntent operation) {
@@ -254,6 +266,28 @@
         return mockPi;
     }
 
+    /**
+     * Careful while calling as this will replace any existing settings for the calling test.
+     */
+    private void setQuotasEnabled(boolean enabled) {
+        final StringBuilder constantsBuilder = new StringBuilder();
+        constantsBuilder.append(KEY_MIN_FUTURITY);
+        constantsBuilder.append("=0,");
+        // Capping active and working quotas to make testing feasible.
+        constantsBuilder.append(mService.mConstants.KEYS_APP_STANDBY_QUOTAS[ACTIVE_INDEX]);
+        constantsBuilder.append("=8,");
+        constantsBuilder.append(mService.mConstants.KEYS_APP_STANDBY_QUOTAS[WORKING_INDEX]);
+        constantsBuilder.append("=5,");
+        if (!enabled) {
+            constantsBuilder.append(KEY_APP_STANDBY_QUOTAS_ENABLED);
+            constantsBuilder.append("=false,");
+        }
+        doReturn(constantsBuilder.toString()).when(() -> Settings.Global.getString(mMockResolver,
+                Settings.Global.ALARM_MANAGER_CONSTANTS));
+        mService.mConstants.onChange(false, null);
+        assertEquals(mService.mConstants.APP_STANDBY_QUOTAS_ENABLED, enabled);
+    }
+
     @Test
     public void testSingleAlarmSet() {
         final long triggerTime = mNowElapsedTest + 5000;
@@ -346,6 +380,7 @@
 
     @Test
     public void testStandbyBucketDelay_workingSet() throws Exception {
+        setQuotasEnabled(false);
         setTestAlarm(ELAPSED_REALTIME_WAKEUP, mNowElapsedTest + 5, getNewMockPendingIntent());
         setTestAlarm(ELAPSED_REALTIME_WAKEUP, mNowElapsedTest + 6, getNewMockPendingIntent());
         assertEquals(mNowElapsedTest + 5, mTestTimer.getElapsed());
@@ -366,6 +401,7 @@
 
     @Test
     public void testStandbyBucketDelay_frequent() throws Exception {
+        setQuotasEnabled(false);
         setTestAlarm(ELAPSED_REALTIME_WAKEUP, mNowElapsedTest + 5, getNewMockPendingIntent());
         setTestAlarm(ELAPSED_REALTIME_WAKEUP, mNowElapsedTest + 6, getNewMockPendingIntent());
         assertEquals(mNowElapsedTest + 5, mTestTimer.getElapsed());
@@ -385,6 +421,7 @@
 
     @Test
     public void testStandbyBucketDelay_rare() throws Exception {
+        setQuotasEnabled(false);
         setTestAlarm(ELAPSED_REALTIME_WAKEUP, mNowElapsedTest + 5, getNewMockPendingIntent());
         setTestAlarm(ELAPSED_REALTIME_WAKEUP, mNowElapsedTest + 6, getNewMockPendingIntent());
         assertEquals(mNowElapsedTest + 5, mTestTimer.getElapsed());
@@ -402,6 +439,253 @@
         assertEquals("Incorrect next alarm trigger.", expectedNextTrigger, mTestTimer.getElapsed());
     }
 
+    private void testQuotasDeferralOnSet(int standbyBucket) throws Exception {
+        final int quota = mService.getQuotaForBucketLocked(standbyBucket);
+        when(mUsageStatsManagerInternal.getAppStandbyBucket(eq(TEST_CALLING_PACKAGE), anyInt(),
+                anyLong())).thenReturn(standbyBucket);
+        final long firstTrigger = mNowElapsedTest + 10;
+        for (int i = 0; i < quota; i++) {
+            setTestAlarm(ELAPSED_REALTIME_WAKEUP, mNowElapsedTest + 10 + i,
+                    getNewMockPendingIntent());
+            mNowElapsedTest = mTestTimer.getElapsed();
+            mTestTimer.expire();
+        }
+        // This one should get deferred on set
+        setTestAlarm(ELAPSED_REALTIME_WAKEUP, mNowElapsedTest + quota + 10,
+                getNewMockPendingIntent());
+        final long expectedNextTrigger = firstTrigger + 1 + mAppStandbyWindow;
+        assertEquals("Incorrect next alarm trigger", expectedNextTrigger, mTestTimer.getElapsed());
+    }
+
+    private void testQuotasDeferralOnExpiration(int standbyBucket) throws Exception {
+        final int quota = mService.getQuotaForBucketLocked(standbyBucket);
+        when(mUsageStatsManagerInternal.getAppStandbyBucket(eq(TEST_CALLING_PACKAGE), anyInt(),
+                anyLong())).thenReturn(standbyBucket);
+        final long firstTrigger = mNowElapsedTest + 10;
+        for (int i = 0; i < quota; i++) {
+            setTestAlarm(ELAPSED_REALTIME_WAKEUP, mNowElapsedTest + 10 + i,
+                    getNewMockPendingIntent());
+        }
+        // This one should get deferred after the latest alarm expires
+        setTestAlarm(ELAPSED_REALTIME_WAKEUP, mNowElapsedTest + quota + 10,
+                getNewMockPendingIntent());
+        for (int i = 0; i < quota; i++) {
+            mNowElapsedTest = mTestTimer.getElapsed();
+            mTestTimer.expire();
+        }
+        final long expectedNextTrigger = firstTrigger + 1 + mAppStandbyWindow;
+        assertEquals("Incorrect next alarm trigger", expectedNextTrigger, mTestTimer.getElapsed());
+    }
+
+    private void testQuotasNoDeferral(int standbyBucket) throws Exception {
+        final int quota = mService.getQuotaForBucketLocked(standbyBucket);
+        when(mUsageStatsManagerInternal.getAppStandbyBucket(eq(TEST_CALLING_PACKAGE), anyInt(),
+                anyLong())).thenReturn(standbyBucket);
+        final long firstTrigger = mNowElapsedTest + 10;
+        for (int i = 0; i < quota; i++) {
+            setTestAlarm(ELAPSED_REALTIME_WAKEUP, mNowElapsedTest + 10 + i,
+                    getNewMockPendingIntent());
+        }
+        // This delivery time maintains the quota invariant. Should not be deferred.
+        final long expectedNextTrigger = firstTrigger + mAppStandbyWindow + 5;
+        setTestAlarm(ELAPSED_REALTIME_WAKEUP, expectedNextTrigger, getNewMockPendingIntent());
+        for (int i = 0; i < quota; i++) {
+            mNowElapsedTest = mTestTimer.getElapsed();
+            mTestTimer.expire();
+        }
+        assertEquals("Incorrect next alarm trigger", expectedNextTrigger, mTestTimer.getElapsed());
+    }
+
+    @Test
+    public void testActiveQuota_deferredOnSet() throws Exception {
+        setQuotasEnabled(true);
+        testQuotasDeferralOnSet(STANDBY_BUCKET_ACTIVE);
+    }
+
+    @Test
+    public void testActiveQuota_deferredOnExpiration() throws Exception {
+        setQuotasEnabled(true);
+        testQuotasDeferralOnExpiration(STANDBY_BUCKET_ACTIVE);
+    }
+
+    @Test
+    public void testActiveQuota_notDeferred() throws Exception {
+        setQuotasEnabled(true);
+        testQuotasNoDeferral(STANDBY_BUCKET_ACTIVE);
+    }
+
+    @Test
+    public void testWorkingQuota_deferredOnSet() throws Exception {
+        setQuotasEnabled(true);
+        testQuotasDeferralOnSet(STANDBY_BUCKET_WORKING_SET);
+    }
+
+    @Test
+    public void testWorkingQuota_deferredOnExpiration() throws Exception {
+        setQuotasEnabled(true);
+        testQuotasDeferralOnExpiration(STANDBY_BUCKET_WORKING_SET);
+    }
+
+    @Test
+    public void testWorkingQuota_notDeferred() throws Exception {
+        setQuotasEnabled(true);
+        testQuotasNoDeferral(STANDBY_BUCKET_WORKING_SET);
+    }
+
+    @Test
+    public void testFrequentQuota_deferredOnSet() throws Exception {
+        setQuotasEnabled(true);
+        testQuotasDeferralOnSet(STANDBY_BUCKET_FREQUENT);
+    }
+
+    @Test
+    public void testFrequentQuota_deferredOnExpiration() throws Exception {
+        setQuotasEnabled(true);
+        testQuotasDeferralOnExpiration(STANDBY_BUCKET_FREQUENT);
+    }
+
+    @Test
+    public void testFrequentQuota_notDeferred() throws Exception {
+        setQuotasEnabled(true);
+        testQuotasNoDeferral(STANDBY_BUCKET_FREQUENT);
+    }
+
+    @Test
+    public void testRareQuota_deferredOnSet() throws Exception {
+        setQuotasEnabled(true);
+        testQuotasDeferralOnSet(STANDBY_BUCKET_RARE);
+    }
+
+    @Test
+    public void testRareQuota_deferredOnExpiration() throws Exception {
+        setQuotasEnabled(true);
+        testQuotasDeferralOnExpiration(STANDBY_BUCKET_RARE);
+    }
+
+    @Test
+    public void testRareQuota_notDeferred() throws Exception {
+        setQuotasEnabled(true);
+        testQuotasNoDeferral(STANDBY_BUCKET_RARE);
+    }
+
+    private void sendAndHandleBucketChanged(int bucket) {
+        when(mUsageStatsManagerInternal.getAppStandbyBucket(eq(TEST_CALLING_PACKAGE), anyInt(),
+                anyLong())).thenReturn(bucket);
+        // Stubbing the handler call to simulate it synchronously here.
+        doReturn(true).when(mService.mHandler).sendMessage(any(Message.class));
+        mAppStandbyListener.onAppIdleStateChanged(TEST_CALLING_PACKAGE,
+                UserHandle.getUserId(TEST_CALLING_UID), false, bucket, 0);
+        final ArgumentCaptor<Message> messageCaptor = ArgumentCaptor.forClass(Message.class);
+        verify(mService.mHandler, atLeastOnce()).sendMessage(messageCaptor.capture());
+        final Message lastMessage = messageCaptor.getValue();
+        assertEquals("Unexpected message send to handler", lastMessage.what,
+                APP_STANDBY_BUCKET_CHANGED);
+        mService.mHandler.handleMessage(lastMessage);
+    }
+
+    @Test
+    public void testQuotaDowngrade() throws Exception {
+        setQuotasEnabled(true);
+        final int workingQuota = mService.getQuotaForBucketLocked(STANDBY_BUCKET_WORKING_SET);
+        when(mUsageStatsManagerInternal.getAppStandbyBucket(eq(TEST_CALLING_PACKAGE), anyInt(),
+                anyLong())).thenReturn(STANDBY_BUCKET_WORKING_SET);
+
+        final long firstTrigger = mNowElapsedTest + 10;
+        for (int i = 0; i < workingQuota; i++) {
+            setTestAlarm(ELAPSED_REALTIME_WAKEUP, firstTrigger + i, getNewMockPendingIntent());
+        }
+        // No deferrals now.
+        for (int i = 0; i < workingQuota - 1; i++) {
+            mNowElapsedTest = mTestTimer.getElapsed();
+            assertEquals(firstTrigger + i, mNowElapsedTest);
+            mTestTimer.expire();
+        }
+        // The next upcoming alarm in queue should also be set as expected.
+        assertEquals(firstTrigger + workingQuota - 1, mTestTimer.getElapsed());
+        // Downgrading the bucket now
+        sendAndHandleBucketChanged(STANDBY_BUCKET_RARE);
+        final int rareQuota = mService.getQuotaForBucketLocked(STANDBY_BUCKET_RARE);
+        // The last alarm should now be deferred.
+        final long expectedNextTrigger = (firstTrigger + workingQuota - 1 - rareQuota)
+                + mAppStandbyWindow + 1;
+        assertEquals("Incorrect next alarm trigger", expectedNextTrigger, mTestTimer.getElapsed());
+    }
+
+    @Test
+    public void testQuotaUpgrade() throws Exception {
+        setQuotasEnabled(true);
+        final int frequentQuota = mService.getQuotaForBucketLocked(STANDBY_BUCKET_FREQUENT);
+        when(mUsageStatsManagerInternal.getAppStandbyBucket(eq(TEST_CALLING_PACKAGE), anyInt(),
+                anyLong())).thenReturn(STANDBY_BUCKET_FREQUENT);
+
+        final long firstTrigger = mNowElapsedTest + 10;
+        for (int i = 0; i < frequentQuota + 1; i++) {
+            setTestAlarm(ELAPSED_REALTIME_WAKEUP, firstTrigger + i, getNewMockPendingIntent());
+            if (i < frequentQuota) {
+                mNowElapsedTest = mTestTimer.getElapsed();
+                mTestTimer.expire();
+            }
+        }
+        // The last alarm should be deferred due to exceeding the quota
+        final long deferredTrigger = firstTrigger + 1 + mAppStandbyWindow;
+        assertEquals(deferredTrigger, mTestTimer.getElapsed());
+
+        // Upgrading the bucket now
+        sendAndHandleBucketChanged(STANDBY_BUCKET_ACTIVE);
+        // The last alarm should now be rescheduled to go as per original expectations
+        final long originalTrigger = firstTrigger + frequentQuota;
+        assertEquals("Incorrect next alarm trigger", originalTrigger, mTestTimer.getElapsed());
+    }
+
+    private void sendAndHandleParoleChanged(boolean parole) {
+        // Stubbing the handler call to simulate it synchronously here.
+        doReturn(true).when(mService.mHandler).sendMessage(any(Message.class));
+        mAppStandbyListener.onParoleStateChanged(parole);
+        final ArgumentCaptor<Message> messageCaptor = ArgumentCaptor.forClass(Message.class);
+        verify(mService.mHandler, atLeastOnce()).sendMessage(messageCaptor.capture());
+        final Message lastMessage = messageCaptor.getValue();
+        assertEquals("Unexpected message send to handler", lastMessage.what,
+                APP_STANDBY_PAROLE_CHANGED);
+        mService.mHandler.handleMessage(lastMessage);
+    }
+
+    @Test
+    public void testParole() throws Exception {
+        setQuotasEnabled(true);
+        final int workingQuota = mService.getQuotaForBucketLocked(STANDBY_BUCKET_WORKING_SET);
+        when(mUsageStatsManagerInternal.getAppStandbyBucket(eq(TEST_CALLING_PACKAGE), anyInt(),
+                anyLong())).thenReturn(STANDBY_BUCKET_WORKING_SET);
+
+        final long firstTrigger = mNowElapsedTest + 10;
+        final int totalAlarms = workingQuota + 10;
+        for (int i = 0; i < totalAlarms; i++) {
+            setTestAlarm(ELAPSED_REALTIME_WAKEUP, firstTrigger + i, getNewMockPendingIntent());
+        }
+        // Use up the quota, no deferrals expected.
+        for (int i = 0; i < workingQuota; i++) {
+            mNowElapsedTest = mTestTimer.getElapsed();
+            assertEquals(firstTrigger + i, mNowElapsedTest);
+            mTestTimer.expire();
+        }
+        // Any subsequent alarms in queue should all be deferred
+        assertEquals(firstTrigger + mAppStandbyWindow + 1, mTestTimer.getElapsed());
+        // Paroling now
+        sendAndHandleParoleChanged(true);
+
+        // Subsequent alarms should now go off as per original expectations.
+        for (int i = 0; i < 5; i++) {
+            mNowElapsedTest = mTestTimer.getElapsed();
+            assertEquals(firstTrigger + workingQuota + i, mNowElapsedTest);
+            mTestTimer.expire();
+        }
+        // Come out of parole
+        sendAndHandleParoleChanged(false);
+
+        // Subsequent alarms should again get deferred
+        final long expectedNextTrigger = (firstTrigger + 5) + 1 + mAppStandbyWindow;
+        assertEquals("Incorrect next alarm trigger", expectedNextTrigger, mTestTimer.getElapsed());
+    }
+
     @Test
     public void testAlarmRestrictedInBatterSaver() throws Exception {
         final ArgumentCaptor<AppStateTracker.Listener> listenerArgumentCaptor =
diff --git a/services/tests/servicestests/src/com/android/server/power/batterysaver/BatterySaverStateMachineTest.java b/services/tests/mockingservicestests/src/com/android/server/power/batterysaver/BatterySaverStateMachineTest.java
similarity index 73%
rename from services/tests/servicestests/src/com/android/server/power/batterysaver/BatterySaverStateMachineTest.java
rename to services/tests/mockingservicestests/src/com/android/server/power/batterysaver/BatterySaverStateMachineTest.java
index f31ca55..5009d64 100644
--- a/services/tests/servicestests/src/com/android/server/power/batterysaver/BatterySaverStateMachineTest.java
+++ b/services/tests/mockingservicestests/src/com/android/server/power/batterysaver/BatterySaverStateMachineTest.java
@@ -19,51 +19,41 @@
 import static org.mockito.ArgumentMatchers.anyBoolean;
 import static org.mockito.ArgumentMatchers.anyInt;
 import static org.mockito.Mockito.doAnswer;
+import static org.mockito.Mockito.doReturn;
 import static org.mockito.Mockito.mock;
 import static org.mockito.Mockito.when;
 
+import android.app.NotificationManager;
 import android.content.ContentResolver;
+import android.content.Context;
 import android.content.res.Resources;
 import android.provider.Settings.Global;
-import android.test.mock.MockContext;
 
 import androidx.test.filters.SmallTest;
 import androidx.test.runner.AndroidJUnit4;
 
-import com.google.common.base.Objects;
-
 import org.junit.Before;
 import org.junit.Test;
 import org.junit.runner.RunWith;
 
 import java.util.HashMap;
+import java.util.Objects;
 
 /**
- atest $ANDROID_BUILD_TOP/frameworks/base/services/tests/servicestests/src/com/android/server/power/batterysaver/BatterySaverStateMachineTest.java
+ * atest com.android.server.power.batterysaver.BatterySaverStateMachineTest
  */
 @SmallTest
 @RunWith(AndroidJUnit4.class)
 public class BatterySaverStateMachineTest {
 
-    private MyMockContext mMockContext;
+    private Context mMockContext;
     private ContentResolver mMockContextResolver;
     private BatterySaverController mMockBatterySaverController;
+    private NotificationManager mMockNotificationManager;
     private Device mDevice;
     private TestableBatterySaverStateMachine mTarget;
     private Resources mMockResources;
 
-    private class MyMockContext extends MockContext {
-        @Override
-        public ContentResolver getContentResolver() {
-            return mMockContextResolver;
-        }
-
-        @Override
-        public Resources getResources() {
-            return mMockResources;
-        }
-    }
-
     private DevicePersistedState mPersistedState;
 
     private class DevicePersistedState {
@@ -116,6 +106,10 @@
                     mPersistedState.global.getOrDefault(Global.LOW_POWER_MODE, 0) != 0,
                     mPersistedState.global.getOrDefault(Global.LOW_POWER_MODE_STICKY, 0) != 0,
                     mDevice.getLowPowerModeTriggerLevel(),
+                    mPersistedState.global.getOrDefault(
+                            Global.LOW_POWER_MODE_STICKY_AUTO_DISABLE_ENABLED, 0) != 0,
+                    mPersistedState.global.getOrDefault(
+                            Global.LOW_POWER_MODE_STICKY_AUTO_DISABLE_LEVEL, 90),
                     mPersistedState.global.getOrDefault(Global.AUTOMATIC_POWER_SAVER_MODE, 0),
                     mPersistedState.global.getOrDefault(
                             Global.DYNAMIC_POWER_SAVINGS_ENABLED, 0) != 0,
@@ -137,13 +131,13 @@
      * Test target class.
      */
     private class TestableBatterySaverStateMachine extends BatterySaverStateMachine {
-        public TestableBatterySaverStateMachine() {
+        TestableBatterySaverStateMachine() {
             super(new Object(), mMockContext, mMockBatterySaverController);
         }
 
         @Override
         protected void putGlobalSetting(String key, int value) {
-            if (Objects.equal(mPersistedState.global.get(key), value)) {
+            if (Objects.equals(mPersistedState.global.get(key), value)) {
                 return;
             }
             mDevice.putGlobalSetting(key, value);
@@ -163,15 +157,25 @@
         void runOnBgThreadLazy(Runnable r, int delayMillis) {
             r.run();
         }
+
+        @Override
+        void triggerDynamicModeNotification() {
+            // Do nothing
+        }
     }
 
     @Before
     public void setUp() {
-        mMockContext = new MyMockContext();
+        mMockContext = mock(Context.class);
         mMockContextResolver = mock(ContentResolver.class);
         mMockBatterySaverController = mock(BatterySaverController.class);
+        mMockNotificationManager = mock(NotificationManager.class);
         mMockResources = mock(Resources.class);
 
+        doReturn(mMockContextResolver).when(mMockContext).getContentResolver();
+        doReturn(mMockResources).when(mMockContext).getResources();
+        doReturn(mMockNotificationManager).when(mMockContext)
+                .getSystemService(NotificationManager.class);
         doAnswer((inv) -> mDevice.batterySaverEnabled = inv.getArgument(0))
                 .when(mMockBatterySaverController).enableBatterySaver(anyBoolean(), anyInt());
         when(mMockBatterySaverController.isEnabled())
@@ -446,8 +450,9 @@
     }
 
     @Test
-    public void testAutoBatterySaver_withSticky() {
+    public void testAutoBatterySaver_withSticky_withAutoOffDisabled() {
         mDevice.putGlobalSetting(Global.LOW_POWER_MODE_TRIGGER_LEVEL, 50);
+        mDevice.putGlobalSetting(Global.LOW_POWER_MODE_STICKY_AUTO_DISABLE_ENABLED, 0);
 
         mTarget.setBatterySaverEnabledManually(true);
 
@@ -518,6 +523,197 @@
     }
 
     @Test
+    public void testAutoBatterySaver_withSticky_withAutoOffEnabled() {
+        mDevice.putGlobalSetting(Global.LOW_POWER_MODE_TRIGGER_LEVEL, 50);
+        mDevice.putGlobalSetting(Global.LOW_POWER_MODE_STICKY_AUTO_DISABLE_ENABLED, 1);
+        mDevice.putGlobalSetting(Global.LOW_POWER_MODE_STICKY_AUTO_DISABLE_LEVEL, 90);
+
+        // Scenario 1: User turns BS on manually above the threshold, it shouldn't turn off even
+        // with battery level change above threshold.
+        mDevice.setBatteryLevel(100);
+        mTarget.setBatterySaverEnabledManually(true);
+
+        assertEquals(true, mDevice.batterySaverEnabled);
+        assertEquals(100, mPersistedState.batteryLevel);
+        assertEquals(false, mPersistedState.batteryLow);
+
+        mDevice.setBatteryLevel(95);
+
+        assertEquals(true, mDevice.batterySaverEnabled); // Stays on.
+        assertEquals(95, mPersistedState.batteryLevel);
+        assertEquals(false, mPersistedState.batteryLow);
+
+        // Scenario 2: User turns BS on manually above the threshold then charges device. BS
+        // shouldn't turn back on.
+        mDevice.setPowered(true);
+
+        assertEquals(false, mDevice.batterySaverEnabled);
+        assertEquals(95, mPersistedState.batteryLevel);
+        assertEquals(false, mPersistedState.batteryLow);
+
+        mDevice.setBatteryLevel(97);
+        mDevice.setPowered(false);
+
+        assertEquals(false, mDevice.batterySaverEnabled); // Sticky BS no longer enabled.
+        assertEquals(97, mPersistedState.batteryLevel);
+        assertEquals(false, mPersistedState.batteryLow);
+
+        // Scenario 3: User turns BS on manually above the threshold. Device drains below
+        // threshold and then charged to below threshold. Sticky BS should activate.
+        mTarget.setBatterySaverEnabledManually(true);
+        mDevice.setBatteryLevel(30);
+
+        assertEquals(true, mDevice.batterySaverEnabled);
+        assertEquals(30, mPersistedState.batteryLevel);
+        assertEquals(true, mPersistedState.batteryLow);
+
+        mDevice.setPowered(true);
+        mDevice.setBatteryLevel(80);
+
+        assertEquals(false, mDevice.batterySaverEnabled);
+        assertEquals(80, mPersistedState.batteryLevel);
+        assertEquals(false, mPersistedState.batteryLow);
+
+        mDevice.setPowered(false);
+
+        assertEquals(true, mDevice.batterySaverEnabled); // Still enabled.
+        assertEquals(80, mPersistedState.batteryLevel);
+        assertEquals(false, mPersistedState.batteryLow);
+
+        mDevice.setBatteryLevel(30);
+
+        assertEquals(true, mDevice.batterySaverEnabled);
+        assertEquals(30, mPersistedState.batteryLevel);
+        assertEquals(true, mPersistedState.batteryLow);
+
+        // Scenario 4: User turns BS on manually above the threshold. Device drains below
+        // threshold and is eventually charged to above threshold. Sticky BS should turn off.
+        mDevice.setPowered(true);
+        mDevice.setBatteryLevel(90);
+
+        assertEquals(false, mDevice.batterySaverEnabled);
+        assertEquals(90, mPersistedState.batteryLevel);
+        assertEquals(false, mPersistedState.batteryLow);
+
+        mDevice.setPowered(false);
+
+        assertEquals(false, mDevice.batterySaverEnabled); // Sticky BS no longer enabled.
+        assertEquals(90, mPersistedState.batteryLevel);
+        assertEquals(false, mPersistedState.batteryLow);
+
+        // Scenario 5: User turns BS on manually below threshold and charges to below threshold.
+        // Sticky BS should activate.
+        mDevice.setBatteryLevel(70);
+
+        assertEquals(false, mDevice.batterySaverEnabled);
+        assertEquals(70, mPersistedState.batteryLevel);
+        assertEquals(false, mPersistedState.batteryLow);
+
+        mTarget.setBatterySaverEnabledManually(true);
+
+        assertEquals(true, mDevice.batterySaverEnabled);
+        assertEquals(70, mPersistedState.batteryLevel);
+        assertEquals(false, mPersistedState.batteryLow);
+
+        mDevice.setPowered(true);
+        mDevice.setBatteryLevel(80);
+
+        assertEquals(false, mDevice.batterySaverEnabled);
+        assertEquals(80, mPersistedState.batteryLevel);
+        assertEquals(false, mPersistedState.batteryLow);
+
+        mDevice.setPowered(false);
+
+        assertEquals(true, mDevice.batterySaverEnabled); // Sticky BS still on.
+        assertEquals(80, mPersistedState.batteryLevel);
+        assertEquals(false, mPersistedState.batteryLow);
+
+        // Scenario 6: User turns BS on manually below threshold and eventually charges to above
+        // threshold. Sticky BS should turn off.
+
+        mDevice.setPowered(true);
+        mDevice.setBatteryLevel(95);
+        mDevice.setPowered(false);
+
+        assertEquals(false, mDevice.batterySaverEnabled);
+        assertEquals(95, mPersistedState.batteryLevel);
+        assertEquals(false, mPersistedState.batteryLow);
+
+        // Scenario 7: User turns BS on above threshold and then reboots device. Sticky BS
+        // shouldn't activate.
+        mTarget.setBatterySaverEnabledManually(true);
+        mPersistedState.batteryLevel = 93;
+
+        initDevice();
+
+        assertEquals(false, mDevice.batterySaverEnabled);
+        assertEquals(93, mPersistedState.batteryLevel);
+        assertEquals(false, mPersistedState.batteryLow);
+
+        // Scenario 8: User turns BS on below threshold and then reboots device without charging.
+        // Sticky BS should activate.
+        mDevice.setBatteryLevel(75);
+        mTarget.setBatterySaverEnabledManually(true);
+        assertEquals(true, mDevice.batterySaverEnabled);
+        assertEquals(75, mPersistedState.batteryLevel);
+        assertEquals(false, mPersistedState.batteryLow);
+
+        initDevice();
+
+        assertEquals(true, mDevice.batterySaverEnabled);
+        assertEquals(75, mPersistedState.batteryLevel);
+        assertEquals(false, mPersistedState.batteryLow);
+
+        // Scenario 9: User turns BS on below threshold and then reboots device after charging
+        // above threshold. Sticky BS shouldn't activate.
+        mDevice.setBatteryLevel(80);
+        mTarget.setBatterySaverEnabledManually(true);
+        mPersistedState.batteryLevel = 100;
+
+        initDevice();
+
+        assertEquals(false, mDevice.batterySaverEnabled);
+        assertEquals(100, mPersistedState.batteryLevel);
+        assertEquals(false, mPersistedState.batteryLow);
+
+        // Scenario 10: Somehow autoDisableLevel is set to a value below lowPowerModeTriggerLevel
+        // and then user enables manually above both thresholds, discharges below
+        // autoDisableLevel and then charges up to between autoDisableLevel and
+        // lowPowerModeTriggerLevel. Sticky BS shouldn't activate, but BS should still be on
+        // because the level is below lowPowerModeTriggerLevel.
+        mDevice.putGlobalSetting(Global.LOW_POWER_MODE_TRIGGER_LEVEL, 75);
+        mDevice.putGlobalSetting(Global.LOW_POWER_MODE_STICKY_AUTO_DISABLE_ENABLED, 1);
+        mDevice.putGlobalSetting(Global.LOW_POWER_MODE_STICKY_AUTO_DISABLE_LEVEL, 60);
+        initDevice();
+
+        mDevice.setBatteryLevel(90);
+        mTarget.setBatterySaverEnabledManually(true);
+
+        assertEquals(true, mDevice.batterySaverEnabled);
+        assertEquals(90, mPersistedState.batteryLevel);
+        assertEquals(false, mPersistedState.batteryLow);
+
+        mDevice.setBatteryLevel(50);
+
+        assertEquals(true, mDevice.batterySaverEnabled);
+        assertEquals(50, mPersistedState.batteryLevel);
+        assertEquals(true, mPersistedState.batteryLow);
+
+        mDevice.setPowered(true);
+        mDevice.setBatteryLevel(65);
+
+        assertEquals(false, mDevice.batterySaverEnabled);
+        assertEquals(65, mPersistedState.batteryLevel);
+        assertEquals(true, mPersistedState.batteryLow);
+
+        mDevice.setPowered(false);
+
+        assertEquals(true, mDevice.batterySaverEnabled);
+        assertEquals(65, mPersistedState.batteryLevel);
+        assertEquals(true, mPersistedState.batteryLow);
+    }
+
+    @Test
     public void testAutoBatterySaver_withStickyDisabled() {
         when(mMockResources.getBoolean(
                 com.android.internal.R.bool.config_batterySaverStickyBehaviourDisabled))
diff --git a/services/tests/servicestests/src/com/android/server/accessibility/AccessibilityInputFilterTest.java b/services/tests/servicestests/src/com/android/server/accessibility/AccessibilityInputFilterTest.java
new file mode 100644
index 0000000..782dc3e
--- /dev/null
+++ b/services/tests/servicestests/src/com/android/server/accessibility/AccessibilityInputFilterTest.java
@@ -0,0 +1,275 @@
+/*
+ * 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.accessibility;
+
+import static android.view.Display.DEFAULT_DISPLAY;
+import static android.view.DisplayAdjustments.DEFAULT_DISPLAY_ADJUSTMENTS;
+import static android.view.WindowManagerPolicyConstants.FLAG_PASS_TO_USER;
+
+import static com.android.server.accessibility.AccessibilityInputFilter.FLAG_FEATURE_AUTOCLICK;
+import static com.android.server.accessibility.AccessibilityInputFilter.FLAG_FEATURE_CONTROL_SCREEN_MAGNIFIER;
+import static com.android.server.accessibility.AccessibilityInputFilter.FLAG_FEATURE_FILTER_KEY_EVENTS;
+import static com.android.server.accessibility.AccessibilityInputFilter.FLAG_FEATURE_INJECT_MOTION_EVENTS;
+import static com.android.server.accessibility.AccessibilityInputFilter.FLAG_FEATURE_TOUCH_EXPLORATION;
+import static com.android.server.accessibility.AccessibilityInputFilter.FLAG_FEATURE_TRIGGERED_SCREEN_MAGNIFIER;
+
+import static org.junit.Assert.assertEquals;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.spy;
+import static org.mockito.Mockito.when;
+
+import android.content.Context;
+import android.hardware.display.DisplayManagerGlobal;
+import android.os.Looper;
+import android.os.SystemClock;
+import android.util.SparseArray;
+import android.view.Display;
+import android.view.DisplayInfo;
+import android.view.InputDevice;
+import android.view.InputEvent;
+import android.view.KeyEvent;
+import android.view.MotionEvent;
+
+import androidx.test.InstrumentationRegistry;
+import androidx.test.runner.AndroidJUnit4;
+
+import org.junit.After;
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.mockito.MockitoAnnotations;
+
+import java.util.ArrayList;
+import java.util.List;
+
+/**
+ * Tests for AccessibilityInputFilterTest
+ */
+@RunWith(AndroidJUnit4.class)
+public class AccessibilityInputFilterTest {
+    private static int sNextDisplayId = DEFAULT_DISPLAY;
+    private static final int SECOND_DISPLAY = DEFAULT_DISPLAY + 1;
+    private static final float DEFAULT_X = 100f;
+    private static final float DEFAULT_Y = 100f;
+
+    private final SparseArray<EventStreamTransformation> mEventHandler = new SparseArray<>(0);
+    private final ArrayList<Display> mDisplayList = new ArrayList<>();
+    private final int mFeatures = FLAG_FEATURE_AUTOCLICK
+            | FLAG_FEATURE_TOUCH_EXPLORATION
+            | FLAG_FEATURE_CONTROL_SCREEN_MAGNIFIER
+            | FLAG_FEATURE_TRIGGERED_SCREEN_MAGNIFIER
+            | FLAG_FEATURE_INJECT_MOTION_EVENTS
+            | FLAG_FEATURE_FILTER_KEY_EVENTS;
+
+    // The expected order of EventStreamTransformations.
+    private final Class[] mExpectedEventHandlerTypes =
+            {KeyboardInterceptor.class, MotionEventInjector.class,
+                    MagnificationGestureHandler.class, TouchExplorer.class,
+                    AutoclickController.class, AccessibilityInputFilter.class};
+
+    private MagnificationController mMockMagnificationController;
+    private AccessibilityManagerService mAms;
+    private AccessibilityInputFilter mA11yInputFilter;
+    private EventCaptor mCaptor1;
+    private EventCaptor mCaptor2;
+    private long mLastDownTime = Integer.MIN_VALUE;
+
+    private class EventCaptor implements EventStreamTransformation {
+        List<InputEvent> mEvents = new ArrayList<>();
+
+        @Override
+        public void onMotionEvent(MotionEvent event, MotionEvent rawEvent, int policyFlags) {
+            mEvents.add(event.copy());
+        }
+
+        @Override
+        public void onKeyEvent(KeyEvent event, int policyFlags) {
+            mEvents.add(event.copy());
+        }
+
+        @Override
+        public void setNext(EventStreamTransformation next) {
+        }
+
+        @Override
+        public EventStreamTransformation getNext() {
+            return null;
+        }
+
+        @Override
+        public void clearEvents(int inputSource) {
+            clear();
+        }
+
+        private void clear() {
+            mEvents.clear();
+        }
+    }
+
+    @Before
+    public void setUp() {
+        MockitoAnnotations.initMocks(this);
+        Context context = InstrumentationRegistry.getContext();
+
+        setDisplayCount(1);
+        mAms = spy(new AccessibilityManagerService(context));
+        mMockMagnificationController = mock(MagnificationController.class);
+        mA11yInputFilter = new AccessibilityInputFilter(context, mAms, mEventHandler);
+        mA11yInputFilter.onInstalled();
+
+        when(mAms.getValidDisplayList()).thenReturn(mDisplayList);
+        when(mAms.getMagnificationController()).thenReturn(mMockMagnificationController);
+    }
+
+    @After
+    public void tearDown() {
+        mA11yInputFilter.onUninstalled();
+    }
+
+    @Test
+    public void testEventHandler_shouldChangeAfterSetUserAndEnabledFeatures() {
+        prepareLooper();
+
+        // Check if there is no mEventHandler when no feature is set.
+        assertEquals(0, mEventHandler.size());
+
+        // Check if mEventHandler is added/removed after setting a11y features.
+        mA11yInputFilter.setUserAndEnabledFeatures(0, mFeatures);
+        assertEquals(1, mEventHandler.size());
+
+        mA11yInputFilter.setUserAndEnabledFeatures(0, 0);
+        assertEquals(0, mEventHandler.size());
+    }
+
+    @Test
+    public void testEventHandler_shouldChangeAfterOnDisplayChanged() {
+        prepareLooper();
+
+        // Check if there is only one mEventHandler when there is one default display.
+        mA11yInputFilter.setUserAndEnabledFeatures(0, mFeatures);
+        assertEquals(1, mEventHandler.size());
+
+        // Check if it has correct numbers of mEventHandler for corresponding displays.
+        setDisplayCount(4);
+        mA11yInputFilter.onDisplayChanged();
+        assertEquals(4, mEventHandler.size());
+
+        setDisplayCount(2);
+        mA11yInputFilter.onDisplayChanged();
+        assertEquals(2, mEventHandler.size());
+    }
+
+    @Test
+    public void testEventHandler_shouldHaveCorrectOrderForEventStreamTransformation() {
+        prepareLooper();
+
+        setDisplayCount(2);
+        mA11yInputFilter.setUserAndEnabledFeatures(0, mFeatures);
+        assertEquals(2, mEventHandler.size());
+
+        // Check if mEventHandler for each display has correct order of the
+        // EventStreamTransformations.
+        EventStreamTransformation next = mEventHandler.get(DEFAULT_DISPLAY);
+        for (int i = 0; next != null; i++) {
+            assertEquals(next.getClass(), mExpectedEventHandlerTypes[i]);
+            next = next.getNext();
+        }
+
+        next = mEventHandler.get(SECOND_DISPLAY);
+        // Start from index 1 because KeyboardInterceptor only exists in EventHandler for
+        // DEFAULT_DISPLAY.
+        for (int i = 1; next != null; i++) {
+            assertEquals(next.getClass(), mExpectedEventHandlerTypes[i]);
+            next = next.getNext();
+        }
+    }
+
+    @Test
+    public void testInputEvent_shouldDispatchToCorrespondingEventHandlers() {
+        prepareLooper();
+
+        setDisplayCount(2);
+        mA11yInputFilter.setUserAndEnabledFeatures(0, mFeatures);
+        assertEquals(2, mEventHandler.size());
+
+        mCaptor1 = new EventCaptor();
+        mCaptor2 = new EventCaptor();
+        mEventHandler.put(DEFAULT_DISPLAY, mCaptor1);
+        mEventHandler.put(SECOND_DISPLAY, mCaptor2);
+
+        // InputEvent with different displayId should be dispatched to corresponding EventHandler.
+        send(downEvent(DEFAULT_DISPLAY, InputDevice.SOURCE_TOUCHSCREEN));
+        send(downEvent(SECOND_DISPLAY, InputDevice.SOURCE_TOUCHSCREEN));
+
+        assertEquals(1, mCaptor1.mEvents.size());
+        assertEquals(1, mCaptor2.mEvents.size());
+    }
+
+    @Test
+    public void testInputEvent_shouldClearEventsForAllEventHandlers() {
+        prepareLooper();
+
+        mA11yInputFilter.setUserAndEnabledFeatures(0, mFeatures);
+        assertEquals(1, mEventHandler.size());
+
+        mCaptor1 = new EventCaptor();
+        mEventHandler.put(DEFAULT_DISPLAY, mCaptor1);
+
+        send(downEvent(DEFAULT_DISPLAY, InputDevice.SOURCE_TOUCHSCREEN));
+        send(downEvent(DEFAULT_DISPLAY, InputDevice.SOURCE_TOUCHSCREEN));
+        assertEquals(2, mCaptor1.mEvents.size());
+
+        // InputEvent with different input source should trigger clearEvents() for each
+        // EventStreamTransformation in EventHandler.
+        send(downEvent(DEFAULT_DISPLAY, InputDevice.SOURCE_MOUSE));
+        assertEquals(1, mCaptor1.mEvents.size());
+    }
+
+    private static void prepareLooper() {
+        if (Looper.myLooper() == null) {
+            Looper.prepare();
+        }
+    }
+
+    private Display createStubDisplay(DisplayInfo displayInfo) {
+        final int displayId = sNextDisplayId++;
+        final Display display = new Display(DisplayManagerGlobal.getInstance(), displayId,
+                displayInfo, DEFAULT_DISPLAY_ADJUSTMENTS);
+        return display;
+    }
+
+    private void setDisplayCount(int count) {
+        sNextDisplayId = DEFAULT_DISPLAY;
+        mDisplayList.clear();
+        for (int i = 0; i < count; i++) {
+            mDisplayList.add(createStubDisplay(new DisplayInfo()));
+        }
+    }
+
+    private void send(InputEvent event) {
+        mA11yInputFilter.onInputEvent(event, /* policyFlags */ FLAG_PASS_TO_USER);
+    }
+
+    private MotionEvent downEvent(int displayId, int source) {
+        mLastDownTime = SystemClock.uptimeMillis();
+        final MotionEvent ev = MotionEvent.obtain(mLastDownTime, mLastDownTime,
+                MotionEvent.ACTION_DOWN, DEFAULT_X, DEFAULT_Y, 0);
+        ev.setDisplayId(displayId);
+        ev.setSource(source);
+        return ev;
+    }
+}
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/am/AppCompactorTest.java b/services/tests/servicestests/src/com/android/server/am/AppCompactorTest.java
new file mode 100644
index 0000000..1a231cf
--- /dev/null
+++ b/services/tests/servicestests/src/com/android/server/am/AppCompactorTest.java
@@ -0,0 +1,379 @@
+/*
+ * 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.am;
+
+import static android.provider.DeviceConfig.ActivityManager.KEY_COMPACT_ACTION_1;
+import static android.provider.DeviceConfig.ActivityManager.KEY_COMPACT_ACTION_2;
+import static android.provider.DeviceConfig.ActivityManager.KEY_COMPACT_THROTTLE_1;
+import static android.provider.DeviceConfig.ActivityManager.KEY_COMPACT_THROTTLE_2;
+import static android.provider.DeviceConfig.ActivityManager.KEY_COMPACT_THROTTLE_3;
+import static android.provider.DeviceConfig.ActivityManager.KEY_COMPACT_THROTTLE_4;
+import static android.provider.DeviceConfig.ActivityManager.KEY_USE_COMPACTION;
+
+import static com.android.server.am.ActivityManagerService.Injector;
+import static com.android.server.am.AppCompactor.compactActionIntToString;
+
+import static org.hamcrest.Matchers.is;
+import static org.junit.Assert.assertThat;
+
+import android.os.Handler;
+import android.os.HandlerThread;
+import android.provider.DeviceConfig;
+import android.support.test.uiautomator.UiDevice;
+
+import androidx.test.InstrumentationRegistry;
+import androidx.test.runner.AndroidJUnit4;
+
+import com.android.server.appop.AppOpsService;
+
+import org.junit.After;
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.mockito.Mock;
+import org.mockito.MockitoAnnotations;
+
+import java.io.File;
+import java.io.IOException;
+import java.util.concurrent.CountDownLatch;
+import java.util.concurrent.TimeUnit;
+
+/**
+ * Tests for {@link AppCompactor}.
+ *
+ * Build/Install/Run:
+ * atest FrameworksServicesTests:AppCompactorTest
+ */
+@RunWith(AndroidJUnit4.class)
+public final class AppCompactorTest {
+
+    @Mock private AppOpsService mAppOpsService;
+    private AppCompactor mCompactorUnderTest;
+    private HandlerThread mHandlerThread;
+    private Handler mHandler;
+    private CountDownLatch mCountDown;
+
+    private static void clearDeviceConfig() throws IOException  {
+        UiDevice uiDevice = UiDevice.getInstance(InstrumentationRegistry.getInstrumentation());
+        uiDevice.executeShellCommand(
+                "device_config delete activity_manager " + KEY_USE_COMPACTION);
+        uiDevice.executeShellCommand(
+                "device_config delete activity_manager " + KEY_COMPACT_ACTION_1);
+        uiDevice.executeShellCommand(
+                "device_config delete activity_manager " + KEY_COMPACT_ACTION_2);
+        uiDevice.executeShellCommand(
+                "device_config delete activity_manager " + KEY_COMPACT_THROTTLE_1);
+        uiDevice.executeShellCommand(
+                "device_config delete activity_manager " + KEY_COMPACT_THROTTLE_2);
+        uiDevice.executeShellCommand(
+                "device_config delete activity_manager " + KEY_COMPACT_THROTTLE_3);
+        uiDevice.executeShellCommand(
+                "device_config delete activity_manager " + KEY_COMPACT_THROTTLE_4);
+    }
+
+    @Before
+    public void setUp() throws IOException {
+        MockitoAnnotations.initMocks(this);
+        clearDeviceConfig();
+        mHandlerThread = new HandlerThread("");
+        mHandlerThread.start();
+        mHandler = new Handler(mHandlerThread.getLooper());
+        ActivityManagerService ams = new ActivityManagerService(new TestInjector());
+        mCompactorUnderTest = new AppCompactor(ams,
+                new AppCompactor.PropertyChangedCallbackForTest() {
+                    @Override
+                    public void onPropertyChanged() {
+                        if (mCountDown != null) {
+                            mCountDown.countDown();
+                        }
+                    }
+                });
+    }
+
+    @After
+    public void tearDown() throws IOException {
+        mHandlerThread.quit();
+        mCountDown = null;
+        clearDeviceConfig();
+    }
+
+    @Test
+    public void init_setsDefaults() {
+        mCompactorUnderTest.init();
+        assertThat(mCompactorUnderTest.useCompaction(),
+                is(mCompactorUnderTest.DEFAULT_USE_COMPACTION));
+        assertThat(mCompactorUnderTest.mCompactActionSome, is(
+                compactActionIntToString(mCompactorUnderTest.DEFAULT_COMPACT_ACTION_1)));
+        assertThat(mCompactorUnderTest.mCompactActionFull, is(
+                compactActionIntToString(mCompactorUnderTest.DEFAULT_COMPACT_ACTION_2)));
+        assertThat(mCompactorUnderTest.mCompactThrottleSomeSome,
+                is(AppCompactor.DEFAULT_COMPACT_THROTTLE_1));
+        assertThat(mCompactorUnderTest.mCompactThrottleSomeFull,
+                is(AppCompactor.DEFAULT_COMPACT_THROTTLE_2));
+        assertThat(mCompactorUnderTest.mCompactThrottleFullSome,
+                is(AppCompactor.DEFAULT_COMPACT_THROTTLE_3));
+        assertThat(mCompactorUnderTest.mCompactThrottleFullFull,
+                is(AppCompactor.DEFAULT_COMPACT_THROTTLE_4));
+    }
+
+    @Test
+    public void init_withDeviceConfigSetsParameters() {
+        // When the DeviceConfig already has a flag value stored (note this test will need to
+        // change if the default value changes from false).
+        assertThat(mCompactorUnderTest.DEFAULT_USE_COMPACTION, is(false));
+        DeviceConfig.setProperty(DeviceConfig.ActivityManager.NAMESPACE,
+                KEY_USE_COMPACTION, "true", false);
+        DeviceConfig.setProperty(DeviceConfig.ActivityManager.NAMESPACE,
+                KEY_COMPACT_ACTION_1,
+                Integer.toString((AppCompactor.DEFAULT_COMPACT_ACTION_1 + 1 % 3) + 1), false);
+        DeviceConfig.setProperty(DeviceConfig.ActivityManager.NAMESPACE,
+                KEY_COMPACT_ACTION_2,
+                Integer.toString((AppCompactor.DEFAULT_COMPACT_ACTION_2 + 1 % 3) + 1), false);
+        DeviceConfig.setProperty(DeviceConfig.ActivityManager.NAMESPACE,
+                KEY_COMPACT_THROTTLE_1,
+                Long.toString(AppCompactor.DEFAULT_COMPACT_THROTTLE_1 + 1), false);
+        DeviceConfig.setProperty(DeviceConfig.ActivityManager.NAMESPACE,
+                KEY_COMPACT_THROTTLE_2,
+                Long.toString(AppCompactor.DEFAULT_COMPACT_THROTTLE_2 + 1), false);
+        DeviceConfig.setProperty(DeviceConfig.ActivityManager.NAMESPACE,
+                KEY_COMPACT_THROTTLE_3,
+                Long.toString(AppCompactor.DEFAULT_COMPACT_THROTTLE_3 + 1), false);
+        DeviceConfig.setProperty(DeviceConfig.ActivityManager.NAMESPACE,
+                KEY_COMPACT_THROTTLE_4,
+                Long.toString(AppCompactor.DEFAULT_COMPACT_THROTTLE_4 + 1), false);
+
+        // Then calling init will read and set that flag.
+        mCompactorUnderTest.init();
+        assertThat(mCompactorUnderTest.useCompaction(), is(true));
+        assertThat(mCompactorUnderTest.mCompactionThread.isAlive(), is(true));
+
+        assertThat(mCompactorUnderTest.mCompactActionSome,
+                is(compactActionIntToString((AppCompactor.DEFAULT_COMPACT_ACTION_1 + 1 % 3) + 1)));
+        assertThat(mCompactorUnderTest.mCompactActionFull,
+                is(compactActionIntToString((AppCompactor.DEFAULT_COMPACT_ACTION_2 + 1 % 3) + 1)));
+        assertThat(mCompactorUnderTest.mCompactThrottleSomeSome,
+                is(AppCompactor.DEFAULT_COMPACT_THROTTLE_1 + 1));
+        assertThat(mCompactorUnderTest.mCompactThrottleSomeFull,
+                is(AppCompactor.DEFAULT_COMPACT_THROTTLE_2 + 1));
+        assertThat(mCompactorUnderTest.mCompactThrottleFullSome,
+                is(AppCompactor.DEFAULT_COMPACT_THROTTLE_3 + 1));
+        assertThat(mCompactorUnderTest.mCompactThrottleFullFull,
+                is(AppCompactor.DEFAULT_COMPACT_THROTTLE_4 + 1));
+    }
+
+    @Test
+    public void useCompaction_listensToDeviceConfigChanges() throws InterruptedException {
+        assertThat(mCompactorUnderTest.useCompaction(),
+                is(mCompactorUnderTest.DEFAULT_USE_COMPACTION));
+        // When we call init and change some the flag value...
+        mCompactorUnderTest.init();
+        mCountDown = new CountDownLatch(1);
+        DeviceConfig.setProperty(DeviceConfig.ActivityManager.NAMESPACE,
+                KEY_USE_COMPACTION, "true", false);
+        assertThat(mCountDown.await(5, TimeUnit.SECONDS), is(true));
+
+        // Then that new flag value is updated in the implementation.
+        assertThat(mCompactorUnderTest.useCompaction(), is(true));
+        assertThat(mCompactorUnderTest.mCompactionThread.isAlive(), is(true));
+
+        // And again, setting the flag the other way.
+        mCountDown = new CountDownLatch(1);
+        DeviceConfig.setProperty(DeviceConfig.ActivityManager.NAMESPACE,
+                KEY_USE_COMPACTION, "false", false);
+        assertThat(mCountDown.await(5, TimeUnit.SECONDS), is(true));
+        assertThat(mCompactorUnderTest.useCompaction(), is(false));
+    }
+
+    @Test
+    public void useCompaction_listensToDeviceConfigChangesBadValues() throws InterruptedException {
+        assertThat(mCompactorUnderTest.useCompaction(),
+                is(mCompactorUnderTest.DEFAULT_USE_COMPACTION));
+        mCompactorUnderTest.init();
+
+        // When we push an invalid flag value...
+        mCountDown = new CountDownLatch(1);
+        DeviceConfig.setProperty(DeviceConfig.ActivityManager.NAMESPACE,
+                KEY_USE_COMPACTION, "foobar", false);
+        assertThat(mCountDown.await(5, TimeUnit.SECONDS), is(true));
+
+        // Then we set the default.
+        assertThat(mCompactorUnderTest.useCompaction(), is(AppCompactor.DEFAULT_USE_COMPACTION));
+    }
+
+    @Test
+    public void compactAction_listensToDeviceConfigChanges() throws InterruptedException {
+        mCompactorUnderTest.init();
+
+        // When we override new values for the compaction action with reasonable values...
+
+        // There are three possible values for compactAction[Some|Full].
+        for (int i = 1; i < 4; i++) {
+            mCountDown = new CountDownLatch(2);
+            int expectedSome = (mCompactorUnderTest.DEFAULT_COMPACT_ACTION_1 + i) % 3 + 1;
+            DeviceConfig.setProperty(DeviceConfig.ActivityManager.NAMESPACE,
+                    KEY_COMPACT_ACTION_1, Integer.toString(expectedSome), false);
+            int expectedFull = (mCompactorUnderTest.DEFAULT_COMPACT_ACTION_2 + i) % 3 + 1;
+            DeviceConfig.setProperty(DeviceConfig.ActivityManager.NAMESPACE,
+                    KEY_COMPACT_ACTION_2, Integer.toString(expectedFull), false);
+            assertThat(mCountDown.await(5, TimeUnit.SECONDS), is(true));
+
+            // Then the updates are reflected in the flags.
+            assertThat(mCompactorUnderTest.mCompactActionSome,
+                    is(compactActionIntToString(expectedSome)));
+            assertThat(mCompactorUnderTest.mCompactActionFull,
+                    is(compactActionIntToString(expectedFull)));
+        }
+    }
+
+    @Test
+    public void compactAction_listensToDeviceConfigChangesBadValues() throws InterruptedException {
+        mCompactorUnderTest.init();
+
+        // When we override new values for the compaction action with bad values ...
+        mCountDown = new CountDownLatch(2);
+        DeviceConfig.setProperty(DeviceConfig.ActivityManager.NAMESPACE,
+                KEY_COMPACT_ACTION_1, "foo", false);
+        DeviceConfig.setProperty(DeviceConfig.ActivityManager.NAMESPACE,
+                KEY_COMPACT_ACTION_2, "foo", false);
+        assertThat(mCountDown.await(5, TimeUnit.SECONDS), is(true));
+
+        // Then the default values are reflected in the flag
+        assertThat(mCompactorUnderTest.mCompactActionSome,
+                is(compactActionIntToString(AppCompactor.DEFAULT_COMPACT_ACTION_1)));
+        assertThat(mCompactorUnderTest.mCompactActionFull,
+                is(compactActionIntToString(AppCompactor.DEFAULT_COMPACT_ACTION_2)));
+
+        mCountDown = new CountDownLatch(2);
+        DeviceConfig.setProperty(DeviceConfig.ActivityManager.NAMESPACE,
+                KEY_COMPACT_ACTION_1, "", false);
+        DeviceConfig.setProperty(DeviceConfig.ActivityManager.NAMESPACE,
+                KEY_COMPACT_ACTION_2, "", false);
+        assertThat(mCountDown.await(5, TimeUnit.SECONDS), is(true));
+
+        assertThat(mCompactorUnderTest.mCompactActionSome,
+                is(compactActionIntToString(AppCompactor.DEFAULT_COMPACT_ACTION_1)));
+        assertThat(mCompactorUnderTest.mCompactActionFull,
+                is(compactActionIntToString(AppCompactor.DEFAULT_COMPACT_ACTION_2)));
+    }
+
+    @Test
+    public void compactThrottle_listensToDeviceConfigChanges() throws InterruptedException {
+        mCompactorUnderTest.init();
+
+        // When we override new reasonable throttle values after init...
+        mCountDown = new CountDownLatch(4);
+        DeviceConfig.setProperty(DeviceConfig.ActivityManager.NAMESPACE,
+                KEY_COMPACT_THROTTLE_1,
+                Long.toString(AppCompactor.DEFAULT_COMPACT_THROTTLE_1 + 1), false);
+        DeviceConfig.setProperty(DeviceConfig.ActivityManager.NAMESPACE,
+                KEY_COMPACT_THROTTLE_2,
+                Long.toString(AppCompactor.DEFAULT_COMPACT_THROTTLE_2 + 1), false);
+        DeviceConfig.setProperty(DeviceConfig.ActivityManager.NAMESPACE,
+                KEY_COMPACT_THROTTLE_3,
+                Long.toString(AppCompactor.DEFAULT_COMPACT_THROTTLE_3 + 1), false);
+        DeviceConfig.setProperty(DeviceConfig.ActivityManager.NAMESPACE,
+                KEY_COMPACT_THROTTLE_4,
+                Long.toString(AppCompactor.DEFAULT_COMPACT_THROTTLE_4 + 1), false);
+        assertThat(mCountDown.await(5, TimeUnit.SECONDS), is(true));
+
+        // Then those flags values are reflected in the compactor.
+        assertThat(mCompactorUnderTest.mCompactThrottleSomeSome,
+                is(AppCompactor.DEFAULT_COMPACT_THROTTLE_1 + 1));
+        assertThat(mCompactorUnderTest.mCompactThrottleSomeFull,
+                is(AppCompactor.DEFAULT_COMPACT_THROTTLE_2 + 1));
+        assertThat(mCompactorUnderTest.mCompactThrottleFullSome,
+                is(AppCompactor.DEFAULT_COMPACT_THROTTLE_3 + 1));
+        assertThat(mCompactorUnderTest.mCompactThrottleFullFull,
+                is(AppCompactor.DEFAULT_COMPACT_THROTTLE_4 + 1));
+    }
+
+    @Test
+    public void compactThrottle_listensToDeviceConfigChangesBadValues()
+            throws IOException, InterruptedException {
+        mCompactorUnderTest.init();
+
+        // When one of the throttles is overridden with a bad value...
+        mCountDown = new CountDownLatch(1);
+        DeviceConfig.setProperty(DeviceConfig.ActivityManager.NAMESPACE,
+                KEY_COMPACT_THROTTLE_1, "foo", false);
+        // Then all the throttles have the defaults set.
+        assertThat(mCountDown.await(5, TimeUnit.SECONDS), is(true));
+        assertThat(mCompactorUnderTest.mCompactThrottleSomeSome,
+                is(AppCompactor.DEFAULT_COMPACT_THROTTLE_1));
+        assertThat(mCompactorUnderTest.mCompactThrottleSomeFull,
+                is(AppCompactor.DEFAULT_COMPACT_THROTTLE_2));
+        assertThat(mCompactorUnderTest.mCompactThrottleFullSome,
+                is(AppCompactor.DEFAULT_COMPACT_THROTTLE_3));
+        assertThat(mCompactorUnderTest.mCompactThrottleFullFull,
+                is(AppCompactor.DEFAULT_COMPACT_THROTTLE_4));
+        clearDeviceConfig();
+
+        // Repeat for each of the throttle keys.
+        mCountDown = new CountDownLatch(1);
+        DeviceConfig.setProperty(DeviceConfig.ActivityManager.NAMESPACE,
+                KEY_COMPACT_THROTTLE_2, "foo", false);
+        assertThat(mCountDown.await(5, TimeUnit.SECONDS), is(true));
+        assertThat(mCompactorUnderTest.mCompactThrottleSomeSome,
+                is(AppCompactor.DEFAULT_COMPACT_THROTTLE_1));
+        assertThat(mCompactorUnderTest.mCompactThrottleSomeFull,
+                is(AppCompactor.DEFAULT_COMPACT_THROTTLE_2));
+        assertThat(mCompactorUnderTest.mCompactThrottleFullSome,
+                is(AppCompactor.DEFAULT_COMPACT_THROTTLE_3));
+        assertThat(mCompactorUnderTest.mCompactThrottleFullFull,
+                is(AppCompactor.DEFAULT_COMPACT_THROTTLE_4));
+        clearDeviceConfig();
+
+        mCountDown = new CountDownLatch(1);
+        DeviceConfig.setProperty(DeviceConfig.ActivityManager.NAMESPACE,
+                KEY_COMPACT_THROTTLE_3, "foo", false);
+        assertThat(mCountDown.await(5, TimeUnit.SECONDS), is(true));
+        assertThat(mCompactorUnderTest.mCompactThrottleSomeSome,
+                is(AppCompactor.DEFAULT_COMPACT_THROTTLE_1));
+        assertThat(mCompactorUnderTest.mCompactThrottleSomeFull,
+                is(AppCompactor.DEFAULT_COMPACT_THROTTLE_2));
+        assertThat(mCompactorUnderTest.mCompactThrottleFullSome,
+                is(AppCompactor.DEFAULT_COMPACT_THROTTLE_3));
+        assertThat(mCompactorUnderTest.mCompactThrottleFullFull,
+                is(AppCompactor.DEFAULT_COMPACT_THROTTLE_4));
+        clearDeviceConfig();
+
+        mCountDown = new CountDownLatch(1);
+        DeviceConfig.setProperty(DeviceConfig.ActivityManager.NAMESPACE,
+                KEY_COMPACT_THROTTLE_4, "foo", false);
+        assertThat(mCountDown.await(5, TimeUnit.SECONDS), is(true));
+        assertThat(mCompactorUnderTest.mCompactThrottleSomeSome,
+                is(AppCompactor.DEFAULT_COMPACT_THROTTLE_1));
+        assertThat(mCompactorUnderTest.mCompactThrottleSomeFull,
+                is(AppCompactor.DEFAULT_COMPACT_THROTTLE_2));
+        assertThat(mCompactorUnderTest.mCompactThrottleFullSome,
+                is(AppCompactor.DEFAULT_COMPACT_THROTTLE_3));
+        assertThat(mCompactorUnderTest.mCompactThrottleFullFull,
+                is(AppCompactor.DEFAULT_COMPACT_THROTTLE_4));
+    }
+
+    private class TestInjector extends Injector {
+        @Override
+        public AppOpsService getAppOpsService(File file, Handler handler) {
+            return mAppOpsService;
+        }
+
+        @Override
+        public Handler getUiHandler(ActivityManagerService service) {
+            return mHandler;
+        }
+    }
+}
diff --git a/services/tests/servicestests/src/com/android/server/backup/TrampolineTest.java b/services/tests/servicestests/src/com/android/server/backup/TrampolineTest.java
index ac4a5fe..a3f36b7 100644
--- a/services/tests/servicestests/src/com/android/server/backup/TrampolineTest.java
+++ b/services/tests/servicestests/src/com/android/server/backup/TrampolineTest.java
@@ -24,11 +24,15 @@
 import static junit.framework.Assert.fail;
 
 import static org.mockito.ArgumentMatchers.anyInt;
+import static org.mockito.ArgumentMatchers.anyString;
+import static org.mockito.ArgumentMatchers.eq;
+import static org.mockito.Mockito.doThrow;
 import static org.mockito.Mockito.never;
 import static org.mockito.Mockito.verify;
 import static org.mockito.Mockito.verifyNoMoreInteractions;
 import static org.mockito.Mockito.when;
 
+import android.Manifest;
 import android.annotation.UserIdInt;
 import android.app.backup.BackupManager;
 import android.app.backup.IBackupManagerMonitor;
@@ -39,21 +43,21 @@
 import android.content.Context;
 import android.content.Intent;
 import android.content.pm.PackageManager;
+import android.content.pm.UserInfo;
 import android.os.IBinder;
 import android.os.ParcelFileDescriptor;
 import android.os.Process;
 import android.os.RemoteException;
 import android.os.UserHandle;
+import android.os.UserManager;
 import android.platform.test.annotations.Presubmit;
-import android.provider.Settings;
-import android.test.mock.MockContentResolver;
 import android.util.SparseArray;
 
+import androidx.test.InstrumentationRegistry;
 import androidx.test.filters.SmallTest;
 import androidx.test.runner.AndroidJUnit4;
 
-import com.android.internal.util.test.FakeSettingsProvider;
-
+import org.junit.After;
 import org.junit.Before;
 import org.junit.Test;
 import org.junit.runner.RunWith;
@@ -99,10 +103,6 @@
     @Mock
     private Context mContextMock;
     @Mock
-    private File mSuppressFileMock;
-    @Mock
-    private File mSuppressFileParentMock;
-    @Mock
     private IBinder mAgentMock;
     @Mock
     private ParcelFileDescriptor mParcelFileDescriptorMock;
@@ -114,95 +114,53 @@
     private IBackupManagerMonitor mBackupManagerMonitorMock;
     @Mock
     private PrintWriter mPrintWriterMock;
+    @Mock
+    private UserManager mUserManagerMock;
+    @Mock
+    private UserInfo mUserInfoMock;
 
     private FileDescriptor mFileDescriptorStub = new FileDescriptor();
 
     private TrampolineTestable mTrampoline;
-    private MockContentResolver mContentResolver;
+    private File mTestDir;
+    private File mSuppressFile;
+    private File mActivatedFile;
 
     @Before
-    public void setUp() {
+    public void setUp() throws Exception {
         MockitoAnnotations.initMocks(this);
-        mUserId = NON_USER_SYSTEM;
+        mUserId = UserHandle.USER_SYSTEM;
 
         SparseArray<UserBackupManagerService> serviceUsers = new SparseArray<>();
-        serviceUsers.append(UserHandle.SYSTEM.getIdentifier(), mUserBackupManagerService);
+        serviceUsers.append(UserHandle.USER_SYSTEM, mUserBackupManagerService);
         serviceUsers.append(NON_USER_SYSTEM, mUserBackupManagerService);
         when(mBackupManagerServiceMock.getServiceUsers()).thenReturn(serviceUsers);
 
+        when(mUserManagerMock.getUserInfo(UserHandle.USER_SYSTEM)).thenReturn(mUserInfoMock);
+        when(mUserManagerMock.getUserInfo(NON_USER_SYSTEM)).thenReturn(mUserInfoMock);
+
         TrampolineTestable.sBackupManagerServiceMock = mBackupManagerServiceMock;
         TrampolineTestable.sCallingUserId = UserHandle.USER_SYSTEM;
         TrampolineTestable.sCallingUid = Process.SYSTEM_UID;
         TrampolineTestable.sBackupDisabled = false;
+        TrampolineTestable.sUserManagerMock = mUserManagerMock;
 
-        when(mSuppressFileMock.getParentFile()).thenReturn(mSuppressFileParentMock);
+        mTestDir = InstrumentationRegistry.getContext().getFilesDir();
+        mTestDir.mkdirs();
+
+        mSuppressFile = new File(mTestDir, "suppress");
+        TrampolineTestable.sSuppressFile = mSuppressFile;
+
+        mActivatedFile = new File(mTestDir, "activate-" + NON_USER_SYSTEM);
+        TrampolineTestable.sActivatedFiles.append(NON_USER_SYSTEM, mActivatedFile);
 
         mTrampoline = new TrampolineTestable(mContextMock);
-
-        mContentResolver = new MockContentResolver();
-        mContentResolver.addProvider(Settings.AUTHORITY, new FakeSettingsProvider());
-        when(mContextMock.getContentResolver()).thenReturn(mContentResolver);
     }
 
-    @Test
-    public void unlockUser_whenMultiUserSettingDisabled_callsBackupManagerServiceForSystemUser() {
-        Settings.Global.putInt(mContentResolver, Settings.Global.BACKUP_MULTI_USER_ENABLED, 0);
-        mTrampoline.initializeService();
-
-        mTrampoline.unlockUser(UserHandle.USER_SYSTEM);
-
-        verify(mBackupManagerServiceMock).startServiceForUser(UserHandle.USER_SYSTEM);
-    }
-
-    @Test
-    public void unlockUser_whenMultiUserSettingDisabled_isIgnoredForNonSystemUser() {
-        Settings.Global.putInt(mContentResolver, Settings.Global.BACKUP_MULTI_USER_ENABLED, 0);
-        mTrampoline.initializeService();
-
-        mTrampoline.unlockUser(NON_USER_SYSTEM);
-
-        verify(mBackupManagerServiceMock, never()).startServiceForUser(NON_USER_SYSTEM);
-    }
-
-    @Test
-    public void unlockUser_whenMultiUserSettingEnabled_callsBackupManagerServiceForNonSystemUser() {
-        Settings.Global.putInt(mContentResolver, Settings.Global.BACKUP_MULTI_USER_ENABLED, 1);
-        mTrampoline.initializeService();
-
-        mTrampoline.unlockUser(NON_USER_SYSTEM);
-
-        verify(mBackupManagerServiceMock).startServiceForUser(NON_USER_SYSTEM);
-    }
-
-    @Test
-    public void stopUser_whenMultiUserSettingDisabled_callsBackupManagerServiceForSystemUser() {
-        Settings.Global.putInt(mContentResolver, Settings.Global.BACKUP_MULTI_USER_ENABLED, 0);
-        mTrampoline.initializeService();
-
-        mTrampoline.stopUser(UserHandle.USER_SYSTEM);
-
-        verify(mBackupManagerServiceMock).stopServiceForUser(UserHandle.USER_SYSTEM);
-    }
-
-    @Test
-    public void stopUser_whenMultiUserSettingDisabled_isIgnoredForNonSystemUser() {
-        Settings.Global.putInt(mContentResolver, Settings.Global.BACKUP_MULTI_USER_ENABLED, 0);
-        mTrampoline.initializeService();
-
-        mTrampoline.stopUser(NON_USER_SYSTEM);
-
-        verify(mBackupManagerServiceMock, never()).stopServiceForUser(NON_USER_SYSTEM);
-    }
-
-    @Test
-    public void stopUser_whenMultiUserSettingEnabled_callsBackupManagerServiceForNonSystemUser() {
-        Settings.Global.putInt(mContentResolver, Settings.Global.BACKUP_MULTI_USER_ENABLED, 1);
-
-        mTrampoline.initializeService();
-
-        mTrampoline.stopUser(NON_USER_SYSTEM);
-
-        verify(mBackupManagerServiceMock).stopServiceForUser(NON_USER_SYSTEM);
+    @After
+    public void tearDown() throws Exception {
+        mSuppressFile.delete();
+        mActivatedFile.delete();
     }
 
     @Test
@@ -222,18 +180,6 @@
         assertFalse(trampoline.isBackupServiceActive(UserHandle.USER_SYSTEM));
     }
 
-    // Verify that BackupManagerService is not initialized if suppress file exists.
-    @Test
-    public void initializeService_suppressFileExists_nonInitialized() throws Exception {
-        TrampolineTestable trampoline = new TrampolineTestable(mContextMock);
-        trampoline.createBackupSuppressFileForUser(UserHandle.USER_SYSTEM);
-
-
-        trampoline.initializeService();
-
-        assertFalse(trampoline.isBackupServiceActive(UserHandle.USER_SYSTEM));
-    }
-
     @Test
     public void initializeService_doesNotStartServiceForUsers() {
         mTrampoline.initializeService();
@@ -247,15 +193,55 @@
     }
 
     @Test
-    public void isBackupServiceActive_forNonSysUser_whenSysUserIsDeactivated_returnsFalse() {
-        mTrampoline.setBackupServiceActive(NON_USER_SYSTEM, true);
+    public void isBackupServiceActive_forSystemUser_returnsTrueWhenActivated() throws Exception {
+        mTrampoline.initializeService();
+        mTrampoline.setBackupServiceActive(UserHandle.USER_SYSTEM, true);
+
+        assertTrue(mTrampoline.isBackupServiceActive(UserHandle.USER_SYSTEM));
+    }
+
+    @Test
+    public void isBackupServiceActive_forSystemUser_returnsFalseWhenDeactivated() throws Exception {
+        mTrampoline.initializeService();
         mTrampoline.setBackupServiceActive(UserHandle.USER_SYSTEM, false);
 
+        assertFalse(mTrampoline.isBackupServiceActive(UserHandle.USER_SYSTEM));
+    }
+
+    @Test
+    public void isBackupServiceActive_forNonSystemUser_returnsFalseWhenSystemUserDeactivated()
+            throws Exception {
+        mTrampoline.initializeService();
+        mTrampoline.setBackupServiceActive(UserHandle.USER_SYSTEM, false);
+        mTrampoline.setBackupServiceActive(NON_USER_SYSTEM, true);
+
         assertFalse(mTrampoline.isBackupServiceActive(NON_USER_SYSTEM));
     }
 
     @Test
-    public void setBackupServiceActive_callerSystemUid_serviceCreated() {
+    public void isBackupServiceActive_forNonSystemUser_returnsFalseWhenNonSystemUserDeactivated()
+            throws Exception {
+        mTrampoline.initializeService();
+        mTrampoline.setBackupServiceActive(UserHandle.USER_SYSTEM, true);
+        // Don't activate non-system user.
+
+        assertFalse(mTrampoline.isBackupServiceActive(NON_USER_SYSTEM));
+    }
+
+    @Test
+    public void
+            isBackupServiceActive_forNonSystemUser_returnsTrueWhenSystemAndNonSystemUserActivated()
+                throws Exception {
+        mTrampoline.initializeService();
+        mTrampoline.setBackupServiceActive(UserHandle.USER_SYSTEM, true);
+        mTrampoline.setBackupServiceActive(NON_USER_SYSTEM, true);
+
+        assertTrue(mTrampoline.isBackupServiceActive(NON_USER_SYSTEM));
+    }
+
+    @Test
+    public void setBackupServiceActive_forSystemUserAndCallerSystemUid_serviceCreated() {
+        mTrampoline.initializeService();
         TrampolineTestable.sCallingUid = Process.SYSTEM_UID;
 
         mTrampoline.setBackupServiceActive(UserHandle.USER_SYSTEM, true);
@@ -264,7 +250,8 @@
     }
 
     @Test
-    public void setBackupServiceActive_callerRootUid_serviceCreated() {
+    public void setBackupServiceActive_forSystemUserAndCallerRootUid_serviceCreated() {
+        mTrampoline.initializeService();
         TrampolineTestable.sCallingUid = Process.ROOT_UID;
 
         mTrampoline.setBackupServiceActive(UserHandle.USER_SYSTEM, true);
@@ -273,7 +260,8 @@
     }
 
     @Test
-    public void setBackupServiceActive_callerNonRootNonSystem_securityExceptionThrown() {
+    public void setBackupServiceActive_forSystemUserAndCallerNonRootNonSystem_throws() {
+        mTrampoline.initializeService();
         TrampolineTestable.sCallingUid = Process.FIRST_APPLICATION_UID;
 
         try {
@@ -281,14 +269,77 @@
             fail();
         } catch (SecurityException expected) {
         }
+    }
 
-        assertFalse(mTrampoline.isBackupServiceActive(UserHandle.USER_SYSTEM));
+    @Test
+    public void setBackupServiceActive_forManagedProfileAndCallerSystemUid_serviceCreated() {
+        when(mUserInfoMock.isManagedProfile()).thenReturn(true);
+        mTrampoline.initializeService();
+        TrampolineTestable.sCallingUid = Process.SYSTEM_UID;
+
+        mTrampoline.setBackupServiceActive(NON_USER_SYSTEM, true);
+
+        assertTrue(mTrampoline.isBackupServiceActive(NON_USER_SYSTEM));
+    }
+
+    @Test
+    public void setBackupServiceActive_forManagedProfileAndCallerRootUid_serviceCreated() {
+        when(mUserInfoMock.isManagedProfile()).thenReturn(true);
+        mTrampoline.initializeService();
+        TrampolineTestable.sCallingUid = Process.ROOT_UID;
+
+        mTrampoline.setBackupServiceActive(NON_USER_SYSTEM, true);
+
+        assertTrue(mTrampoline.isBackupServiceActive(NON_USER_SYSTEM));
+    }
+
+    @Test
+    public void setBackupServiceActive_forManagedProfileAndCallerNonRootNonSystem_throws() {
+        when(mUserInfoMock.isManagedProfile()).thenReturn(true);
+        mTrampoline.initializeService();
+        TrampolineTestable.sCallingUid = Process.FIRST_APPLICATION_UID;
+
+        try {
+            mTrampoline.setBackupServiceActive(NON_USER_SYSTEM, true);
+            fail();
+        } catch (SecurityException expected) {
+        }
+    }
+
+    @Test
+    public void setBackupServiceActive_forNonSystemUserAndCallerWithoutBackupPermission_throws() {
+        doThrow(new SecurityException())
+                .when(mContextMock)
+                .enforceCallingOrSelfPermission(eq(Manifest.permission.BACKUP), anyString());
+        mTrampoline.initializeService();
+
+        try {
+            mTrampoline.setBackupServiceActive(NON_USER_SYSTEM, true);
+            fail();
+        } catch (SecurityException expected) {
+        }
+    }
+
+    @Test
+    public void setBackupServiceActive_forNonSystemUserAndCallerWithoutUserPermission_throws() {
+        doThrow(new SecurityException())
+                .when(mContextMock)
+                .enforceCallingOrSelfPermission(
+                        eq(Manifest.permission.INTERACT_ACROSS_USERS_FULL), anyString());
+        mTrampoline.initializeService();
+
+        try {
+            mTrampoline.setBackupServiceActive(NON_USER_SYSTEM, true);
+            fail();
+        } catch (SecurityException expected) {
+        }
     }
 
     @Test
     public void setBackupServiceActive_backupDisabled_ignored() {
         TrampolineTestable.sBackupDisabled = true;
         TrampolineTestable trampoline = new TrampolineTestable(mContextMock);
+        trampoline.initializeService();
 
         trampoline.setBackupServiceActive(UserHandle.USER_SYSTEM, true);
 
@@ -296,14 +347,8 @@
     }
 
     @Test
-    public void setBackupServiceActive_nonUserSystem_ignored() {
-        mTrampoline.setBackupServiceActive(NON_USER_SYSTEM, true);
-
-        assertFalse(mTrampoline.isBackupServiceActive(NON_USER_SYSTEM));
-    }
-
-    @Test
     public void setBackupServiceActive_alreadyActive_ignored() {
+        mTrampoline.initializeService();
         mTrampoline.setBackupServiceActive(UserHandle.USER_SYSTEM, true);
         assertTrue(mTrampoline.isBackupServiceActive(UserHandle.USER_SYSTEM));
         assertEquals(1, mTrampoline.getCreateServiceCallsCount());
@@ -315,6 +360,7 @@
 
     @Test
     public void setBackupServiceActive_makeNonActive_alreadyNonActive_ignored() {
+        mTrampoline.initializeService();
         mTrampoline.setBackupServiceActive(UserHandle.USER_SYSTEM, false);
         mTrampoline.setBackupServiceActive(UserHandle.USER_SYSTEM, false);
 
@@ -323,6 +369,7 @@
 
     @Test
     public void setBackupServiceActive_makeActive_serviceCreatedAndSuppressFileDeleted() {
+        mTrampoline.initializeService();
         mTrampoline.setBackupServiceActive(UserHandle.USER_SYSTEM, true);
 
         assertTrue(mTrampoline.isBackupServiceActive(UserHandle.USER_SYSTEM));
@@ -349,6 +396,21 @@
     }
 
     @Test
+    public void setBackupServiceActive_forOneNonSystemUser_doesNotActivateForAllNonSystemUsers() {
+        mTrampoline.initializeService();
+        int otherUser = NON_USER_SYSTEM + 1;
+        File activateFile = new File(mTestDir, "activate-" + otherUser);
+        TrampolineTestable.sActivatedFiles.append(otherUser, activateFile);
+        mTrampoline.setBackupServiceActive(UserHandle.USER_SYSTEM, true);
+
+        mTrampoline.setBackupServiceActive(NON_USER_SYSTEM, true);
+
+        assertTrue(mTrampoline.isBackupServiceActive(NON_USER_SYSTEM));
+        assertFalse(mTrampoline.isBackupServiceActive(otherUser));
+        activateFile.delete();
+    }
+
+    @Test
     public void dataChanged_calledBeforeInitialize_ignored() throws Exception {
         mTrampoline.dataChanged(PACKAGE_NAME);
         verifyNoMoreInteractions(mBackupManagerServiceMock);
@@ -1123,7 +1185,7 @@
     @Test
     public void requestBackup_forwardedToCallingUserId() throws Exception {
         TrampolineTestable.sCallingUserId = mUserId;
-        when(mBackupManagerServiceMock.requestBackup(NON_USER_SYSTEM, PACKAGE_NAMES,
+        when(mBackupManagerServiceMock.requestBackup(mUserId, PACKAGE_NAMES,
                 mBackupObserverMock, mBackupManagerMonitorMock, 123)).thenReturn(456);
         mTrampoline.initializeService();
 
@@ -1227,50 +1289,33 @@
         static int sCallingUserId = -1;
         static int sCallingUid = -1;
         static BackupManagerService sBackupManagerServiceMock = null;
+        static File sSuppressFile = null;
+        static SparseArray<File> sActivatedFiles = new SparseArray<>();
+        static UserManager sUserManagerMock = null;
         private int mCreateServiceCallsCount = 0;
-        private SparseArray<FakeFile> mSuppressFiles = new SparseArray<>();
-
-        private static class FakeFile extends File {
-            private boolean mExists;
-
-            FakeFile(String pathname) {
-                super(pathname);
-            }
-
-            @Override
-            public boolean exists() {
-                return mExists;
-            }
-
-            @Override
-            public boolean delete() {
-                mExists = false;
-                return true;
-            }
-
-            @Override
-            public boolean createNewFile() throws IOException {
-                mExists = true;
-                return true;
-            }
-        }
 
         TrampolineTestable(Context context) {
             super(context);
         }
 
         @Override
+        protected UserManager getUserManager() {
+            return sUserManagerMock;
+        }
+
+        @Override
         public boolean isBackupDisabled() {
             return sBackupDisabled;
         }
 
         @Override
-        public File getSuppressFileForUser(int userId) {
-            if (mSuppressFiles.get(userId) == null) {
-                FakeFile file = new FakeFile(Integer.toString(userId));
-                mSuppressFiles.append(userId, file);
-            }
-            return mSuppressFiles.get(userId);
+        protected File getSuppressFileForSystemUser() {
+            return sSuppressFile;
+        }
+
+        @Override
+        protected File getActivatedFileForNonSystemUser(int userId) {
+            return sActivatedFiles.get(userId);
         }
 
         protected int binderGetCallingUserId() {
@@ -1289,11 +1334,6 @@
         }
 
         @Override
-        protected void createBackupSuppressFileForUser(int userId) throws IOException {
-            getSuppressFileForUser(userId).createNewFile();
-        }
-
-        @Override
         protected void postToHandler(Runnable runnable) {
             runnable.run();
         }
diff --git a/services/tests/servicestests/src/com/android/server/devicepolicy/DevicePolicyManagerTest.java b/services/tests/servicestests/src/com/android/server/devicepolicy/DevicePolicyManagerTest.java
index 0813e6fa..535198b 100644
--- a/services/tests/servicestests/src/com/android/server/devicepolicy/DevicePolicyManagerTest.java
+++ b/services/tests/servicestests/src/com/android/server/devicepolicy/DevicePolicyManagerTest.java
@@ -5239,6 +5239,45 @@
         assertEquals(PASSWORD_COMPLEXITY_HIGH, dpm.getPasswordComplexity());
     }
 
+    public void testCrossProfileCalendarPackages_initiallyEmpty() {
+        setAsProfileOwner(admin1);
+        final Set<String> packages = dpm.getCrossProfileCalendarPackages(admin1);
+        assertCrossProfileCalendarPackagesEqual(packages, Collections.emptySet());
+    }
+
+    public void testCrossProfileCalendarPackages_reopenDpms() {
+        setAsProfileOwner(admin1);
+        dpm.setCrossProfileCalendarPackages(admin1, null);
+        Set<String> packages = dpm.getCrossProfileCalendarPackages(admin1);
+        assertTrue(packages == null);
+        initializeDpms();
+        packages = dpm.getCrossProfileCalendarPackages(admin1);
+        assertTrue(packages == null);
+
+        dpm.setCrossProfileCalendarPackages(admin1, Collections.emptySet());
+        packages = dpm.getCrossProfileCalendarPackages(admin1);
+        assertCrossProfileCalendarPackagesEqual(packages, Collections.emptySet());
+        initializeDpms();
+        packages = dpm.getCrossProfileCalendarPackages(admin1);
+        assertCrossProfileCalendarPackagesEqual(packages, Collections.emptySet());
+
+        final String dummyPackageName = "test";
+        final Set<String> testPackages = new ArraySet<String>(Arrays.asList(dummyPackageName));
+        dpm.setCrossProfileCalendarPackages(admin1, testPackages);
+        packages = dpm.getCrossProfileCalendarPackages(admin1);
+        assertCrossProfileCalendarPackagesEqual(packages, testPackages);
+        initializeDpms();
+        packages = dpm.getCrossProfileCalendarPackages(admin1);
+        assertCrossProfileCalendarPackagesEqual(packages, testPackages);
+    }
+
+    private void assertCrossProfileCalendarPackagesEqual(Set<String> expected, Set<String> actual) {
+        assertTrue(expected != null);
+        assertTrue(actual != null);
+        assertTrue(expected.containsAll(actual));
+        assertTrue(actual.containsAll(expected));
+    }
+
     private void configureProfileOwnerForDeviceIdAccess(ComponentName who, int userId) {
         final long ident = mServiceContext.binder.clearCallingIdentity();
         mServiceContext.binder.callingUid =
diff --git a/services/tests/servicestests/src/com/android/server/display/ColorDisplayServiceTest.java b/services/tests/servicestests/src/com/android/server/display/ColorDisplayServiceTest.java
index e0ecd3e..0b01657 100644
--- a/services/tests/servicestests/src/com/android/server/display/ColorDisplayServiceTest.java
+++ b/services/tests/servicestests/src/com/android/server/display/ColorDisplayServiceTest.java
@@ -25,6 +25,7 @@
 import android.app.AlarmManager;
 import android.content.Context;
 import android.content.ContextWrapper;
+import android.hardware.display.ColorDisplayManager;
 import android.os.Handler;
 import android.os.UserHandle;
 import android.provider.Settings;
@@ -45,7 +46,9 @@
 import com.android.server.twilight.TwilightState;
 
 import org.junit.After;
+import org.junit.AfterClass;
 import org.junit.Before;
+import org.junit.BeforeClass;
 import org.junit.Test;
 import org.junit.runner.RunWith;
 import org.mockito.Mockito;
@@ -67,16 +70,23 @@
 
     private MockTwilightManager mTwilightManager;
 
-    private ColorDisplayController mColorDisplayController;
     private ColorDisplayService mColorDisplayService;
+    private ColorDisplayController mColorDisplayController;
+    private ColorDisplayService.BinderService mBinderService;
+
+    @BeforeClass
+    public static void setDtm() {
+        final DisplayTransformManager dtm = Mockito.mock(DisplayTransformManager.class);
+        LocalServices.addService(DisplayTransformManager.class, dtm);
+    }
 
     @Before
     public void setUp() {
         mContext = Mockito.spy(new ContextWrapper(InstrumentationRegistry.getTargetContext()));
-        mUserId = ActivityManager.getCurrentUser();
-
         doReturn(mContext).when(mContext).getApplicationContext();
 
+        mUserId = ActivityManager.getCurrentUser();
+
         final MockContentResolver cr = new MockContentResolver(mContext);
         cr.addProvider(Settings.AUTHORITY, new FakeSettingsProvider());
         doReturn(cr).when(mContext).getContentResolver();
@@ -84,23 +94,19 @@
         final AlarmManager am = Mockito.mock(AlarmManager.class);
         doReturn(am).when(mContext).getSystemService(Context.ALARM_SERVICE);
 
-        final DisplayTransformManager dtm = Mockito.mock(DisplayTransformManager.class);
-        LocalServices.addService(DisplayTransformManager.class, dtm);
-
         mTwilightManager = new MockTwilightManager();
         LocalServices.addService(TwilightManager.class, mTwilightManager);
 
         mColorDisplayController = new ColorDisplayController(mContext, mUserId);
         mColorDisplayService = new ColorDisplayService(mContext);
+        mBinderService = mColorDisplayService.new BinderService();
     }
 
     @After
     public void tearDown() {
-        LocalServices.removeServiceForTest(DisplayTransformManager.class);
         LocalServices.removeServiceForTest(TwilightManager.class);
 
         mColorDisplayService = null;
-        mColorDisplayController = null;
 
         mTwilightManager = null;
 
@@ -108,6 +114,11 @@
         mContext = null;
     }
 
+    @AfterClass
+    public static void removeDtm() {
+        LocalServices.removeServiceForTest(DisplayTransformManager.class);
+    }
+
     @Test
     public void customSchedule_whenStartedAfterNight_ifOffAfterNight_turnsOff() {
         setAutoModeCustom(-120 /* startTimeOffset */, -60 /* endTimeOffset */);
@@ -907,15 +918,14 @@
         }
 
         setAccessibilityColorInversion(true);
-        setColorMode(ColorDisplayController.COLOR_MODE_NATURAL);
+        setColorMode(ColorDisplayManager.COLOR_MODE_NATURAL);
 
         startService();
-        assertAccessibilityTransformActivated(true /* activated */ );
-        assertUserColorMode(ColorDisplayController.COLOR_MODE_NATURAL);
-        if (isColorModeValid(ColorDisplayController.COLOR_MODE_SATURATED)) {
-            assertActiveColorMode(ColorDisplayController.COLOR_MODE_SATURATED);
-        } else if (isColorModeValid(ColorDisplayController.COLOR_MODE_AUTOMATIC)) {
-            assertActiveColorMode(ColorDisplayController.COLOR_MODE_AUTOMATIC);
+        assertUserColorMode(ColorDisplayManager.COLOR_MODE_NATURAL);
+        if (isColorModeValid(ColorDisplayManager.COLOR_MODE_SATURATED)) {
+            assertActiveColorMode(ColorDisplayManager.COLOR_MODE_SATURATED);
+        } else if (isColorModeValid(ColorDisplayManager.COLOR_MODE_AUTOMATIC)) {
+            assertActiveColorMode(ColorDisplayManager.COLOR_MODE_AUTOMATIC);
         }
     }
 
@@ -926,15 +936,14 @@
         }
 
         setAccessibilityColorCorrection(true);
-        setColorMode(ColorDisplayController.COLOR_MODE_NATURAL);
+        setColorMode(ColorDisplayManager.COLOR_MODE_NATURAL);
 
         startService();
-        assertAccessibilityTransformActivated(true /* activated */ );
-        assertUserColorMode(ColorDisplayController.COLOR_MODE_NATURAL);
-        if (isColorModeValid(ColorDisplayController.COLOR_MODE_SATURATED)) {
-            assertActiveColorMode(ColorDisplayController.COLOR_MODE_SATURATED);
-        } else if (isColorModeValid(ColorDisplayController.COLOR_MODE_AUTOMATIC)) {
-            assertActiveColorMode(ColorDisplayController.COLOR_MODE_AUTOMATIC);
+        assertUserColorMode(ColorDisplayManager.COLOR_MODE_NATURAL);
+        if (isColorModeValid(ColorDisplayManager.COLOR_MODE_SATURATED)) {
+            assertActiveColorMode(ColorDisplayManager.COLOR_MODE_SATURATED);
+        } else if (isColorModeValid(ColorDisplayManager.COLOR_MODE_AUTOMATIC)) {
+            assertActiveColorMode(ColorDisplayManager.COLOR_MODE_AUTOMATIC);
         }
     }
 
@@ -946,15 +955,14 @@
 
         setAccessibilityColorCorrection(true);
         setAccessibilityColorInversion(true);
-        setColorMode(ColorDisplayController.COLOR_MODE_NATURAL);
+        setColorMode(ColorDisplayManager.COLOR_MODE_NATURAL);
 
         startService();
-        assertAccessibilityTransformActivated(true /* activated */ );
-        assertUserColorMode(ColorDisplayController.COLOR_MODE_NATURAL);
-        if (isColorModeValid(ColorDisplayController.COLOR_MODE_SATURATED)) {
-            assertActiveColorMode(ColorDisplayController.COLOR_MODE_SATURATED);
-        } else if (isColorModeValid(ColorDisplayController.COLOR_MODE_AUTOMATIC)) {
-            assertActiveColorMode(ColorDisplayController.COLOR_MODE_AUTOMATIC);
+        assertUserColorMode(ColorDisplayManager.COLOR_MODE_NATURAL);
+        if (isColorModeValid(ColorDisplayManager.COLOR_MODE_SATURATED)) {
+            assertActiveColorMode(ColorDisplayManager.COLOR_MODE_SATURATED);
+        } else if (isColorModeValid(ColorDisplayManager.COLOR_MODE_AUTOMATIC)) {
+            assertActiveColorMode(ColorDisplayManager.COLOR_MODE_AUTOMATIC);
         }
     }
 
@@ -966,12 +974,11 @@
 
         setAccessibilityColorCorrection(false);
         setAccessibilityColorInversion(false);
-        setColorMode(ColorDisplayController.COLOR_MODE_NATURAL);
+        setColorMode(ColorDisplayManager.COLOR_MODE_NATURAL);
 
         startService();
-        assertAccessibilityTransformActivated(false /* activated */ );
-        assertUserColorMode(ColorDisplayController.COLOR_MODE_NATURAL);
-        assertActiveColorMode(ColorDisplayController.COLOR_MODE_NATURAL);
+        assertUserColorMode(ColorDisplayManager.COLOR_MODE_NATURAL);
+        assertActiveColorMode(ColorDisplayManager.COLOR_MODE_NATURAL);
     }
 
     /**
@@ -981,7 +988,7 @@
      * @param endTimeOffset the offset relative to now to deactivate Night display (in minutes)
      */
     private void setAutoModeCustom(int startTimeOffset, int endTimeOffset) {
-        mColorDisplayController.setAutoMode(ColorDisplayController.AUTO_MODE_CUSTOM);
+        mColorDisplayController.setAutoMode(ColorDisplayManager.AUTO_MODE_CUSTOM_TIME);
         mColorDisplayController.setCustomStartTime(getLocalTimeRelativeToNow(startTimeOffset));
         mColorDisplayController.setCustomEndTime(getLocalTimeRelativeToNow(endTimeOffset));
     }
@@ -993,7 +1000,7 @@
      * @param sunriseOffset the offset relative to now for sunrise (in minutes)
      */
     private void setAutoModeTwilight(int sunsetOffset, int sunriseOffset) {
-        mColorDisplayController.setAutoMode(ColorDisplayController.AUTO_MODE_TWILIGHT);
+        mColorDisplayController.setAutoMode(ColorDisplayManager.AUTO_MODE_TWILIGHT);
         mTwilightManager.setTwilightState(
                 getTwilightStateRelativeToNow(sunsetOffset, sunriseOffset));
     }
@@ -1006,7 +1013,7 @@
      * activated (in minutes)
      */
     private void setActivated(boolean activated, int lastActivatedTimeOffset) {
-        mColorDisplayController.setActivated(activated);
+        mBinderService.setNightDisplayActivated(activated);
         Secure.putStringForUser(mContext.getContentResolver(),
                 Secure.NIGHT_DISPLAY_LAST_ACTIVATED_TIME,
                 LocalDateTime.now().plusMinutes(lastActivatedTimeOffset).toString(),
@@ -1036,10 +1043,10 @@
     /**
      * Configures color mode via ColorDisplayController.
      *
-     * @param mode the color mode to set
+     * @param colorMode the color mode to set
      */
-    private void setColorMode(int mode) {
-        mColorDisplayController.setColorMode(mode);
+    private void setColorMode(int colorMode) {
+        mColorDisplayController.setColorMode(colorMode);
     }
 
     /**
@@ -1081,23 +1088,12 @@
      * @param activated the expected activated state of Night display
      */
     private void assertActivated(boolean activated) {
-        assertWithMessage("Invalid Night display activated state")
-                .that(mColorDisplayController.isActivated())
+        assertWithMessage("Incorrect Night display activated state")
+                .that(mBinderService.isNightDisplayActivated())
                 .isEqualTo(activated);
     }
 
     /**
-     * Convenience method for asserting that Accessibility color transform is detected.
-     *
-     * @param state {@code true} if any Accessibility transform should be activated
-     */
-    private void assertAccessibilityTransformActivated(boolean state) {
-        assertWithMessage("Unexpected Accessibility color transform state")
-                .that(mColorDisplayController.getAccessibilityTransformActivated())
-                .isEqualTo(state);
-    }
-
-    /**
      * Convenience method for asserting that the active color mode matches expectation.
      *
      * @param mode the expected active color mode.
diff --git a/services/tests/servicestests/src/com/android/server/job/MaxJobCountsTest.java b/services/tests/servicestests/src/com/android/server/job/MaxJobCountsTest.java
index 01199a3..0219f22 100644
--- a/services/tests/servicestests/src/com/android/server/job/MaxJobCountsTest.java
+++ b/services/tests/servicestests/src/com/android/server/job/MaxJobCountsTest.java
@@ -17,15 +17,15 @@
 
 import android.util.KeyValueListParser;
 
+import androidx.test.filters.SmallTest;
+import androidx.test.runner.AndroidJUnit4;
+
 import com.android.server.job.JobSchedulerService.MaxJobCounts;
 
 import org.junit.Assert;
 import org.junit.Test;
 import org.junit.runner.RunWith;
 
-import androidx.test.filters.SmallTest;
-import androidx.test.runner.AndroidJUnit4;
-
 @RunWith(AndroidJUnit4.class)
 @SmallTest
 public class MaxJobCountsTest {
@@ -43,13 +43,14 @@
 
         counts.parse(parser);
 
-        Assert.assertEquals(expectedTotal, counts.getTotalMax());
+        Assert.assertEquals(expectedTotal, counts.getMaxTotal());
         Assert.assertEquals(expectedMaxBg, counts.getMaxBg());
         Assert.assertEquals(expectedMinBg, counts.getMinBg());
     }
 
     @Test
     public void test() {
+        // Tests with various combinations.
         check("", /*default*/ 5, 1, 0, /*expected*/ 5, 1, 0);
         check("", /*default*/ 5, 0, 0, /*expected*/ 5, 1, 0);
         check("", /*default*/ 0, 0, 0, /*expected*/ 1, 1, 0);
@@ -58,7 +59,11 @@
         check("", /*default*/ 6, 5, 6, /*expected*/ 6, 5, 5);
         check("", /*default*/ 4, 5, 6, /*expected*/ 4, 4, 3);
         check("", /*default*/ 5, 1, 1, /*expected*/ 5, 1, 1);
+        check("", /*default*/ 15, 15, 15, /*expected*/ 15, 15, 14);
+        check("", /*default*/ 16, 16, 16, /*expected*/ 16, 16, 15);
+        check("", /*default*/ 20, 20, 20, /*expected*/ 16, 16, 15);
 
+        // Test for overriding with a setting string.
         check("total=5,maxbg=4,minbg=3", /*default*/ 9, 9, 9, /*expected*/ 5, 4, 3);
         check("total=5", /*default*/ 9, 9, 9, /*expected*/ 5, 5, 4);
         check("maxbg=4", /*default*/ 9, 9, 9, /*expected*/ 9, 4, 4);
diff --git a/services/tests/servicestests/src/com/android/server/pm/BaseShortcutManagerTest.java b/services/tests/servicestests/src/com/android/server/pm/BaseShortcutManagerTest.java
index bc1f798..6845f15 100644
--- a/services/tests/servicestests/src/com/android/server/pm/BaseShortcutManagerTest.java
+++ b/services/tests/servicestests/src/com/android/server/pm/BaseShortcutManagerTest.java
@@ -43,6 +43,7 @@
 import android.app.ActivityManagerInternal;
 import android.app.IUidObserver;
 import android.app.Person;
+import android.app.admin.DevicePolicyManager;
 import android.app.usage.UsageStatsManagerInternal;
 import android.content.ActivityNotFoundException;
 import android.content.BroadcastReceiver;
@@ -143,8 +144,10 @@
             switch (name) {
                 case Context.USER_SERVICE:
                     return mMockUserManager;
+                case Context.DEVICE_POLICY_SERVICE:
+                    return mMockDevicePolicyManager;
             }
-            throw new UnsupportedOperationException();
+            throw new UnsupportedOperationException("Couldn't find system service: " + name);
         }
 
         @Override
@@ -610,6 +613,7 @@
     protected PackageManager mMockPackageManager;
     protected PackageManagerInternal mMockPackageManagerInternal;
     protected UserManager mMockUserManager;
+    protected DevicePolicyManager mMockDevicePolicyManager;
     protected UserManagerInternal mMockUserManagerInternal;
     protected UsageStatsManagerInternal mMockUsageStatsManagerInternal;
     protected ActivityManagerInternal mMockActivityManagerInternal;
@@ -750,6 +754,7 @@
         mMockPackageManager = mock(PackageManager.class);
         mMockPackageManagerInternal = mock(PackageManagerInternal.class);
         mMockUserManager = mock(UserManager.class);
+        mMockDevicePolicyManager = mock(DevicePolicyManager.class);
         mMockUserManagerInternal = mock(UserManagerInternal.class);
         mMockUsageStatsManagerInternal = mock(UsageStatsManagerInternal.class);
         mMockActivityManagerInternal = mock(ActivityManagerInternal.class);
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/dex/DexManagerTests.java b/services/tests/servicestests/src/com/android/server/pm/dex/DexManagerTests.java
index 7cd8cedd..2ddc71f 100644
--- a/services/tests/servicestests/src/com/android/server/pm/dex/DexManagerTests.java
+++ b/services/tests/servicestests/src/com/android/server/pm/dex/DexManagerTests.java
@@ -55,6 +55,7 @@
 import java.io.File;
 import java.util.ArrayList;
 import java.util.Arrays;
+import java.util.Collection;
 import java.util.Collections;
 import java.util.HashMap;
 import java.util.List;
@@ -685,4 +686,68 @@
             return mPackageInfo.applicationInfo.splitSourceDirs[length - 1];
         }
     }
+
+    private boolean shouldPackageRunOob(
+            boolean isDefaultEnabled, String defaultWhitelist, String overrideEnabled,
+            String overrideWhitelist, Collection<String> packageNamesInSameProcess) {
+        return DexManager.isPackageSelectedToRunOobInternal(
+                isDefaultEnabled, defaultWhitelist, overrideEnabled, overrideWhitelist,
+                packageNamesInSameProcess);
+    }
+
+    @Test
+    public void testOobPackageSelectionSwitch() {
+        // Feature is off by default, not overriden
+        assertFalse(shouldPackageRunOob(false, "ALL", null, null, null));
+
+        // Feature is off by default, overriden
+        assertTrue(shouldPackageRunOob(false, "ALL", "true", "ALL", null));
+        assertFalse(shouldPackageRunOob(false, "ALL", "false", null, null));
+        assertFalse(shouldPackageRunOob(false, "ALL", "false", "ALL", null));
+        assertFalse(shouldPackageRunOob(false, "ALL", "false", null, null));
+
+        // Feature is on by default, not overriden
+        assertTrue(shouldPackageRunOob(true, "ALL", null, null, null));
+        assertTrue(shouldPackageRunOob(true, "ALL", null, null, null));
+        assertTrue(shouldPackageRunOob(true, "ALL", null, "ALL", null));
+
+        // Feature is on by default, overriden
+        assertTrue(shouldPackageRunOob(true, "ALL", "true", null, null));
+        assertTrue(shouldPackageRunOob(true, "ALL", "true", "ALL", null));
+        assertFalse(shouldPackageRunOob(true, "ALL", "false", null, null));
+        assertFalse(shouldPackageRunOob(true, "ALL", "false", "ALL", null));
+    }
+
+    @Test
+    public void testOobPackageSelectionWhitelist() {
+        // Various whitelist of apps to run in OOB mode.
+        final String kWhitelistApp0 = "com.priv.app0";
+        final String kWhitelistApp1 = "com.priv.app1";
+        final String kWhitelistApp2 = "com.priv.app2";
+        final String kWhitelistApp1AndApp2 = "com.priv.app1,com.priv.app2";
+
+        // Packages that shares the targeting process.
+        final Collection<String> runningPackages = Arrays.asList("com.priv.app1", "com.priv.app2");
+
+        // Feature is off, whitelist does not matter
+        assertFalse(shouldPackageRunOob(false, kWhitelistApp0, null, null, runningPackages));
+        assertFalse(shouldPackageRunOob(false, kWhitelistApp1, null, null, runningPackages));
+        assertFalse(shouldPackageRunOob(false, "", null, kWhitelistApp1, runningPackages));
+        assertFalse(shouldPackageRunOob(false, "", null, "ALL", runningPackages));
+        assertFalse(shouldPackageRunOob(false, "ALL", null, "ALL", runningPackages));
+        assertFalse(shouldPackageRunOob(false, "ALL", null, "", runningPackages));
+
+        // Feature is on, app not in default or overridden whitelist
+        assertFalse(shouldPackageRunOob(true, kWhitelistApp0, null, null, runningPackages));
+        assertFalse(shouldPackageRunOob(true, "", null, kWhitelistApp0, runningPackages));
+        assertFalse(shouldPackageRunOob(true, "ALL", null, kWhitelistApp0, runningPackages));
+
+        // Feature is on, app in default or overridden whitelist
+        assertTrue(shouldPackageRunOob(true, kWhitelistApp1, null, null, runningPackages));
+        assertTrue(shouldPackageRunOob(true, kWhitelistApp2, null, null, runningPackages));
+        assertTrue(shouldPackageRunOob(true, kWhitelistApp1AndApp2, null, null, runningPackages));
+        assertTrue(shouldPackageRunOob(true, kWhitelistApp1, null, "ALL", runningPackages));
+        assertTrue(shouldPackageRunOob(true, "", null, kWhitelistApp1, runningPackages));
+        assertTrue(shouldPackageRunOob(true, "ALL", null, kWhitelistApp1, runningPackages));
+    }
 }
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 b348aee..5d69bbd 100644
--- a/services/tests/servicestests/src/com/android/server/usage/AppTimeLimitControllerTests.java
+++ b/services/tests/servicestests/src/com/android/server/usage/AppTimeLimitControllerTests.java
@@ -18,12 +18,15 @@
 
 import static org.junit.Assert.assertEquals;
 import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertNotNull;
 import static org.junit.Assert.assertTrue;
 import static org.junit.Assert.fail;
 
 import android.app.PendingIntent;
+import android.app.usage.UsageStatsManagerInternal;
 import android.os.HandlerThread;
 import android.os.Looper;
+import android.os.UserHandle;
 
 import androidx.test.filters.LargeTest;
 import androidx.test.runner.AndroidJUnit4;
@@ -64,7 +67,7 @@
 
     private static final long TIME_30_MIN = 30 * 60_000L;
     private static final long TIME_10_MIN = 10 * 60_000L;
-    private static final long TIME_1_MIN = 10 * 60_000L;
+    private static final long TIME_1_MIN = 1 * 60_000L;
 
     private static final long MAX_OBSERVER_PER_UID = 10;
     private static final long MIN_TIME_LIMIT = 4_000L;
@@ -128,6 +131,11 @@
         }
 
         @Override
+        protected long getAppUsageLimitObserverPerUidLimit() {
+            return MAX_OBSERVER_PER_UID;
+        }
+
+        @Override
         protected long getMinTimeLimit() {
             return MIN_TIME_LIMIT;
         }
@@ -164,6 +172,16 @@
         assertTrue("Observer wasn't added", hasUsageSessionObserver(UID, OBS_ID2));
     }
 
+    /** Verify app usage limit observer is added */
+    @Test
+    public void testAppUsageLimitObserver_AddObserver() {
+        addAppUsageLimitObserver(OBS_ID1, GROUP1, TIME_30_MIN);
+        assertTrue("Observer wasn't added", hasAppUsageLimitObserver(UID, OBS_ID1));
+        addAppUsageLimitObserver(OBS_ID2, GROUP_GAME, TIME_30_MIN);
+        assertTrue("Observer wasn't added", hasAppUsageLimitObserver(UID, OBS_ID2));
+        assertTrue("Observer wasn't added", hasAppUsageLimitObserver(UID, OBS_ID1));
+    }
+
     /** Verify app usage observer is removed */
     @Test
     public void testAppUsageObserver_RemoveObserver() {
@@ -182,6 +200,15 @@
         assertFalse("Observer wasn't removed", hasUsageSessionObserver(UID, OBS_ID1));
     }
 
+    /** Verify app usage limit observer is removed */
+    @Test
+    public void testAppUsageLimitObserver_RemoveObserver() {
+        addAppUsageLimitObserver(OBS_ID1, GROUP1, TIME_30_MIN);
+        assertTrue("Observer wasn't added", hasAppUsageLimitObserver(UID, OBS_ID1));
+        mController.removeAppUsageLimitObserver(UID, OBS_ID1, USER_ID);
+        assertFalse("Observer wasn't removed", hasAppUsageLimitObserver(UID, OBS_ID1));
+    }
+
     /** Verify nothing happens when a nonexistent app usage observer is removed */
     @Test
     public void testAppUsageObserver_RemoveMissingObserver() {
@@ -218,6 +245,24 @@
         assertFalse("Observer should not exist", hasUsageSessionObserver(UID, OBS_ID1));
     }
 
+    /** Verify nothing happens when a nonexistent app usage limit observer is removed */
+    @Test
+    public void testAppUsageLimitObserver_RemoveMissingObserver() {
+        assertFalse("Observer should not exist", hasAppUsageLimitObserver(UID, OBS_ID1));
+        try {
+            mController.removeAppUsageLimitObserver(UID, OBS_ID1, USER_ID);
+        } catch (Exception e) {
+            StringWriter sw = new StringWriter();
+            sw.write("Hit exception trying to remove nonexistent observer:\n");
+            sw.write(e.toString());
+            PrintWriter pw = new PrintWriter(sw);
+            e.printStackTrace(pw);
+            sw.write("\nTest Failed!");
+            fail(sw.toString());
+        }
+        assertFalse("Observer should not exist", hasAppUsageLimitObserver(UID, OBS_ID1));
+    }
+
     /** Re-adding an observer should result in only one copy */
     @Test
     public void testAppUsageObserver_ObserverReAdd() {
@@ -242,22 +287,39 @@
         assertFalse("Observer wasn't removed", hasUsageSessionObserver(UID, OBS_ID1));
     }
 
+    /** Re-adding an observer should result in only one copy */
+    @Test
+    public void testAppUsageLimitObserver_ObserverReAdd() {
+        addAppUsageLimitObserver(OBS_ID1, GROUP1, TIME_30_MIN);
+        assertTrue("Observer wasn't added", hasAppUsageLimitObserver(UID, OBS_ID1));
+        addAppUsageLimitObserver(OBS_ID1, GROUP1, TIME_10_MIN);
+        assertTrue("Observer wasn't added",
+                getAppUsageLimitObserver(UID, OBS_ID1).getTimeLimitMs() == TIME_10_MIN);
+        mController.removeAppUsageLimitObserver(UID, OBS_ID1, USER_ID);
+        assertFalse("Observer wasn't removed", hasAppUsageLimitObserver(UID, OBS_ID1));
+    }
+
     /** Different type observers can be registered to the same observerId value */
     @Test
     public void testAllObservers_ExclusiveObserverIds() {
         addAppUsageObserver(OBS_ID1, GROUP1, TIME_10_MIN);
         addUsageSessionObserver(OBS_ID1, GROUP1, TIME_30_MIN, TIME_1_MIN);
+        addAppUsageLimitObserver(OBS_ID1, GROUP1, TIME_10_MIN);
         assertTrue("Observer wasn't added", hasAppUsageObserver(UID, OBS_ID1));
         assertTrue("Observer wasn't added", hasUsageSessionObserver(UID, OBS_ID1));
+        assertTrue("Observer wasn't added", hasAppUsageLimitObserver(UID, OBS_ID1));
 
         AppTimeLimitController.UsageGroup appUsageGroup = mController.getAppUsageGroup(UID,
                 OBS_ID1);
         AppTimeLimitController.UsageGroup sessionUsageGroup = mController.getSessionUsageGroup(UID,
                 OBS_ID1);
+        AppTimeLimitController.UsageGroup appUsageLimitGroup = getAppUsageLimitObserver(
+                UID, OBS_ID1);
 
         // Verify data still intact
         assertEquals(TIME_10_MIN, appUsageGroup.getTimeLimitMs());
         assertEquals(TIME_30_MIN, sessionUsageGroup.getTimeLimitMs());
+        assertEquals(TIME_10_MIN, appUsageLimitGroup.getTimeLimitMs());
     }
 
     /** Verify that usage across different apps within a group are added up */
@@ -299,7 +361,7 @@
     @Test
     public void testUsageSessionObserver_Accumulation() throws Exception {
         setTime(0L);
-        addUsageSessionObserver(OBS_ID1, GROUP1, TIME_30_MIN, TIME_1_MIN);
+        addUsageSessionObserver(OBS_ID1, GROUP1, TIME_30_MIN, TIME_10_MIN);
         startUsage(PKG_SOC1);
         // Add 10 mins
         setTime(TIME_10_MIN);
@@ -330,6 +392,41 @@
         assertTrue(mLimitReachedLatch.await(100L, TimeUnit.MILLISECONDS));
     }
 
+    /** Verify that usage across different apps within a group are added up */
+    @Test
+    public void testAppUsageLimitObserver_Accumulation() throws Exception {
+        setTime(0L);
+        addAppUsageLimitObserver(OBS_ID1, GROUP1, TIME_30_MIN);
+        startUsage(PKG_SOC1);
+        // Add 10 mins
+        setTime(TIME_10_MIN);
+        stopUsage(PKG_SOC1);
+
+        AppTimeLimitController.UsageGroup group = getAppUsageLimitObserver(UID, OBS_ID1);
+
+        long timeRemaining = group.getTimeLimitMs() - group.getUsageTimeMs();
+        assertEquals(TIME_10_MIN * 2, timeRemaining);
+
+        startUsage(PKG_SOC1);
+        setTime(TIME_10_MIN * 2);
+        stopUsage(PKG_SOC1);
+
+        timeRemaining = group.getTimeLimitMs() - group.getUsageTimeMs();
+        assertEquals(TIME_10_MIN, timeRemaining);
+
+        setTime(TIME_30_MIN);
+
+        assertFalse(mLimitReachedLatch.await(100L, TimeUnit.MILLISECONDS));
+
+        // Add a different package in the group
+        startUsage(PKG_GAME1);
+        setTime(TIME_30_MIN + TIME_10_MIN);
+        stopUsage(PKG_GAME1);
+
+        assertEquals(0, group.getTimeLimitMs() - group.getUsageTimeMs());
+        assertTrue(mLimitReachedLatch.await(100L, TimeUnit.MILLISECONDS));
+    }
+
     /** Verify that time limit does not get triggered due to a different app */
     @Test
     public void testAppUsageObserver_TimeoutOtherApp() throws Exception {
@@ -355,6 +452,18 @@
 
     }
 
+    /** Verify that time limit does not get triggered due to a different app */
+    @Test
+    public void testAppUsageLimitObserver_TimeoutOtherApp() throws Exception {
+        setTime(0L);
+        addAppUsageLimitObserver(OBS_ID1, GROUP1, 4_000L);
+        startUsage(PKG_SOC2);
+        assertFalse(mLimitReachedLatch.await(6_000L, TimeUnit.MILLISECONDS));
+        setTime(6_000L);
+        stopUsage(PKG_SOC2);
+        assertFalse(mLimitReachedLatch.await(100L, TimeUnit.MILLISECONDS));
+    }
+
     /** Verify the timeout message is delivered at the right time */
     @Test
     public void testAppUsageObserver_Timeout() throws Exception {
@@ -385,6 +494,19 @@
         assertTrue(hasUsageSessionObserver(UID, OBS_ID1));
     }
 
+    /** Verify the timeout message is delivered at the right time */
+    @Test
+    public void testAppUsageLimitObserver_Timeout() throws Exception {
+        setTime(0L);
+        addAppUsageLimitObserver(OBS_ID1, GROUP1, 4_000L);
+        startUsage(PKG_SOC1);
+        setTime(6_000L);
+        assertTrue(mLimitReachedLatch.await(6_000L, TimeUnit.MILLISECONDS));
+        stopUsage(PKG_SOC1);
+        // Verify that the observer was not removed
+        assertTrue(hasAppUsageLimitObserver(UID, OBS_ID1));
+    }
+
     /** If an app was already running, make sure it is partially counted towards the time limit */
     @Test
     public void testAppUsageObserver_AlreadyRunning() throws Exception {
@@ -423,6 +545,25 @@
         assertTrue(hasUsageSessionObserver(UID, OBS_ID2));
     }
 
+    /** If an app was already running, make sure it is partially counted towards the time limit */
+    @Test
+    public void testAppUsageLimitObserver_AlreadyRunning() throws Exception {
+        setTime(TIME_10_MIN);
+        startUsage(PKG_GAME1);
+        setTime(TIME_30_MIN);
+        addAppUsageLimitObserver(OBS_ID2, GROUP_GAME, TIME_30_MIN);
+        setTime(TIME_30_MIN + TIME_10_MIN);
+        stopUsage(PKG_GAME1);
+        assertFalse(mLimitReachedLatch.await(1_000L, TimeUnit.MILLISECONDS));
+
+        startUsage(PKG_GAME2);
+        setTime(TIME_30_MIN + TIME_30_MIN);
+        stopUsage(PKG_GAME2);
+        assertTrue(mLimitReachedLatch.await(1_000L, TimeUnit.MILLISECONDS));
+        // Verify that the observer was not removed
+        assertTrue(hasAppUsageLimitObserver(UID, OBS_ID2));
+    }
+
     /** If watched app is already running, verify the timeout callback happens at the right time */
     @Test
     public void testAppUsageObserver_AlreadyRunningTimeout() throws Exception {
@@ -464,6 +605,24 @@
         assertTrue(hasUsageSessionObserver(UID, OBS_ID1));
     }
 
+    /** If watched app is already running, verify the timeout callback happens at the right time */
+    @Test
+    public void testAppUsageLimitObserver_AlreadyRunningTimeout() throws Exception {
+        setTime(0);
+        startUsage(PKG_SOC1);
+        setTime(TIME_10_MIN);
+        // 10 second time limit
+        addAppUsageLimitObserver(OBS_ID1, GROUP_SOC, 10_000L);
+        setTime(TIME_10_MIN + 5_000L);
+        // Shouldn't call back in 6 seconds
+        assertFalse(mLimitReachedLatch.await(6_000L, TimeUnit.MILLISECONDS));
+        setTime(TIME_10_MIN + 10_000L);
+        // Should call back by 11 seconds (6 earlier + 5 now)
+        assertTrue(mLimitReachedLatch.await(5_000L, TimeUnit.MILLISECONDS));
+        // Verify that the observer was not removed
+        assertTrue(hasAppUsageLimitObserver(UID, OBS_ID1));
+    }
+
     /**
      * Verify that App Time Limit Controller will limit the number of observerIds for app usage
      * observers
@@ -525,6 +684,37 @@
         assertTrue("Should have caused an IllegalStateException", receivedException);
     }
 
+    /**
+     * Verify that App Time Limit Controller will limit the number of observerIds for app usage
+     * limit observers
+     */
+    @Test
+    public void testAppUsageLimitObserver_MaxObserverLimit() throws Exception {
+        boolean receivedException = false;
+        int ANOTHER_UID = UID + 1;
+        addAppUsageLimitObserver(OBS_ID1, GROUP1, TIME_30_MIN);
+        addAppUsageLimitObserver(OBS_ID2, GROUP1, TIME_30_MIN);
+        addAppUsageLimitObserver(OBS_ID3, GROUP1, TIME_30_MIN);
+        addAppUsageLimitObserver(OBS_ID4, GROUP1, TIME_30_MIN);
+        addAppUsageLimitObserver(OBS_ID5, GROUP1, TIME_30_MIN);
+        addAppUsageLimitObserver(OBS_ID6, GROUP1, TIME_30_MIN);
+        addAppUsageLimitObserver(OBS_ID7, GROUP1, TIME_30_MIN);
+        addAppUsageLimitObserver(OBS_ID8, GROUP1, TIME_30_MIN);
+        addAppUsageLimitObserver(OBS_ID9, GROUP1, TIME_30_MIN);
+        addAppUsageLimitObserver(OBS_ID10, GROUP1, TIME_30_MIN);
+        // Readding an observer should not cause an IllegalStateException
+        addAppUsageLimitObserver(OBS_ID5, GROUP1, TIME_30_MIN);
+        // Adding an observer for a different uid shouldn't cause an IllegalStateException
+        mController.addAppUsageLimitObserver(
+                ANOTHER_UID, OBS_ID11, GROUP1, TIME_30_MIN, null, USER_ID);
+        try {
+            addAppUsageLimitObserver(OBS_ID11, GROUP1, TIME_30_MIN);
+        } catch (IllegalStateException ise) {
+            receivedException = true;
+        }
+        assertTrue("Should have caused an IllegalStateException", receivedException);
+    }
+
     /** Verify that addAppUsageObserver minimum time limit is one minute */
     @Test
     public void testAppUsageObserver_MinimumTimeLimit() throws Exception {
@@ -553,6 +743,20 @@
         assertTrue("Should have caused an IllegalArgumentException", receivedException);
     }
 
+    /** Verify that addAppUsageLimitObserver minimum time limit is one minute */
+    @Test
+    public void testAppUsageLimitObserver_MinimumTimeLimit() throws Exception {
+        boolean receivedException = false;
+        // adding an observer with a one minute time limit should not cause an exception
+        addAppUsageLimitObserver(OBS_ID1, GROUP1, MIN_TIME_LIMIT);
+        try {
+            addAppUsageLimitObserver(OBS_ID1, GROUP1, MIN_TIME_LIMIT - 1);
+        } catch (IllegalArgumentException iae) {
+            receivedException = true;
+        }
+        assertTrue("Should have caused an IllegalArgumentException", receivedException);
+    }
+
     /** Verify that concurrent usage from multiple apps in the same group will counted correctly */
     @Test
     public void testAppUsageObserver_ConcurrentUsage() throws Exception {
@@ -599,6 +803,29 @@
         assertTrue(mLimitReachedLatch.await(100L, TimeUnit.MILLISECONDS));
     }
 
+    /** Verify that concurrent usage from multiple apps in the same group will counted correctly */
+    @Test
+    public void testAppUsageLimitObserver_ConcurrentUsage() throws Exception {
+        setTime(0L);
+        addAppUsageLimitObserver(OBS_ID1, GROUP1, TIME_30_MIN);
+        AppTimeLimitController.UsageGroup group = getAppUsageLimitObserver(UID, OBS_ID1);
+        startUsage(PKG_SOC1);
+        // Add 10 mins
+        setTime(TIME_10_MIN);
+
+        // Add a different package in the group will first package is still in use
+        startUsage(PKG_GAME1);
+        setTime(TIME_10_MIN * 2);
+        // Stop first package usage
+        stopUsage(PKG_SOC1);
+
+        setTime(TIME_30_MIN);
+        stopUsage(PKG_GAME1);
+
+        assertEquals(TIME_30_MIN, group.getUsageTimeMs());
+        assertTrue(mLimitReachedLatch.await(100L, TimeUnit.MILLISECONDS));
+    }
+
     /** Verify that a session will continue if usage starts again within the session threshold */
     @Test
     public void testUsageSessionObserver_ContinueSession() throws Exception {
@@ -737,6 +964,97 @@
         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() {
+        addAppUsageLimitObserver(OBS_ID1, GROUP1, TIME_30_MIN);
+        AppTimeLimitController.AppUsageLimitGroup group = getAppUsageLimitObserver(UID, OBS_ID1);
+        assertNotNull("Observer wasn't added", group);
+        assertEquals("Observer didn't correctly report total usage limit",
+                TIME_30_MIN, group.getTotaUsageLimit());
+    }
+
+    /** Verify app usage limit observer added correctly reports its total usage limit */
+    @Test
+    public void testAppUsageLimitObserver_GetUsageRemaining() {
+        setTime(0L);
+        addAppUsageLimitObserver(OBS_ID1, GROUP1, TIME_30_MIN);
+        startUsage(PKG_SOC1);
+        setTime(TIME_10_MIN);
+        stopUsage(PKG_SOC1);
+        AppTimeLimitController.AppUsageLimitGroup group = getAppUsageLimitObserver(UID, OBS_ID1);
+        assertNotNull("Observer wasn't added", group);
+        assertEquals("Observer didn't correctly report total usage limit",
+                TIME_10_MIN * 2, group.getUsageRemaining());
+    }
+
+    /** Verify the app usage limit observer with the smallest usage limit remaining is returned
+     *  when querying the getAppUsageLimit API.
+     */
+    @Test
+    public void testAppUsageLimitObserver_GetAppUsageLimit() {
+        addAppUsageLimitObserver(OBS_ID1, GROUP1, TIME_30_MIN);
+        addAppUsageLimitObserver(OBS_ID2, GROUP_SOC, TIME_10_MIN);
+        UsageStatsManagerInternal.AppUsageLimitData group = getAppUsageLimit(PKG_SOC1);
+        assertEquals("Observer with the smallest usage limit remaining wasn't returned",
+                TIME_10_MIN, group.getTotalUsageLimit());
+    }
+
+    /** Verify the app usage limit observer with the smallest usage limit remaining is returned
+     *  when querying the getAppUsageLimit API.
+     */
+    @Test
+    public void testAppUsageLimitObserver_GetAppUsageLimitUsed() {
+        setTime(0L);
+        addAppUsageLimitObserver(OBS_ID1, GROUP1, TIME_30_MIN);
+        addAppUsageLimitObserver(OBS_ID2, GROUP_SOC, TIME_10_MIN);
+        startUsage(PKG_GAME1);
+        setTime(TIME_10_MIN * 2 + TIME_1_MIN);
+        stopUsage(PKG_GAME1);
+        // PKG_GAME1 is only in GROUP1 but since we're querying for PCK_SOC1 which is
+        // in both groups, GROUP1 should be returned since it has a smaller time remaining
+        UsageStatsManagerInternal.AppUsageLimitData group = getAppUsageLimit(PKG_SOC1);
+        assertEquals("Observer with the smallest usage limit remaining wasn't returned",
+                TIME_1_MIN * 9, group.getUsageRemaining());
+    }
+
+    /** Verify the app usage limit observer with the smallest usage limit remaining is returned
+     *  when querying the getAppUsageLimit API.
+     */
+    @Test
+    public void testAppUsageLimitObserver_GetAppUsageLimitAllUsed() {
+        setTime(0L);
+        addAppUsageLimitObserver(OBS_ID1, GROUP1, TIME_30_MIN);
+        addAppUsageLimitObserver(OBS_ID2, GROUP_SOC, TIME_10_MIN);
+        startUsage(PKG_SOC1);
+        setTime(TIME_10_MIN);
+        stopUsage(PKG_SOC1);
+        // GROUP_SOC should be returned since it should be completely used up (0ms remaining)
+        UsageStatsManagerInternal.AppUsageLimitData group = getAppUsageLimit(PKG_SOC1);
+        assertEquals("Observer with the smallest usage limit remaining wasn't returned",
+                0L, group.getUsageRemaining());
+    }
+
     private void startUsage(String packageName) {
         mController.noteUsageStart(packageName, USER_ID);
     }
@@ -759,6 +1077,10 @@
                 null, null, USER_ID);
     }
 
+    private void addAppUsageLimitObserver(int observerId, String[] packages, long timeLimit) {
+        mController.addAppUsageLimitObserver(UID, observerId, packages, timeLimit, null, USER_ID);
+    }
+
     /** Is there still an app usage observer by that id */
     private boolean hasAppUsageObserver(int uid, int observerId) {
         return mController.getAppUsageGroup(uid, observerId) != null;
@@ -769,6 +1091,20 @@
         return mController.getSessionUsageGroup(uid, observerId) != null;
     }
 
+    /** Is there still an app usage limit observer by that id */
+    private boolean hasAppUsageLimitObserver(int uid, int observerId) {
+        return mController.getAppUsageLimitGroup(uid, observerId) != null;
+    }
+
+    private AppTimeLimitController.AppUsageLimitGroup getAppUsageLimitObserver(
+            int uid, int observerId) {
+        return mController.getAppUsageLimitGroup(uid, observerId);
+    }
+
+    private UsageStatsManagerInternal.AppUsageLimitData getAppUsageLimit(String packageName) {
+        return mController.getAppUsageLimit(packageName, UserHandle.of(USER_ID));
+    }
+
     private void setTime(long time) {
         mUptimeMillis = time;
     }
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 c0f9b80..9c6ab0a 100644
--- a/services/tests/uiservicestests/src/com/android/server/notification/NotificationManagerServiceTest.java
+++ b/services/tests/uiservicestests/src/com/android/server/notification/NotificationManagerServiceTest.java
@@ -245,8 +245,8 @@
         }
 
         @Override
-        void logSmartSuggestionsVisible(NotificationRecord r) {
-            super.logSmartSuggestionsVisible(r);
+        void logSmartSuggestionsVisible(NotificationRecord r, int notificationLocation) {
+            super.logSmartSuggestionsVisible(r, notificationLocation);
             countLogSmartSuggestionsVisible++;
         }
 
@@ -3410,6 +3410,77 @@
     }
 
     @Test
+    public void testHideAndUnhideNotificationsOnDistractingPackageBroadcast() {
+        // Post 2 notifications from 2 packages
+        NotificationRecord pkgA = new NotificationRecord(mContext,
+                generateSbn("a", 1000, 9, 0), mTestNotificationChannel);
+        mService.addNotification(pkgA);
+        NotificationRecord pkgB = new NotificationRecord(mContext,
+                generateSbn("b", 1001, 9, 0), mTestNotificationChannel);
+        mService.addNotification(pkgB);
+
+        // on broadcast, hide one of the packages
+        mService.simulatePackageDistractionBroadcast(
+                PackageManager.RESTRICTION_HIDE_NOTIFICATIONS, new String[] {"a"});
+        ArgumentCaptor<List<NotificationRecord>> captorHide = ArgumentCaptor.forClass(List.class);
+        verify(mListeners, times(1)).notifyHiddenLocked(captorHide.capture());
+        assertEquals(1, captorHide.getValue().size());
+        assertEquals("a", captorHide.getValue().get(0).sbn.getPackageName());
+
+        // on broadcast, unhide the package
+        mService.simulatePackageDistractionBroadcast(
+                PackageManager.RESTRICTION_HIDE_FROM_SUGGESTIONS, new String[] {"a"});
+        ArgumentCaptor<List<NotificationRecord>> captorUnhide = ArgumentCaptor.forClass(List.class);
+        verify(mListeners, times(1)).notifyUnhiddenLocked(captorUnhide.capture());
+        assertEquals(1, captorUnhide.getValue().size());
+        assertEquals("a", captorUnhide.getValue().get(0).sbn.getPackageName());
+    }
+
+    @Test
+    public void testHideAndUnhideNotificationsOnDistractingPackageBroadcast_multiPkg() {
+        // Post 2 notifications from 2 packages
+        NotificationRecord pkgA = new NotificationRecord(mContext,
+                generateSbn("a", 1000, 9, 0), mTestNotificationChannel);
+        mService.addNotification(pkgA);
+        NotificationRecord pkgB = new NotificationRecord(mContext,
+                generateSbn("b", 1001, 9, 0), mTestNotificationChannel);
+        mService.addNotification(pkgB);
+
+        // on broadcast, hide one of the packages
+        mService.simulatePackageDistractionBroadcast(
+                PackageManager.RESTRICTION_HIDE_NOTIFICATIONS, new String[] {"a", "b"});
+        ArgumentCaptor<List<NotificationRecord>> captorHide = ArgumentCaptor.forClass(List.class);
+        verify(mListeners, times(2)).notifyHiddenLocked(captorHide.capture());
+        assertEquals(2, captorHide.getValue().size());
+        assertEquals("a", captorHide.getValue().get(0).sbn.getPackageName());
+        assertEquals("b", captorHide.getValue().get(1).sbn.getPackageName());
+
+        // on broadcast, unhide the package
+        mService.simulatePackageDistractionBroadcast(
+                PackageManager.RESTRICTION_HIDE_FROM_SUGGESTIONS, new String[] {"a", "b"});
+        ArgumentCaptor<List<NotificationRecord>> captorUnhide = ArgumentCaptor.forClass(List.class);
+        verify(mListeners, times(2)).notifyUnhiddenLocked(captorUnhide.capture());
+        assertEquals(2, captorUnhide.getValue().size());
+        assertEquals("a", captorUnhide.getValue().get(0).sbn.getPackageName());
+        assertEquals("b", captorUnhide.getValue().get(1).sbn.getPackageName());
+    }
+
+    @Test
+    public void testNoNotificationsHiddenOnDistractingPackageBroadcast() {
+        // post notification from this package
+        final NotificationRecord notif1 = generateNotificationRecord(
+                mTestNotificationChannel, 1, null, true);
+        mService.addNotification(notif1);
+
+        // on broadcast, nothing is hidden since no notifications are of package "test_package"
+        mService.simulatePackageDistractionBroadcast(
+                PackageManager.RESTRICTION_HIDE_NOTIFICATIONS, new String[] {"test_package"});
+        ArgumentCaptor<List> captor = ArgumentCaptor.forClass(List.class);
+        verify(mListeners, times(1)).notifyHiddenLocked(captor.capture());
+        assertEquals(0, captor.getValue().size());
+    }
+
+    @Test
     public void testCanUseManagedServicesLowRamNoWatchNullPkg() {
         when(mPackageManagerClient.hasSystemFeature(FEATURE_WATCH)).thenReturn(false);
         when(mActivityManager.isLowRamDevice()).thenReturn(true);
diff --git a/services/tests/wmtests/src/com/android/server/wm/ActivityRecordTests.java b/services/tests/wmtests/src/com/android/server/wm/ActivityRecordTests.java
index 8be63fc..319ffed 100644
--- a/services/tests/wmtests/src/com/android/server/wm/ActivityRecordTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/ActivityRecordTests.java
@@ -31,6 +31,7 @@
 import static com.android.server.policy.WindowManagerPolicy.NAV_BAR_RIGHT;
 import static com.android.server.wm.ActivityStack.ActivityState.INITIALIZING;
 import static com.android.server.wm.ActivityStack.ActivityState.PAUSING;
+import static com.android.server.wm.ActivityStack.ActivityState.RESUMED;
 import static com.android.server.wm.ActivityStack.ActivityState.STOPPED;
 import static com.android.server.wm.ActivityStack.REMOVE_TASK_MODE_MOVING;
 
@@ -75,6 +76,9 @@
         mStack = (TestActivityStack) new StackBuilder(mRootActivityContainer).build();
         mTask = mStack.getChildAt(0);
         mActivity = mTask.getTopActivity();
+
+        doReturn(false).when(mService).isBooting();
+        doReturn(true).when(mService).isBooted();
     }
 
     @Test
@@ -117,22 +121,23 @@
 
         mActivity.setState(STOPPED, "testPausingWhenVisibleFromStopped");
 
-        // The activity is in the focused stack so it should not move to paused.
+        // The activity is in the focused stack so it should be resumed.
         mActivity.makeVisibleIfNeeded(null /* starting */, true /* reportToClient */);
-        assertTrue(mActivity.isState(STOPPED));
+        assertTrue(mActivity.isState(RESUMED));
         assertFalse(pauseFound.value);
 
-        // Clear focused stack
-        final ActivityDisplay display = mRootActivityContainer.getDefaultDisplay();
-        when(display.getFocusedStack()).thenReturn(null);
+        // Make the activity non focusable
+        mActivity.setState(STOPPED, "testPausingWhenVisibleFromStopped");
+        doReturn(false).when(mActivity).isFocusable();
 
-        // In the unfocused stack, the activity should move to paused.
+        // If the activity is not focusable, it should move to paused.
         mActivity.makeVisibleIfNeeded(null /* starting */, true /* reportToClient */);
         assertTrue(mActivity.isState(PAUSING));
         assertTrue(pauseFound.value);
 
         // Make sure that the state does not change for current non-stopping states.
         mActivity.setState(INITIALIZING, "testPausingWhenVisibleFromStopped");
+        doReturn(true).when(mActivity).isFocusable();
 
         mActivity.makeVisibleIfNeeded(null /* starting */, true /* reportToClient */);
 
diff --git a/services/tests/wmtests/src/com/android/server/wm/ActivityStarterTests.java b/services/tests/wmtests/src/com/android/server/wm/ActivityStarterTests.java
index a381023..056568a 100644
--- a/services/tests/wmtests/src/com/android/server/wm/ActivityStarterTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/ActivityStarterTests.java
@@ -549,8 +549,7 @@
         verify(mActivityMetricsLogger, times(1)).logActivityStart(any(), any(), any(),
                 eq(FAKE_CALLING_UID), eq(FAKE_CALLING_PACKAGE), anyInt(), anyBoolean(),
                 eq(FAKE_REAL_CALLING_UID), anyInt(), anyBoolean(), anyInt(),
-                eq(ActivityBuilder.getDefaultComponent().getPackageName()), anyInt(), anyBoolean(),
-                any(), eq(false));
+                any(), anyInt(), anyBoolean(), any(), eq(false));
     }
 
     /**
@@ -599,6 +598,10 @@
                 Process.SYSTEM_UID, false, PROCESS_STATE_TOP + 1,
                 UNIMPORTANT_UID2, false, PROCESS_STATE_TOP + 1,
                 false, false, false);
+        runAndVerifyBackgroundActivityStartsSubtest("disallowed_nfcUid_notAborted", false,
+                Process.NFC_UID, false, PROCESS_STATE_TOP + 1,
+                UNIMPORTANT_UID2, false, PROCESS_STATE_TOP + 1,
+                false, false, false);
         runAndVerifyBackgroundActivityStartsSubtest(
                 "disallowed_callingUidHasVisibleWindow_notAborted", false,
                 UNIMPORTANT_UID, true, PROCESS_STATE_TOP + 1,
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/AppChangeTransitionTests.java b/services/tests/wmtests/src/com/android/server/wm/AppChangeTransitionTests.java
new file mode 100644
index 0000000..19ace3c
--- /dev/null
+++ b/services/tests/wmtests/src/com/android/server/wm/AppChangeTransitionTests.java
@@ -0,0 +1,102 @@
+/*
+ * 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.app.WindowConfiguration.WINDOWING_MODE_FREEFORM;
+import static android.view.WindowManager.TRANSIT_TASK_CHANGE_WINDOWING_MODE;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertNotNull;
+import static org.junit.Assert.assertNull;
+
+import android.os.IBinder;
+import android.view.IRemoteAnimationFinishedCallback;
+import android.view.IRemoteAnimationRunner;
+import android.view.RemoteAnimationAdapter;
+import android.view.RemoteAnimationDefinition;
+import android.view.RemoteAnimationTarget;
+
+import androidx.test.filters.FlakyTest;
+import androidx.test.filters.SmallTest;
+
+import org.junit.Before;
+import org.junit.Test;
+
+/**
+ * Tests for change transitions
+ *
+ * Build/Install/Run:
+ *  atest WmTests:AppChangeTransitionTests
+ */
+@FlakyTest(detail = "Promote when shown to be stable.")
+@SmallTest
+public class AppChangeTransitionTests extends WindowTestsBase {
+
+    private TaskStack mStack;
+    private Task mTask;
+    private WindowTestUtils.TestAppWindowToken mToken;
+
+    @Before
+    public void setUp() throws Exception {
+        mStack = createTaskStackOnDisplay(mDisplayContent);
+        mTask = createTaskInStack(mStack, 0 /* userId */);
+        mToken = WindowTestUtils.createTestAppWindowToken(mDisplayContent);
+        mToken.mSkipOnParentSet = false;
+
+        mTask.addChild(mToken, 0);
+    }
+
+    class TestRemoteAnimationRunner implements IRemoteAnimationRunner {
+        @Override
+        public void onAnimationStart(RemoteAnimationTarget[] apps,
+                IRemoteAnimationFinishedCallback finishedCallback) {
+            for (RemoteAnimationTarget target : apps) {
+                assertNotNull(target.startBounds);
+            }
+            try {
+                finishedCallback.onAnimationFinished();
+            } catch (Exception e) {
+                throw new RuntimeException("Something went wrong");
+            }
+        }
+
+        @Override
+        public void onAnimationCancelled() {
+        }
+
+        @Override
+        public IBinder asBinder() {
+            return null;
+        }
+    }
+
+    @Test
+    public void testModeChangeRemoteAnimatorNoSnapshot() {
+        RemoteAnimationDefinition definition = new RemoteAnimationDefinition();
+        RemoteAnimationAdapter adapter =
+                new RemoteAnimationAdapter(new TestRemoteAnimationRunner(), 10, 1, false);
+        definition.addRemoteAnimation(TRANSIT_TASK_CHANGE_WINDOWING_MODE, adapter);
+        mDisplayContent.registerRemoteAnimations(definition);
+
+        mTask.setWindowingMode(WINDOWING_MODE_FREEFORM);
+        assertEquals(1, mDisplayContent.mChangingApps.size());
+        assertNull(mToken.getThumbnail());
+
+        waitUntilHandlersIdle();
+        mToken.removeImmediately();
+    }
+}
diff --git a/services/tests/wmtests/src/com/android/server/wm/BoundsAnimationControllerTests.java b/services/tests/wmtests/src/com/android/server/wm/BoundsAnimationControllerTests.java
index 1c5391e..9ce5795 100644
--- a/services/tests/wmtests/src/com/android/server/wm/BoundsAnimationControllerTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/BoundsAnimationControllerTests.java
@@ -106,7 +106,7 @@
         }
 
         public void notifyTransitionStarting(int transit) {
-            mListener.onAppTransitionStartingLocked(transit, null, null, 0, 0, 0);
+            mListener.onAppTransitionStartingLocked(transit, 0, 0, 0);
         }
 
         public void notifyTransitionFinished() {
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/RemoteAnimationControllerTest.java b/services/tests/wmtests/src/com/android/server/wm/RemoteAnimationControllerTest.java
index 413b6f4..b867799 100644
--- a/services/tests/wmtests/src/com/android/server/wm/RemoteAnimationControllerTest.java
+++ b/services/tests/wmtests/src/com/android/server/wm/RemoteAnimationControllerTest.java
@@ -25,6 +25,7 @@
 import static com.android.dx.mockito.inline.extended.ExtendedMockito.when;
 
 import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertNotNull;
 import static org.mockito.ArgumentMatchers.eq;
 
 import android.graphics.Point;
@@ -43,6 +44,7 @@
 
 import com.android.server.testutils.OffsettableClock;
 import com.android.server.testutils.TestHandler;
+import com.android.server.wm.RemoteAnimationController.RemoteAnimationRecord;
 import com.android.server.wm.SurfaceAnimator.OnAnimationFinishedCallback;
 
 import org.junit.Before;
@@ -60,8 +62,10 @@
 public class RemoteAnimationControllerTest extends WindowTestsBase {
 
     @Mock SurfaceControl mMockLeash;
+    @Mock SurfaceControl mMockThumbnailLeash;
     @Mock Transaction mMockTransaction;
     @Mock OnAnimationFinishedCallback mFinishedCallback;
+    @Mock OnAnimationFinishedCallback mThumbnailFinishedCallback;
     @Mock IRemoteAnimationRunner mMockRunner;
     private RemoteAnimationAdapter mAdapter;
     private RemoteAnimationController mController;
@@ -73,7 +77,7 @@
         MockitoAnnotations.initMocks(this);
 
         when(mMockRunner.asBinder()).thenReturn(new Binder());
-        mAdapter = new RemoteAnimationAdapter(mMockRunner, 100, 50);
+        mAdapter = new RemoteAnimationAdapter(mMockRunner, 100, 50, true /* changeNeedsSnapshot */);
         mAdapter.setCallingPid(123);
         mWm.mH.runWithScissors(() -> mHandler = new TestHandler(null, mClock), 0);
         mController = new RemoteAnimationController(mWm, mAdapter, mHandler);
@@ -84,8 +88,8 @@
         final WindowState win = createWindow(null /* parent */, TYPE_BASE_APPLICATION, "testWin");
         mDisplayContent.mOpeningApps.add(win.mAppToken);
         try {
-            final AnimationAdapter adapter = mController.createAnimationAdapter(win.mAppToken,
-                    new Point(50, 100), new Rect(50, 100, 150, 150));
+            final AnimationAdapter adapter = mController.createRemoteAnimationRecord(win.mAppToken,
+                    new Point(50, 100), new Rect(50, 100, 150, 150), null).mAdapter;
             adapter.startAnimation(mMockLeash, mMockTransaction, mFinishedCallback);
             mController.goodToGo();
             mWm.mAnimator.executeAfterPrepareSurfacesRunnables();
@@ -117,8 +121,8 @@
     @Test
     public void testCancel() throws Exception {
         final WindowState win = createWindow(null /* parent */, TYPE_BASE_APPLICATION, "testWin");
-        final AnimationAdapter adapter = mController.createAnimationAdapter(win.mAppToken,
-                new Point(50, 100), new Rect(50, 100, 150, 150));
+        final AnimationAdapter adapter = mController.createRemoteAnimationRecord(win.mAppToken,
+                new Point(50, 100), new Rect(50, 100, 150, 150), null).mAdapter;
         adapter.startAnimation(mMockLeash, mMockTransaction, mFinishedCallback);
         mController.goodToGo();
 
@@ -129,8 +133,8 @@
     @Test
     public void testTimeout() throws Exception {
         final WindowState win = createWindow(null /* parent */, TYPE_BASE_APPLICATION, "testWin");
-        final AnimationAdapter adapter = mController.createAnimationAdapter(win.mAppToken,
-                new Point(50, 100), new Rect(50, 100, 150, 150));
+        final AnimationAdapter adapter = mController.createRemoteAnimationRecord(win.mAppToken,
+                new Point(50, 100), new Rect(50, 100, 150, 150), null).mAdapter;
         adapter.startAnimation(mMockLeash, mMockTransaction, mFinishedCallback);
         mController.goodToGo();
 
@@ -147,8 +151,8 @@
             mWm.setAnimationScale(2, 5.0f);
             final WindowState win = createWindow(null /* parent */, TYPE_BASE_APPLICATION,
                     "testWin");
-            final AnimationAdapter adapter = mController.createAnimationAdapter(win.mAppToken,
-                    new Point(50, 100), new Rect(50, 100, 150, 150));
+            final AnimationAdapter adapter = mController.createRemoteAnimationRecord(
+                    win.mAppToken, new Point(50, 100), new Rect(50, 100, 150, 150), null).mAdapter;
             adapter.startAnimation(mMockLeash, mMockTransaction, mFinishedCallback);
             mController.goodToGo();
 
@@ -176,8 +180,8 @@
     @Test
     public void testNotReallyStarted() {
         final WindowState win = createWindow(null /* parent */, TYPE_BASE_APPLICATION, "testWin");
-        mController.createAnimationAdapter(win.mAppToken,
-                new Point(50, 100), new Rect(50, 100, 150, 150));
+        mController.createRemoteAnimationRecord(win.mAppToken,
+                new Point(50, 100), new Rect(50, 100, 150, 150), null);
         mController.goodToGo();
         verifyNoMoreInteractionsExceptAsBinder(mMockRunner);
     }
@@ -186,10 +190,10 @@
     public void testOneNotStarted() throws Exception {
         final WindowState win1 = createWindow(null /* parent */, TYPE_BASE_APPLICATION, "testWin1");
         final WindowState win2 = createWindow(null /* parent */, TYPE_BASE_APPLICATION, "testWin2");
-        mController.createAnimationAdapter(win1.mAppToken,
-                new Point(50, 100), new Rect(50, 100, 150, 150));
-        final AnimationAdapter adapter = mController.createAnimationAdapter(win2.mAppToken,
-                new Point(50, 100), new Rect(50, 100, 150, 150));
+        mController.createRemoteAnimationRecord(win1.mAppToken,
+                new Point(50, 100), new Rect(50, 100, 150, 150), null);
+        final AnimationAdapter adapter = mController.createRemoteAnimationRecord(win2.mAppToken,
+                new Point(50, 100), new Rect(50, 100, 150, 150), null).mAdapter;
         adapter.startAnimation(mMockLeash, mMockTransaction, mFinishedCallback);
         mController.goodToGo();
         mWm.mAnimator.executeAfterPrepareSurfacesRunnables();
@@ -205,8 +209,8 @@
     @Test
     public void testRemovedBeforeStarted() {
         final WindowState win = createWindow(null /* parent */, TYPE_BASE_APPLICATION, "testWin");
-        final AnimationAdapter adapter = mController.createAnimationAdapter(win.mAppToken,
-                new Point(50, 100), new Rect(50, 100, 150, 150));
+        final AnimationAdapter adapter = mController.createRemoteAnimationRecord(win.mAppToken,
+                new Point(50, 100), new Rect(50, 100, 150, 150), null).mAdapter;
         adapter.startAnimation(mMockLeash, mMockTransaction, mFinishedCallback);
         win.mAppToken.removeImmediately();
         mController.goodToGo();
@@ -214,6 +218,49 @@
         verify(mFinishedCallback).onAnimationFinished(eq(adapter));
     }
 
+    @Test
+    public void testChange() throws Exception {
+        final WindowState win = createWindow(null /* parent */, TYPE_BASE_APPLICATION, "testWin");
+        mDisplayContent.mChangingApps.add(win.mAppToken);
+        try {
+            final RemoteAnimationRecord record = mController.createRemoteAnimationRecord(
+                    win.mAppToken, new Point(50, 100), new Rect(50, 100, 150, 150),
+                    new Rect(0, 0, 200, 200));
+            assertNotNull(record.mThumbnailAdapter);
+            ((AnimationAdapter) record.mAdapter)
+                    .startAnimation(mMockLeash, mMockTransaction, mFinishedCallback);
+            ((AnimationAdapter) record.mThumbnailAdapter).startAnimation(mMockThumbnailLeash,
+                    mMockTransaction, mThumbnailFinishedCallback);
+            mController.goodToGo();
+            mWm.mAnimator.executeAfterPrepareSurfacesRunnables();
+            final ArgumentCaptor<RemoteAnimationTarget[]> appsCaptor =
+                    ArgumentCaptor.forClass(RemoteAnimationTarget[].class);
+            final ArgumentCaptor<IRemoteAnimationFinishedCallback> finishedCaptor =
+                    ArgumentCaptor.forClass(IRemoteAnimationFinishedCallback.class);
+            verify(mMockRunner).onAnimationStart(appsCaptor.capture(), finishedCaptor.capture());
+            assertEquals(1, appsCaptor.getValue().length);
+            final RemoteAnimationTarget app = appsCaptor.getValue()[0];
+            assertEquals(RemoteAnimationTarget.MODE_CHANGING, app.mode);
+            assertEquals(new Point(50, 100), app.position);
+            assertEquals(new Rect(50, 100, 150, 150), app.sourceContainerBounds);
+            assertEquals(new Rect(0, 0, 200, 200), app.startBounds);
+            assertEquals(mMockLeash, app.leash);
+            assertEquals(mMockThumbnailLeash, app.startLeash);
+            assertEquals(win.mWinAnimator.mLastClipRect, app.clipRect);
+            assertEquals(false, app.isTranslucent);
+            verify(mMockTransaction).setLayer(mMockLeash, app.prefixOrderIndex);
+            verify(mMockTransaction).setPosition(mMockLeash, app.position.x, app.position.y);
+            verify(mMockTransaction).setWindowCrop(mMockLeash, new Rect(0, 0, 200, 200));
+            verify(mMockTransaction).setPosition(mMockThumbnailLeash, 0, 0);
+
+            finishedCaptor.getValue().onAnimationFinished();
+            verify(mFinishedCallback).onAnimationFinished(eq(record.mAdapter));
+            verify(mThumbnailFinishedCallback).onAnimationFinished(eq(record.mThumbnailAdapter));
+        } finally {
+            mDisplayContent.mChangingApps.clear();
+        }
+    }
+
     private static void verifyNoMoreInteractionsExceptAsBinder(IInterface binder) {
         verify(binder, atLeast(0)).asBinder();
         verifyNoMoreInteractions(binder);
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/tests/wmtests/src/com/android/server/wm/WindowTestUtils.java b/services/tests/wmtests/src/com/android/server/wm/WindowTestUtils.java
index 44e998b7..2263cf3 100644
--- a/services/tests/wmtests/src/com/android/server/wm/WindowTestUtils.java
+++ b/services/tests/wmtests/src/com/android/server/wm/WindowTestUtils.java
@@ -152,6 +152,7 @@
     public static class TestAppWindowToken extends AppWindowToken {
         boolean mOnTop = false;
         private Transaction mPendingTransactionOverride;
+        boolean mSkipOnParentSet = true;
 
         private TestAppWindowToken(DisplayContent dc) {
             super(dc.mWmService, new IApplicationToken.Stub() {
@@ -200,7 +201,9 @@
 
         @Override
         void onParentSet() {
-            // Do nothing.
+            if (!mSkipOnParentSet) {
+                super.onParentSet();
+            }
         }
 
         @Override
diff --git a/services/usage/java/com/android/server/usage/AppTimeLimitController.java b/services/usage/java/com/android/server/usage/AppTimeLimitController.java
index 2ed11fe..fa472e2 100644
--- a/services/usage/java/com/android/server/usage/AppTimeLimitController.java
+++ b/services/usage/java/com/android/server/usage/AppTimeLimitController.java
@@ -18,11 +18,14 @@
 
 import android.annotation.UserIdInt;
 import android.app.PendingIntent;
+import android.app.usage.UsageStatsManagerInternal;
 import android.os.Handler;
 import android.os.Looper;
 import android.os.Message;
 import android.os.SystemClock;
+import android.os.UserHandle;
 import android.util.ArrayMap;
+import android.util.ArraySet;
 import android.util.Slog;
 import android.util.SparseArray;
 
@@ -163,6 +166,9 @@
         /** Map of observerId to details of the time limit group */
         SparseArray<SessionUsageGroup> sessionUsageGroups = new SparseArray<>();
 
+        /** Map of observerId to details of the app usage limit group */
+        SparseArray<AppUsageLimitGroup> appUsageLimitGroups = new SparseArray<>();
+
         private ObserverAppData(int uid) {
             this.uid = uid;
         }
@@ -177,6 +183,10 @@
             sessionUsageGroups.remove(observerId);
         }
 
+        @GuardedBy("mLock")
+        void removeAppUsageLimitGroup(int observerId) {
+            appUsageLimitGroups.remove(observerId);
+        }
 
         @GuardedBy("mLock")
         void dump(PrintWriter pw) {
@@ -194,6 +204,12 @@
                 sessionUsageGroups.valueAt(i).dump(pw);
                 pw.println();
             }
+            pw.println("    App Usage Limit Groups:");
+            final int nAppUsageLimitGroups = appUsageLimitGroups.size();
+            for (int i = 0; i < nAppUsageLimitGroups; i++) {
+                appUsageLimitGroups.valueAt(i).dump(pw);
+                pw.println();
+            }
         }
     }
 
@@ -493,6 +509,54 @@
         }
     }
 
+    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
+        @GuardedBy("mLock")
+        public void remove() {
+            super.remove();
+            ObserverAppData observerApp = mObserverAppRef.get();
+            if (observerApp != null) {
+                observerApp.removeAppUsageLimitGroup(mObserverId);
+            }
+        }
+
+        @GuardedBy("mLock")
+        boolean isGroupLimit() {
+            return mGroupLimit;
+        }
+
+        @GuardedBy("mLock")
+        long getTotaUsageLimit() {
+            return mTimeLimitMs;
+        }
+
+        @GuardedBy("mLock")
+        long getUsageRemaining() {
+            // If there is currently an active session, account for its usage
+            if (mActives > 0) {
+                return mTimeLimitMs - mUsageTimeMs - (getUptimeMillis() - mLastKnownUsageTimeMs);
+            } else {
+                return mTimeLimitMs - mUsageTimeMs;
+            }
+        }
+
+        @Override
+        @GuardedBy("mLock")
+        void dump(PrintWriter pw) {
+            super.dump(pw);
+            pw.print(" groupLimit=");
+            pw.print(mGroupLimit);
+        }
+    }
+
 
     private class MyHandler extends Handler {
         static final int MSG_CHECK_TIMEOUT = 1;
@@ -553,6 +617,12 @@
 
     /** Overrideable for testing purposes */
     @VisibleForTesting
+    protected long getAppUsageLimitObserverPerUidLimit() {
+        return MAX_OBSERVER_PER_UID;
+    }
+
+    /** Overrideable for testing purposes */
+    @VisibleForTesting
     protected long getMinTimeLimit() {
         return ONE_MINUTE;
     }
@@ -572,6 +642,61 @@
         }
     }
 
+    @VisibleForTesting
+    AppUsageLimitGroup getAppUsageLimitGroup(int observerAppUid, int observerId) {
+        synchronized (mLock) {
+            return getOrCreateObserverAppDataLocked(observerAppUid).appUsageLimitGroups.get(
+                    observerId);
+        }
+    }
+
+    /**
+     * Returns an object describing the app usage limit for the given package which was set via
+     * {@link #addAppUsageLimitObserver).
+     * If there are multiple limits that apply to the package, the one with the smallest
+     * time remaining will be returned.
+     */
+    public UsageStatsManagerInternal.AppUsageLimitData getAppUsageLimit(
+            String packageName, UserHandle user) {
+        synchronized (mLock) {
+            final UserData userData = getOrCreateUserDataLocked(user.getIdentifier());
+            if (userData == null) {
+                return null;
+            }
+
+            final ArrayList<UsageGroup> usageGroups = userData.observedMap.get(packageName);
+            if (usageGroups == null || usageGroups.isEmpty()) {
+                return null;
+            }
+
+            final ArraySet<AppUsageLimitGroup> usageLimitGroups = new ArraySet<>();
+            for (int i = 0; i < usageGroups.size(); i++) {
+                if (usageGroups.get(i) instanceof AppUsageLimitGroup) {
+                    final AppUsageLimitGroup group = (AppUsageLimitGroup) usageGroups.get(i);
+                    for (int j = 0; j < group.mObserved.length; j++) {
+                        if (group.mObserved[j].equals(packageName)) {
+                            usageLimitGroups.add(group);
+                            break;
+                        }
+                    }
+                }
+            }
+            if (usageLimitGroups.isEmpty()) {
+                return null;
+            }
+
+            AppUsageLimitGroup smallestGroup = usageLimitGroups.valueAt(0);
+            for (int i = 1; i < usageLimitGroups.size(); i++) {
+                final AppUsageLimitGroup otherGroup = usageLimitGroups.valueAt(i);
+                if (otherGroup.getUsageRemaining() < smallestGroup.getUsageRemaining()) {
+                    smallestGroup = otherGroup;
+                }
+            }
+            return new UsageStatsManagerInternal.AppUsageLimitData(smallestGroup.isGroupLimit(),
+                    smallestGroup.getTotaUsageLimit(), smallestGroup.getUsageRemaining());
+        }
+    }
+
     /** Returns an existing UserData object for the given userId, or creates one */
     @GuardedBy("mLock")
     private UserData getOrCreateUserDataLocked(int userId) {
@@ -726,6 +851,61 @@
     }
 
     /**
+     * Registers an app usage limit observer with the given details.
+     * Existing app usage limit observer with the same observerId will be removed.
+     */
+    public void addAppUsageLimitObserver(int requestingUid, int observerId, String[] observed,
+            long timeLimit, PendingIntent callbackIntent, @UserIdInt int userId) {
+        if (timeLimit < getMinTimeLimit()) {
+            throw new IllegalArgumentException("Time limit must be >= " + getMinTimeLimit());
+        }
+        synchronized (mLock) {
+            UserData user = getOrCreateUserDataLocked(userId);
+            ObserverAppData observerApp = getOrCreateObserverAppDataLocked(requestingUid);
+            AppUsageLimitGroup group = observerApp.appUsageLimitGroups.get(observerId);
+            if (group != null) {
+                // Remove previous app usage group associated with observerId
+                group.remove();
+            }
+
+            final int observerIdCount = observerApp.appUsageLimitGroups.size();
+            if (observerIdCount >= getAppUsageLimitObserverPerUidLimit()) {
+                throw new IllegalStateException(
+                        "Too many app usage observers added by uid " + requestingUid);
+            }
+            group = new AppUsageLimitGroup(user, observerApp, observerId, observed, timeLimit,
+                    callbackIntent);
+            observerApp.appUsageLimitGroups.append(observerId, group);
+
+            if (DEBUG) {
+                Slog.d(TAG, "addObserver " + observed + " for " + timeLimit);
+            }
+
+            user.addUsageGroup(group);
+            noteActiveLocked(user, group, getUptimeMillis());
+        }
+    }
+
+    /**
+     * Remove a registered observer by observerId and calling uid.
+     *
+     * @param requestingUid The calling uid
+     * @param observerId    The unique observer id for this user
+     * @param userId        The user id of the observer
+     */
+    public void removeAppUsageLimitObserver(int requestingUid, int observerId,
+            @UserIdInt int userId) {
+        synchronized (mLock) {
+            final ObserverAppData observerApp = getOrCreateObserverAppDataLocked(requestingUid);
+            final AppUsageLimitGroup group = observerApp.appUsageLimitGroups.get(observerId);
+            if (group != null) {
+                // Remove previous app usage group associated with observerId
+                group.remove();
+            }
+        }
+    }
+
+    /**
      * Called when an entity becomes active.
      *
      * @param name      The entity that became active
diff --git a/services/usage/java/com/android/server/usage/UsageStatsService.java b/services/usage/java/com/android/server/usage/UsageStatsService.java
index 6ad698b..85939d4 100644
--- a/services/usage/java/com/android/server/usage/UsageStatsService.java
+++ b/services/usage/java/com/android/server/usage/UsageStatsService.java
@@ -855,6 +855,22 @@
                     == PackageManager.PERMISSION_GRANTED;
         }
 
+        private boolean hasPermissions(String callingPackage, String... permissions) {
+            final int callingUid = Binder.getCallingUid();
+            if (callingUid == Process.SYSTEM_UID) {
+                // Caller is the system, so proceed.
+                return true;
+            }
+
+            boolean hasPermissions = true;
+            final Context context = getContext();
+            for (int i = 0; i < permissions.length; i++) {
+                hasPermissions = hasPermissions && (context.checkCallingPermission(permissions[i])
+                        == PackageManager.PERMISSION_GRANTED);
+            }
+            return hasPermissions;
+        }
+
         private void checkCallerIsSystemOrSameApp(String pkg) {
             if (isCallingUidSystem()) {
                 return;
@@ -1346,6 +1362,51 @@
         }
 
         @Override
+        public void registerAppUsageLimitObserver(int observerId, String[] packages,
+                long timeLimitMs, PendingIntent callbackIntent, String callingPackage) {
+            if (!hasPermissions(callingPackage,
+                    Manifest.permission.SUSPEND_APPS, Manifest.permission.OBSERVE_APP_USAGE)) {
+                throw new SecurityException("Caller doesn't have both SUSPEND_APPS and "
+                        + "OBSERVE_APP_USAGE permissions");
+            }
+
+            if (packages == null || packages.length == 0) {
+                throw new IllegalArgumentException("Must specify at least one package");
+            }
+            if (callbackIntent == null) {
+                throw new NullPointerException("callbackIntent can't be null");
+            }
+            final int callingUid = Binder.getCallingUid();
+            final int userId = UserHandle.getUserId(callingUid);
+            final long token = Binder.clearCallingIdentity();
+            try {
+                UsageStatsService.this.registerAppUsageLimitObserver(callingUid, observerId,
+                        packages, timeLimitMs, callbackIntent, userId);
+            } finally {
+                Binder.restoreCallingIdentity(token);
+            }
+        }
+
+        @Override
+        public void unregisterAppUsageLimitObserver(int observerId, String callingPackage) {
+            if (!hasPermissions(callingPackage,
+                    Manifest.permission.SUSPEND_APPS, Manifest.permission.OBSERVE_APP_USAGE)) {
+                throw new SecurityException("Caller doesn't have both SUSPEND_APPS and "
+                        + "OBSERVE_APP_USAGE permissions");
+            }
+
+            final int callingUid = Binder.getCallingUid();
+            final int userId = UserHandle.getUserId(callingUid);
+            final long token = Binder.clearCallingIdentity();
+            try {
+                UsageStatsService.this.unregisterAppUsageLimitObserver(
+                        callingUid, observerId, userId);
+            } finally {
+                Binder.restoreCallingIdentity(token);
+            }
+        }
+
+        @Override
         public void reportUsageStart(IBinder activity, String token, String callingPackage) {
             reportPastUsageStart(activity, token, 0, callingPackage);
         }
@@ -1447,6 +1508,16 @@
         mAppTimeLimit.removeUsageSessionObserver(callingUid, sessionObserverId, userId);
     }
 
+    void registerAppUsageLimitObserver(int callingUid, int observerId, String[] packages,
+            long timeLimitMs, PendingIntent callbackIntent, int userId) {
+        mAppTimeLimit.addAppUsageLimitObserver(callingUid, observerId, packages, timeLimitMs,
+                callbackIntent, userId);
+    }
+
+    void unregisterAppUsageLimitObserver(int callingUid, int observerId, int userId) {
+        mAppTimeLimit.removeAppUsageLimitObserver(callingUid, observerId, userId);
+    }
+
     /**
      * This local service implementation is primarily used by ActivityManagerService.
      * ActivityManagerService will call these methods holding the 'am' lock, which means we
@@ -1652,5 +1723,10 @@
         public void reportExemptedSyncStart(String packageName, int userId) {
             mAppStandby.postReportExemptedSyncStart(packageName, userId);
         }
+
+        @Override
+        public AppUsageLimitData getAppUsageLimit(String packageName, UserHandle user) {
+            return mAppTimeLimit.getAppUsageLimit(packageName, user);
+        }
     }
 }
diff --git a/services/usb/Android.bp b/services/usb/Android.bp
index feb7b76..20855b7 100644
--- a/services/usb/Android.bp
+++ b/services/usb/Android.bp
@@ -10,6 +10,7 @@
     static_libs: [
         "android.hardware.usb-V1.0-java",
         "android.hardware.usb-V1.1-java",
+        "android.hardware.usb-V1.2-java",
         "android.hardware.usb.gadget-V1.0-java",
     ],
 }
diff --git a/services/usb/java/com/android/server/usb/UsbPortManager.java b/services/usb/java/com/android/server/usb/UsbPortManager.java
index 6f210e3..50e4faa 100644
--- a/services/usb/java/com/android/server/usb/UsbPortManager.java
+++ b/services/usb/java/com/android/server/usb/UsbPortManager.java
@@ -16,6 +16,8 @@
 
 package com.android.server.usb;
 
+import static android.hardware.usb.UsbPortStatus.CONTAMINANT_DETECTION_NOT_SUPPORTED;
+import static android.hardware.usb.UsbPortStatus.CONTAMINANT_PROTECTION_NONE;
 import static android.hardware.usb.UsbPortStatus.DATA_ROLE_DEVICE;
 import static android.hardware.usb.UsbPortStatus.DATA_ROLE_HOST;
 import static android.hardware.usb.UsbPortStatus.MODE_DFP;
@@ -29,19 +31,23 @@
 
 import android.Manifest;
 import android.annotation.NonNull;
+import android.app.Notification;
+import android.app.NotificationManager;
+import android.app.PendingIntent;
 import android.content.Context;
 import android.content.Intent;
+import android.content.res.Resources;
 import android.hardware.usb.ParcelableUsbPort;
 import android.hardware.usb.UsbManager;
 import android.hardware.usb.UsbPort;
 import android.hardware.usb.UsbPortStatus;
-import android.hardware.usb.V1_0.IUsb;
 import android.hardware.usb.V1_0.PortRole;
 import android.hardware.usb.V1_0.PortRoleType;
-import android.hardware.usb.V1_0.PortStatus;
 import android.hardware.usb.V1_0.Status;
-import android.hardware.usb.V1_1.IUsbCallback;
 import android.hardware.usb.V1_1.PortStatus_1_1;
+import android.hardware.usb.V1_2.IUsb;
+import android.hardware.usb.V1_2.IUsbCallback;
+import android.hardware.usb.V1_2.PortStatus;
 import android.hidl.manager.V1_0.IServiceManager;
 import android.hidl.manager.V1_0.IServiceNotification;
 import android.os.Bundle;
@@ -55,18 +61,20 @@
 import android.os.UserHandle;
 import android.service.usb.UsbPortInfoProto;
 import android.service.usb.UsbPortManagerProto;
+import android.service.usb.UsbServiceProto;
 import android.util.ArrayMap;
 import android.util.Log;
 import android.util.Slog;
 import android.util.StatsLog;
 
 import com.android.internal.annotations.GuardedBy;
+import com.android.internal.messages.nano.SystemMessageProto.SystemMessage;
+import com.android.internal.notification.SystemNotificationChannels;
 import com.android.internal.util.IndentingPrintWriter;
 import com.android.internal.util.dump.DualDumpOutputStream;
 import com.android.server.FgThread;
 
 import java.util.ArrayList;
-import java.util.HashMap;
 import java.util.NoSuchElementException;
 
 /**
@@ -85,6 +93,7 @@
     private static final String TAG = "UsbPortManager";
 
     private static final int MSG_UPDATE_PORTS = 1;
+    private static final int MSG_SYSTEM_READY = 2;
 
     // All non-trivial role combinations.
     private static final int COMBO_SOURCE_HOST =
@@ -132,7 +141,19 @@
 
     // Maintains the current connected status of the port.
     // Uploads logs only when the connection status is changes.
-    private final HashMap<String, Boolean> mConnected = new HashMap<>();
+    private final ArrayMap<String, Boolean> mConnected = new ArrayMap<>();
+
+    // Maintains the USB contaminant status that was previously logged.
+    // Logs get uploaded only when contaminant presence status changes.
+    private final ArrayMap<String, Integer> mContaminantStatus = new ArrayMap<>();
+
+    private NotificationManager mNotificationManager;
+
+    /**
+     * If there currently is a notification about contaminated USB port shown the id of the
+     * notification, or 0 if there is none.
+     */
+    private int mIsPortContaminatedNotificationId;
 
     public UsbPortManager(Context context) {
         mContext = context;
@@ -164,6 +185,90 @@
                         "ServiceStart: Failed to query port status", e);
             }
         }
+        mHandler.sendEmptyMessage(MSG_SYSTEM_READY);
+    }
+
+    private void updateContaminantNotification() {
+        PortInfo currentPortInfo = null;
+        Resources r = mContext.getResources();
+
+        // Not handling multiple ports here. Showing the notification
+        // for the first port that returns CONTAMINANT_PRESENCE_DETECTED.
+        for (PortInfo portInfo : mPorts.values()) {
+            if (portInfo.mUsbPortStatus.getContaminantDetectionStatus()
+                    == UsbPortStatus.CONTAMINANT_DETECTION_DETECTED) {
+                currentPortInfo = portInfo;
+                break;
+            }
+        }
+
+        if (currentPortInfo != null && mIsPortContaminatedNotificationId
+                    != SystemMessage.NOTE_USB_CONTAMINANT_DETECTED) {
+            if (mIsPortContaminatedNotificationId
+                    == SystemMessage.NOTE_USB_CONTAMINANT_NOT_DETECTED) {
+                mNotificationManager.cancelAsUser(null, mIsPortContaminatedNotificationId,
+                        UserHandle.ALL);
+            }
+
+            mIsPortContaminatedNotificationId = SystemMessage.NOTE_USB_CONTAMINANT_DETECTED;
+            int titleRes = com.android.internal.R.string.usb_contaminant_detected_title;
+            CharSequence title = r.getText(titleRes);
+            String channel = SystemNotificationChannels.ALERTS;
+            CharSequence message = r.getText(
+                    com.android.internal.R.string.usb_contaminant_detected_message);
+
+            Intent intent = new Intent();
+            intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
+            intent.setClassName("com.android.systemui",
+                    "com.android.systemui.usb.UsbContaminantActivity");
+            intent.putExtra(UsbManager.EXTRA_PORT, ParcelableUsbPort.of(currentPortInfo.mUsbPort));
+
+            PendingIntent pi = PendingIntent.getActivityAsUser(mContext, 0,
+                                intent, 0, null, UserHandle.CURRENT);
+
+            Notification.Builder builder = new Notification.Builder(mContext, channel)
+                    .setOngoing(true)
+                    .setTicker(title)
+                    .setColor(mContext.getColor(
+                           com.android.internal.R.color
+                           .system_notification_accent_color))
+                    .setContentIntent(pi)
+                    .setContentTitle(title)
+                    .setContentText(message)
+                    .setVisibility(Notification.VISIBILITY_PUBLIC)
+                    .setSmallIcon(android.R.drawable.stat_sys_warning)
+                    .setStyle(new Notification.BigTextStyle()
+                    .bigText(message));
+            Notification notification = builder.build();
+            mNotificationManager.notifyAsUser(null, mIsPortContaminatedNotificationId, notification,
+                    UserHandle.ALL);
+        } else if (currentPortInfo == null && mIsPortContaminatedNotificationId
+                == SystemMessage.NOTE_USB_CONTAMINANT_DETECTED) {
+            mNotificationManager.cancelAsUser(null, mIsPortContaminatedNotificationId,
+                    UserHandle.ALL);
+
+            mIsPortContaminatedNotificationId = SystemMessage.NOTE_USB_CONTAMINANT_NOT_DETECTED;
+            int titleRes = com.android.internal.R.string.usb_contaminant_not_detected_title;
+            CharSequence title = r.getText(titleRes);
+            String channel = SystemNotificationChannels.ALERTS;
+            CharSequence message = r.getText(
+                    com.android.internal.R.string.usb_contaminant_not_detected_message);
+
+            Notification.Builder builder = new Notification.Builder(mContext, channel)
+                    .setSmallIcon(com.android.internal.R.drawable.ic_usb_48dp)
+                    .setTicker(title)
+                    .setColor(mContext.getColor(
+                           com.android.internal.R.color
+                           .system_notification_accent_color))
+                    .setContentTitle(title)
+                    .setContentText(message)
+                    .setVisibility(Notification.VISIBILITY_PUBLIC)
+                    .setStyle(new Notification.BigTextStyle()
+                    .bigText(message));
+            Notification notification = builder.build();
+            mNotificationManager.notifyAsUser(null, mIsPortContaminatedNotificationId, notification,
+                    UserHandle.ALL);
+        }
     }
 
     public UsbPort[] getPorts() {
@@ -184,6 +289,43 @@
         }
     }
 
+    /**
+     * Enables/disables contaminant detection.
+     *
+     * @param portId port identifier.
+     * @param enable enable contaminant detection when set to true.
+     */
+    public void enableContaminantDetection(@NonNull String portId, boolean enable,
+            @NonNull IndentingPrintWriter pw) {
+        final PortInfo portInfo = mPorts.get(portId);
+        if (portInfo == null) {
+            if (pw != null) {
+                pw.println("No such USB port: " + portId);
+            }
+            return;
+        }
+
+        if (!portInfo.mUsbPort.supportsEnableContaminantPresenceDetection()) {
+            return;
+        }
+
+        if ((enable && portInfo.mUsbPortStatus.getContaminantDetectionStatus()
+                != UsbPortStatus.CONTAMINANT_DETECTION_DISABLED) || (!enable
+                && portInfo.mUsbPortStatus.getContaminantDetectionStatus()
+                == UsbPortStatus.CONTAMINANT_DETECTION_DISABLED)
+                || (portInfo.mUsbPortStatus.getContaminantDetectionStatus()
+                == UsbPortStatus.CONTAMINANT_DETECTION_NOT_SUPPORTED)) {
+            return;
+        }
+
+        try {
+            // Oneway call into the hal
+            mProxy.enableContaminantPresenceDetection(portId, enable);
+        } catch (RemoteException e) {
+            logAndPrintException(null, "Failed to set contaminant detection", e);
+        }
+    }
+
     public void setPortRoles(String portId, int newPowerRole, int newDataRole,
             IndentingPrintWriter pw) {
         synchronized (mLock) {
@@ -371,6 +513,27 @@
         }
     }
 
+    /**
+     * Sets contaminant status for simulated USB port objects.
+     */
+    public void simulateContaminantStatus(String portId, boolean detected,
+            IndentingPrintWriter pw) {
+        synchronized (mLock) {
+            final RawPortInfo portInfo = mSimulatedPorts.get(portId);
+            if (portInfo == null) {
+                pw.println("Simulated port not found.");
+                return;
+            }
+
+            pw.println("Simulating wet port: portId=" + portId
+                    + ", wet=" + detected);
+            portInfo.contaminantDetectionStatus = detected
+                    ? UsbPortStatus.CONTAMINANT_DETECTION_DETECTED
+                    : UsbPortStatus.CONTAMINANT_DETECTION_NOT_DETECTED;
+            updatePortsLocked(pw, null);
+        }
+    }
+
     public void disconnectSimulatedPort(String portId, IndentingPrintWriter pw) {
         synchronized (mLock) {
             final RawPortInfo portInfo = mSimulatedPorts.get(portId);
@@ -441,7 +604,8 @@
             this.portManager = portManager;
         }
 
-        public void notifyPortStatusChange(ArrayList<PortStatus> currentPortStatus, int retval) {
+        public void notifyPortStatusChange(
+                ArrayList<android.hardware.usb.V1_0.PortStatus> currentPortStatus, int retval) {
             if (!portManager.mSystemReady) {
                 return;
             }
@@ -453,14 +617,17 @@
 
             ArrayList<RawPortInfo> newPortInfo = new ArrayList<>();
 
-            for (PortStatus current : currentPortStatus) {
+            for (android.hardware.usb.V1_0.PortStatus current : currentPortStatus) {
                 RawPortInfo temp = new RawPortInfo(current.portName,
-                        current.supportedModes, current.currentMode,
+                        current.supportedModes, CONTAMINANT_PROTECTION_NONE,
+                        current.currentMode,
                         current.canChangeMode, current.currentPowerRole,
                         current.canChangePowerRole,
-                        current.currentDataRole, current.canChangeDataRole);
+                        current.currentDataRole, current.canChangeDataRole,
+                        false, CONTAMINANT_PROTECTION_NONE,
+                        false, CONTAMINANT_DETECTION_NOT_SUPPORTED);
                 newPortInfo.add(temp);
-                logAndPrint(Log.INFO, pw, "ClientCallback: " + current.portName);
+                logAndPrint(Log.INFO, pw, "ClientCallback V1_0: " + current.portName);
             }
 
             Message message = portManager.mHandler.obtainMessage();
@@ -485,14 +652,61 @@
 
             ArrayList<RawPortInfo> newPortInfo = new ArrayList<>();
 
-            for (PortStatus_1_1 current : currentPortStatus) {
+            int numStatus = currentPortStatus.size();
+            for (int i = 0; i < numStatus; i++) {
+                PortStatus_1_1 current = currentPortStatus.get(i);
                 RawPortInfo temp = new RawPortInfo(current.status.portName,
-                        current.supportedModes, current.currentMode,
+                        current.supportedModes, CONTAMINANT_PROTECTION_NONE,
+                        current.currentMode,
                         current.status.canChangeMode, current.status.currentPowerRole,
                         current.status.canChangePowerRole,
-                        current.status.currentDataRole, current.status.canChangeDataRole);
+                        current.status.currentDataRole, current.status.canChangeDataRole,
+                        false, CONTAMINANT_PROTECTION_NONE,
+                        false, CONTAMINANT_DETECTION_NOT_SUPPORTED);
                 newPortInfo.add(temp);
-                logAndPrint(Log.INFO, pw, "ClientCallback: " + current.status.portName);
+                logAndPrint(Log.INFO, pw, "ClientCallback V1_1: " + current.status.portName);
+            }
+
+            Message message = portManager.mHandler.obtainMessage();
+            Bundle bundle = new Bundle();
+            bundle.putParcelableArrayList(PORT_INFO, newPortInfo);
+            message.what = MSG_UPDATE_PORTS;
+            message.setData(bundle);
+            portManager.mHandler.sendMessage(message);
+        }
+
+        public void notifyPortStatusChange_1_2(
+                ArrayList<PortStatus> currentPortStatus, int retval) {
+            if (!portManager.mSystemReady) {
+                return;
+            }
+
+            if (retval != Status.SUCCESS) {
+                logAndPrint(Log.ERROR, pw, "port status enquiry failed");
+                return;
+            }
+
+            ArrayList<RawPortInfo> newPortInfo = new ArrayList<>();
+
+            int numStatus = currentPortStatus.size();
+            for (int i = 0; i < numStatus; i++) {
+                PortStatus current = currentPortStatus.get(i);
+                RawPortInfo temp = new RawPortInfo(current.status_1_1.status.portName,
+                        current.status_1_1.supportedModes,
+                        current.supportedContaminantProtectionModes,
+                        current.status_1_1.currentMode,
+                        current.status_1_1.status.canChangeMode,
+                        current.status_1_1.status.currentPowerRole,
+                        current.status_1_1.status.canChangePowerRole,
+                        current.status_1_1.status.currentDataRole,
+                        current.status_1_1.status.canChangeDataRole,
+                        current.supportsEnableContaminantPresenceProtection,
+                        current.contaminantProtectionStatus,
+                        current.supportsEnableContaminantPresenceDetection,
+                        current.contaminantDetectionStatus);
+                newPortInfo.add(temp);
+                logAndPrint(Log.INFO, pw, "ClientCallback V1_2: "
+                        + current.status_1_1.status.portName);
             }
 
             Message message = portManager.mHandler.obtainMessage();
@@ -573,16 +787,26 @@
             for (int i = 0; i < count; i++) {
                 final RawPortInfo portInfo = mSimulatedPorts.valueAt(i);
                 addOrUpdatePortLocked(portInfo.portId, portInfo.supportedModes,
+                        portInfo.supportedContaminantProtectionModes,
                         portInfo.currentMode, portInfo.canChangeMode,
                         portInfo.currentPowerRole, portInfo.canChangePowerRole,
-                        portInfo.currentDataRole, portInfo.canChangeDataRole, pw);
+                        portInfo.currentDataRole, portInfo.canChangeDataRole,
+                        portInfo.supportsEnableContaminantPresenceProtection,
+                        portInfo.contaminantProtectionStatus,
+                        portInfo.supportsEnableContaminantPresenceDetection,
+                        portInfo.contaminantDetectionStatus, pw);
             }
         } else {
             for (RawPortInfo currentPortInfo : newPortInfo) {
                 addOrUpdatePortLocked(currentPortInfo.portId, currentPortInfo.supportedModes,
+                        currentPortInfo.supportedContaminantProtectionModes,
                         currentPortInfo.currentMode, currentPortInfo.canChangeMode,
                         currentPortInfo.currentPowerRole, currentPortInfo.canChangePowerRole,
-                        currentPortInfo.currentDataRole, currentPortInfo.canChangeDataRole, pw);
+                        currentPortInfo.currentDataRole, currentPortInfo.canChangeDataRole,
+                        currentPortInfo.supportsEnableContaminantPresenceProtection,
+                        currentPortInfo.contaminantProtectionStatus,
+                        currentPortInfo.supportsEnableContaminantPresenceDetection,
+                        currentPortInfo.contaminantDetectionStatus, pw);
             }
         }
 
@@ -608,12 +832,16 @@
         }
     }
 
-
     // Must only be called by updatePortsLocked.
     private void addOrUpdatePortLocked(String portId, int supportedModes,
+            int supportedContaminantProtectionModes,
             int currentMode, boolean canChangeMode,
             int currentPowerRole, boolean canChangePowerRole,
             int currentDataRole, boolean canChangeDataRole,
+            boolean supportsEnableContaminantPresenceProtection,
+            int contaminantProtectionStatus,
+            boolean supportsEnableContaminantPresenceDetection,
+            int contaminantDetectionStatus,
             IndentingPrintWriter pw) {
         // Only allow mode switch capability for dual role ports.
         // Validate that the current mode matches the supported modes we expect.
@@ -664,12 +892,15 @@
         // Update the port data structures.
         PortInfo portInfo = mPorts.get(portId);
         if (portInfo == null) {
-            portInfo = new PortInfo(mContext.getSystemService(UsbManager.class), portId,
-                    supportedModes);
+            portInfo = new PortInfo(mContext.getSystemService(UsbManager.class),
+                portId, supportedModes, supportedContaminantProtectionModes,
+                supportsEnableContaminantPresenceProtection,
+                supportsEnableContaminantPresenceDetection);
             portInfo.setStatus(currentMode, canChangeMode,
                     currentPowerRole, canChangePowerRole,
                     currentDataRole, canChangeDataRole,
-                    supportedRoleCombinations);
+                    supportedRoleCombinations, contaminantProtectionStatus,
+                    contaminantDetectionStatus);
             mPorts.put(portId, portInfo);
         } else {
             // Sanity check that ports aren't changing definition out from under us.
@@ -681,10 +912,32 @@
                         + ", current=" + UsbPort.modeToString(supportedModes));
             }
 
+            if (supportsEnableContaminantPresenceProtection
+                    != portInfo.mUsbPort.supportsEnableContaminantPresenceProtection()) {
+                logAndPrint(Log.WARN, pw,
+                        "Ignoring inconsistent supportsEnableContaminantPresenceProtection"
+                        + "USB port driver (should be immutable): "
+                        + "previous="
+                        + portInfo.mUsbPort.supportsEnableContaminantPresenceProtection()
+                        + ", current=" + supportsEnableContaminantPresenceProtection);
+            }
+
+            if (supportsEnableContaminantPresenceDetection
+                    != portInfo.mUsbPort.supportsEnableContaminantPresenceDetection()) {
+                logAndPrint(Log.WARN, pw,
+                        "Ignoring inconsistent supportsEnableContaminantPresenceDetection "
+                        + "USB port driver (should be immutable): "
+                        + "previous="
+                        + portInfo.mUsbPort.supportsEnableContaminantPresenceDetection()
+                        + ", current=" + supportsEnableContaminantPresenceDetection);
+            }
+
+
             if (portInfo.setStatus(currentMode, canChangeMode,
                     currentPowerRole, canChangePowerRole,
                     currentDataRole, canChangeDataRole,
-                    supportedRoleCombinations)) {
+                    supportedRoleCombinations, contaminantProtectionStatus,
+                    contaminantDetectionStatus)) {
                 portInfo.mDisposition = PortInfo.DISPOSITION_CHANGED;
             } else {
                 portInfo.mDisposition = PortInfo.DISPOSITION_READY;
@@ -695,16 +948,37 @@
     private void handlePortAddedLocked(PortInfo portInfo, IndentingPrintWriter pw) {
         logAndPrint(Log.INFO, pw, "USB port added: " + portInfo);
         sendPortChangedBroadcastLocked(portInfo);
+        updateContaminantNotification();
     }
 
     private void handlePortChangedLocked(PortInfo portInfo, IndentingPrintWriter pw) {
         logAndPrint(Log.INFO, pw, "USB port changed: " + portInfo);
         sendPortChangedBroadcastLocked(portInfo);
+        updateContaminantNotification();
     }
 
     private void handlePortRemovedLocked(PortInfo portInfo, IndentingPrintWriter pw) {
         logAndPrint(Log.INFO, pw, "USB port removed: " + portInfo);
         sendPortChangedBroadcastLocked(portInfo);
+        updateContaminantNotification();
+    }
+
+    // Constants have to be converted between USB HAL V1.2 ContaminantDetectionStatus
+    // to usb.proto as proto guidelines recommends 0 to be UNKNOWN/UNSUPPORTTED
+    // whereas HAL policy is against a loosely defined constant.
+    private static int convertContaminantDetectionStatusToProto(int contaminantDetectionStatus) {
+        switch (contaminantDetectionStatus) {
+            case UsbPortStatus.CONTAMINANT_DETECTION_NOT_SUPPORTED:
+                return UsbServiceProto.CONTAMINANT_STATUS_NOT_SUPPORTED;
+            case UsbPortStatus.CONTAMINANT_DETECTION_DISABLED:
+                return UsbServiceProto.CONTAMINANT_STATUS_DISABLED;
+            case UsbPortStatus.CONTAMINANT_DETECTION_NOT_DETECTED:
+                return UsbServiceProto.CONTAMINANT_STATUS_NOT_DETECTED;
+            case UsbPortStatus.CONTAMINANT_DETECTION_DETECTED:
+                return UsbServiceProto.CONTAMINANT_STATUS_DETECTED;
+            default:
+                return UsbServiceProto.CONTAMINANT_STATUS_UNKNOWN;
+        }
     }
 
     private void sendPortChangedBroadcastLocked(PortInfo portInfo) {
@@ -721,6 +995,33 @@
                 Manifest.permission.MANAGE_USB));
 
         // Log to statsd
+
+        // Port is removed
+        if (portInfo.mUsbPortStatus == null) {
+            if (mConnected.containsKey(portInfo.mUsbPort.getId())) {
+                //Previous logged a connected. Set it to disconnected.
+                if (mConnected.get(portInfo.mUsbPort.getId())) {
+                    StatsLog.write(StatsLog.USB_CONNECTOR_STATE_CHANGED,
+                            StatsLog.USB_CONNECTOR_STATE_CHANGED__STATE__STATE_DISCONNECTED,
+                            portInfo.mUsbPort.getId(), portInfo.mLastConnectDurationMillis);
+                }
+                mConnected.remove(portInfo.mUsbPort.getId());
+            }
+
+            if (mContaminantStatus.containsKey(portInfo.mUsbPort.getId())) {
+                //Previous logged a contaminant detected. Set it to not detected.
+                if ((mContaminantStatus.get(portInfo.mUsbPort.getId())
+                        == UsbPortStatus.CONTAMINANT_DETECTION_DETECTED)) {
+                    StatsLog.write(StatsLog.USB_CONTAMINANT_REPORTED,
+                            portInfo.mUsbPort.getId(),
+                            convertContaminantDetectionStatusToProto(
+                                    UsbPortStatus.CONTAMINANT_DETECTION_NOT_DETECTED));
+                }
+                mContaminantStatus.remove(portInfo.mUsbPort.getId());
+            }
+            return;
+        }
+
         if (!mConnected.containsKey(portInfo.mUsbPort.getId())
                 || (mConnected.get(portInfo.mUsbPort.getId())
                 != portInfo.mUsbPortStatus.isConnected())) {
@@ -731,6 +1032,17 @@
                     StatsLog.USB_CONNECTOR_STATE_CHANGED__STATE__STATE_DISCONNECTED,
                     portInfo.mUsbPort.getId(), portInfo.mLastConnectDurationMillis);
         }
+
+        if (!mContaminantStatus.containsKey(portInfo.mUsbPort.getId())
+                || (mContaminantStatus.get(portInfo.mUsbPort.getId())
+                != portInfo.mUsbPortStatus.getContaminantDetectionStatus())) {
+            mContaminantStatus.put(portInfo.mUsbPort.getId(),
+                    portInfo.mUsbPortStatus.getContaminantDetectionStatus());
+            StatsLog.write(StatsLog.USB_CONTAMINANT_REPORTED,
+                    portInfo.mUsbPort.getId(),
+                    convertContaminantDetectionStatusToProto(
+                            portInfo.mUsbPortStatus.getContaminantDetectionStatus()));
+        }
     }
 
     private static void logAndPrint(int priority, IndentingPrintWriter pw, String msg) {
@@ -759,6 +1071,11 @@
                     }
                     break;
                 }
+                case MSG_SYSTEM_READY: {
+                    mNotificationManager = (NotificationManager)
+                            mContext.getSystemService(Context.NOTIFICATION_SERVICE);
+                    break;
+                }
             }
         }
     };
@@ -784,8 +1101,14 @@
         // 0 when port is connected. Else reports the last connected duration
         public long mLastConnectDurationMillis;
 
-        PortInfo(@NonNull UsbManager usbManager, @NonNull String portId, int supportedModes) {
-            mUsbPort = new UsbPort(usbManager, portId, supportedModes);
+        PortInfo(@NonNull UsbManager usbManager, @NonNull String portId, int supportedModes,
+                int supportedContaminantProtectionModes,
+                boolean supportsEnableContaminantPresenceDetection,
+                boolean supportsEnableContaminantPresenceProtection) {
+            mUsbPort = new UsbPort(usbManager, portId, supportedModes,
+                    supportedContaminantProtectionModes,
+                    supportsEnableContaminantPresenceDetection,
+                    supportsEnableContaminantPresenceProtection);
         }
 
         public boolean setStatus(int currentMode, boolean canChangeMode,
@@ -804,7 +1127,45 @@
                     || mUsbPortStatus.getSupportedRoleCombinations()
                     != supportedRoleCombinations) {
                 mUsbPortStatus = new UsbPortStatus(currentMode, currentPowerRole, currentDataRole,
-                        supportedRoleCombinations);
+                        supportedRoleCombinations, UsbPortStatus.CONTAMINANT_PROTECTION_NONE,
+                        UsbPortStatus.CONTAMINANT_DETECTION_NOT_SUPPORTED);
+                dispositionChanged = true;
+            }
+
+            if (mUsbPortStatus.isConnected() && mConnectedAtMillis == 0) {
+                mConnectedAtMillis = SystemClock.elapsedRealtime();
+                mLastConnectDurationMillis = 0;
+            } else if (!mUsbPortStatus.isConnected() && mConnectedAtMillis != 0) {
+                mLastConnectDurationMillis = SystemClock.elapsedRealtime() - mConnectedAtMillis;
+                mConnectedAtMillis = 0;
+            }
+
+            return dispositionChanged;
+        }
+
+        public boolean setStatus(int currentMode, boolean canChangeMode,
+                int currentPowerRole, boolean canChangePowerRole,
+                int currentDataRole, boolean canChangeDataRole,
+                int supportedRoleCombinations, int contaminantProtectionStatus,
+                int contaminantDetectionStatus) {
+            boolean dispositionChanged = false;
+
+            mCanChangeMode = canChangeMode;
+            mCanChangePowerRole = canChangePowerRole;
+            mCanChangeDataRole = canChangeDataRole;
+            if (mUsbPortStatus == null
+                    || mUsbPortStatus.getCurrentMode() != currentMode
+                    || mUsbPortStatus.getCurrentPowerRole() != currentPowerRole
+                    || mUsbPortStatus.getCurrentDataRole() != currentDataRole
+                    || mUsbPortStatus.getSupportedRoleCombinations()
+                    != supportedRoleCombinations
+                    || mUsbPortStatus.getContaminantProtectionStatus()
+                    != contaminantProtectionStatus
+                    || mUsbPortStatus.getContaminantDetectionStatus()
+                    != contaminantDetectionStatus) {
+                mUsbPortStatus = new UsbPortStatus(currentMode, currentPowerRole, currentDataRole,
+                        supportedRoleCombinations, contaminantProtectionStatus,
+                        contaminantDetectionStatus);
                 dispositionChanged = true;
             }
 
@@ -855,32 +1216,54 @@
     private static final class RawPortInfo implements Parcelable {
         public final String portId;
         public final int supportedModes;
+        public final int supportedContaminantProtectionModes;
         public int currentMode;
         public boolean canChangeMode;
         public int currentPowerRole;
         public boolean canChangePowerRole;
         public int currentDataRole;
         public boolean canChangeDataRole;
+        public boolean supportsEnableContaminantPresenceProtection;
+        public int contaminantProtectionStatus;
+        public boolean supportsEnableContaminantPresenceDetection;
+        public int contaminantDetectionStatus;
 
         RawPortInfo(String portId, int supportedModes) {
             this.portId = portId;
             this.supportedModes = supportedModes;
+            this.supportedContaminantProtectionModes = UsbPortStatus.CONTAMINANT_PROTECTION_NONE;
+            this.supportsEnableContaminantPresenceProtection = false;
+            this.contaminantProtectionStatus = UsbPortStatus.CONTAMINANT_PROTECTION_NONE;
+            this.supportsEnableContaminantPresenceDetection = false;
+            this.contaminantDetectionStatus = UsbPortStatus.CONTAMINANT_DETECTION_NOT_SUPPORTED;
         }
 
-        RawPortInfo(String portId, int supportedModes,
+        RawPortInfo(String portId, int supportedModes, int supportedContaminantProtectionModes,
                 int currentMode, boolean canChangeMode,
                 int currentPowerRole, boolean canChangePowerRole,
-                int currentDataRole, boolean canChangeDataRole) {
+                int currentDataRole, boolean canChangeDataRole,
+                boolean supportsEnableContaminantPresenceProtection,
+                int contaminantProtectionStatus,
+                boolean supportsEnableContaminantPresenceDetection,
+                int contaminantDetectionStatus) {
             this.portId = portId;
             this.supportedModes = supportedModes;
+            this.supportedContaminantProtectionModes = supportedContaminantProtectionModes;
             this.currentMode = currentMode;
             this.canChangeMode = canChangeMode;
             this.currentPowerRole = currentPowerRole;
             this.canChangePowerRole = canChangePowerRole;
             this.currentDataRole = currentDataRole;
             this.canChangeDataRole = canChangeDataRole;
+            this.supportsEnableContaminantPresenceProtection =
+                    supportsEnableContaminantPresenceProtection;
+            this.contaminantProtectionStatus = contaminantProtectionStatus;
+            this.supportsEnableContaminantPresenceDetection =
+                    supportsEnableContaminantPresenceDetection;
+            this.contaminantDetectionStatus = contaminantDetectionStatus;
         }
 
+
         @Override
         public int describeContents() {
             return 0;
@@ -890,35 +1273,50 @@
         public void writeToParcel(Parcel dest, int flags) {
             dest.writeString(portId);
             dest.writeInt(supportedModes);
+            dest.writeInt(supportedContaminantProtectionModes);
             dest.writeInt(currentMode);
             dest.writeByte((byte) (canChangeMode ? 1 : 0));
             dest.writeInt(currentPowerRole);
             dest.writeByte((byte) (canChangePowerRole ? 1 : 0));
             dest.writeInt(currentDataRole);
             dest.writeByte((byte) (canChangeDataRole ? 1 : 0));
+            dest.writeBoolean(supportsEnableContaminantPresenceProtection);
+            dest.writeInt(contaminantProtectionStatus);
+            dest.writeBoolean(supportsEnableContaminantPresenceDetection);
+            dest.writeInt(contaminantDetectionStatus);
         }
 
         public static final Parcelable.Creator<RawPortInfo> CREATOR =
                 new Parcelable.Creator<RawPortInfo>() {
-                    @Override
-                    public RawPortInfo createFromParcel(Parcel in) {
-                        String id = in.readString();
-                        int supportedModes = in.readInt();
-                        int currentMode = in.readInt();
-                        boolean canChangeMode = in.readByte() != 0;
-                        int currentPowerRole = in.readInt();
-                        boolean canChangePowerRole = in.readByte() != 0;
-                        int currentDataRole = in.readInt();
-                        boolean canChangeDataRole = in.readByte() != 0;
-                        return new RawPortInfo(id, supportedModes, currentMode, canChangeMode,
-                                currentPowerRole, canChangePowerRole,
-                                currentDataRole, canChangeDataRole);
-                    }
+            @Override
+            public RawPortInfo createFromParcel(Parcel in) {
+                String id = in.readString();
+                int supportedModes = in.readInt();
+                int supportedContaminantProtectionModes = in.readInt();
+                int currentMode = in.readInt();
+                boolean canChangeMode = in.readByte() != 0;
+                int currentPowerRole = in.readInt();
+                boolean canChangePowerRole = in.readByte() != 0;
+                int currentDataRole = in.readInt();
+                boolean canChangeDataRole = in.readByte() != 0;
+                boolean supportsEnableContaminantPresenceProtection = in.readBoolean();
+                int contaminantProtectionStatus = in.readInt();
+                boolean supportsEnableContaminantPresenceDetection = in.readBoolean();
+                int contaminantDetectionStatus = in.readInt();
+                return new RawPortInfo(id, supportedModes,
+                        supportedContaminantProtectionModes, currentMode, canChangeMode,
+                        currentPowerRole, canChangePowerRole,
+                        currentDataRole, canChangeDataRole,
+                        supportsEnableContaminantPresenceProtection,
+                        contaminantProtectionStatus,
+                        supportsEnableContaminantPresenceDetection,
+                        contaminantDetectionStatus);
+            }
 
-                    @Override
-                    public RawPortInfo[] newArray(int size) {
-                        return new RawPortInfo[size];
-                    }
-                };
+            @Override
+            public RawPortInfo[] newArray(int size) {
+                return new RawPortInfo[size];
+            }
+        };
     }
 }
diff --git a/services/usb/java/com/android/server/usb/UsbService.java b/services/usb/java/com/android/server/usb/UsbService.java
index 9115477..4be68b8 100644
--- a/services/usb/java/com/android/server/usb/UsbService.java
+++ b/services/usb/java/com/android/server/usb/UsbService.java
@@ -554,6 +554,21 @@
     }
 
     @Override
+    public void enableContaminantDetection(String portId, boolean enable) {
+        Preconditions.checkNotNull(portId, "portId must not be null");
+        mContext.enforceCallingOrSelfPermission(android.Manifest.permission.MANAGE_USB, null);
+
+        final long ident = Binder.clearCallingIdentity();
+        try {
+            if (mPortManager != null) {
+                mPortManager.enableContaminantDetection(portId, enable, null);
+            }
+        } finally {
+            Binder.restoreCallingIdentity(ident);
+        }
+    }
+
+    @Override
     public void setUsbDeviceConnectionHandler(ComponentName usbDeviceConnectionHandler) {
         mContext.enforceCallingOrSelfPermission(android.Manifest.permission.MANAGE_USB, null);
         synchronized (mLock) {
@@ -747,6 +762,15 @@
                     mPortManager.dump(new DualDumpOutputStream(new IndentingPrintWriter(pw, "  ")),
                             "", 0);
                 }
+            } else if ("set-contaminant-status".equals(args[0]) && args.length == 3) {
+                final String portId = args[1];
+                final Boolean wet = Boolean.parseBoolean(args[2]);
+                if (mPortManager != null) {
+                    mPortManager.simulateContaminantStatus(portId, wet, pw);
+                    pw.println();
+                    mPortManager.dump(new DualDumpOutputStream(new IndentingPrintWriter(pw, "  ")),
+                            "", 0);
+                }
             } else if ("ports".equals(args[0]) && args.length == 1) {
                 if (mPortManager != null) {
                     mPortManager.dump(new DualDumpOutputStream(new IndentingPrintWriter(pw, "  ")),
@@ -791,6 +815,11 @@
                 pw.println("  dumpsys usb connect-port \"matrix\" ufp sink device");
                 pw.println("  dumpsys usb reset");
                 pw.println();
+                pw.println("Example simulate contaminant status:");
+                pw.println("  dumpsys usb add-port \"matrix\" ufp");
+                pw.println("  dumpsys usb set-contaminant-status \"matrix\" true");
+                pw.println("  dumpsys usb set-contaminant-status \"matrix\" false");
+                pw.println();
                 pw.println("Example USB device descriptors:");
                 pw.println("  dumpsys usb dump-descriptors -dump-short");
                 pw.println("  dumpsys usb dump-descriptors -dump-tree");
diff --git a/services/voiceinteraction/java/com/android/server/soundtrigger/SoundTriggerService.java b/services/voiceinteraction/java/com/android/server/soundtrigger/SoundTriggerService.java
index 8c82cc8..697469a 100644
--- a/services/voiceinteraction/java/com/android/server/soundtrigger/SoundTriggerService.java
+++ b/services/voiceinteraction/java/com/android/server/soundtrigger/SoundTriggerService.java
@@ -939,11 +939,7 @@
             runOrAddOperation(new Operation(
                     // always execute:
                     () -> {
-                        // Don't remove the callback if multiple triggers are allowed or
-                        // if this event was triggered by a getModelState request
-                        if (!mRecognitionConfig.allowMultipleTriggers
-                                && event.status
-                                    != SoundTrigger.RECOGNITION_STATUS_GET_STATE_RESPONSE) {
+                        if (!mRecognitionConfig.allowMultipleTriggers) {
                             // Unregister this remoteService once op is done
                             synchronized (mCallbacksLock) {
                                 mCallbacks.remove(mPuuid.getUuid());
diff --git a/services/voiceinteraction/java/com/android/server/voiceinteraction/VoiceInteractionManagerService.java b/services/voiceinteraction/java/com/android/server/voiceinteraction/VoiceInteractionManagerService.java
index 613c4ff..718f2d3 100644
--- a/services/voiceinteraction/java/com/android/server/voiceinteraction/VoiceInteractionManagerService.java
+++ b/services/voiceinteraction/java/com/android/server/voiceinteraction/VoiceInteractionManagerService.java
@@ -17,16 +17,18 @@
 package com.android.server.voiceinteraction;
 
 import android.Manifest;
+import android.annotation.CallbackExecutor;
+import android.annotation.NonNull;
 import android.app.ActivityManager;
 import android.app.ActivityManagerInternal;
-
-import com.android.internal.app.IVoiceActionCheckCallback;
-import com.android.server.wm.ActivityTaskManagerInternal;
 import android.app.AppGlobals;
+import android.app.role.OnRoleHoldersChangedListener;
+import android.app.role.RoleManager;
 import android.content.ComponentName;
 import android.content.ContentResolver;
 import android.content.Context;
 import android.content.Intent;
+import android.content.pm.ActivityInfo;
 import android.content.pm.ApplicationInfo;
 import android.content.pm.IPackageManager;
 import android.content.pm.PackageManager;
@@ -62,8 +64,9 @@
 import android.util.Log;
 import android.util.Slog;
 
-import com.android.internal.app.IVoiceInteractionSessionListener;
+import com.android.internal.app.IVoiceActionCheckCallback;
 import com.android.internal.app.IVoiceInteractionManagerService;
+import com.android.internal.app.IVoiceInteractionSessionListener;
 import com.android.internal.app.IVoiceInteractionSessionShowCallback;
 import com.android.internal.app.IVoiceInteractor;
 import com.android.internal.content.PackageMonitor;
@@ -75,10 +78,12 @@
 import com.android.server.SystemService;
 import com.android.server.UiThread;
 import com.android.server.soundtrigger.SoundTriggerInternal;
+import com.android.server.wm.ActivityTaskManagerInternal;
 
 import java.io.FileDescriptor;
 import java.io.PrintWriter;
 import java.util.List;
+import java.util.concurrent.Executor;
 
 /**
  * SystemService that publishes an IVoiceInteractionManagerService.
@@ -201,6 +206,7 @@
 
         VoiceInteractionManagerServiceStub() {
             mEnableService = shouldEnableService(mContext);
+            new RoleObserver(mContext.getMainExecutor());
         }
 
         // TODO: VI Make sure the caller is the current user or profile
@@ -1205,6 +1211,57 @@
             mSoundTriggerInternal.dump(fd, pw, args);
         }
 
+        @Override
+        public void setTranscription(String transcription) {
+            synchronized (this) {
+                final int size = mVoiceInteractionSessionListeners.beginBroadcast();
+                for (int i = 0; i < size; ++i) {
+                    final IVoiceInteractionSessionListener listener =
+                            mVoiceInteractionSessionListeners.getBroadcastItem(i);
+                    try {
+                        listener.onTranscriptionUpdate(transcription);
+                    } catch (RemoteException e) {
+                        Slog.e(TAG, "Error delivering voice transcription.", e);
+                    }
+                }
+                mVoiceInteractionSessionListeners.finishBroadcast();
+            }
+        }
+
+        @Override
+        public void clearTranscription(boolean immediate) {
+            synchronized (this) {
+                final int size = mVoiceInteractionSessionListeners.beginBroadcast();
+                for (int i = 0; i < size; ++i) {
+                    final IVoiceInteractionSessionListener listener =
+                            mVoiceInteractionSessionListeners.getBroadcastItem(i);
+                    try {
+                        listener.onTranscriptionComplete(immediate);
+                    } catch (RemoteException e) {
+                        Slog.e(TAG, "Error delivering transcription complete event.", e);
+                    }
+                }
+                mVoiceInteractionSessionListeners.finishBroadcast();
+            }
+        }
+
+        @Override
+        public void setVoiceState(int state) {
+            synchronized (this) {
+                final int size = mVoiceInteractionSessionListeners.beginBroadcast();
+                for (int i = 0; i < size; ++i) {
+                    final IVoiceInteractionSessionListener listener =
+                            mVoiceInteractionSessionListeners.getBroadcastItem(i);
+                    try {
+                        listener.onVoiceStateChange(state);
+                    } catch (RemoteException e) {
+                        Slog.e(TAG, "Error delivering voice state change.", e);
+                    }
+                }
+                mVoiceInteractionSessionListeners.finishBroadcast();
+            }
+        }
+
         private void enforceCallingPermission(String permission) {
             if (mContext.checkCallingOrSelfPermission(permission)
                     != PackageManager.PERMISSION_GRANTED) {
@@ -1218,6 +1275,106 @@
                     getActiveServiceComponentName());
         }
 
+        class RoleObserver implements OnRoleHoldersChangedListener {
+            private PackageManager mPm = mContext.getPackageManager();
+            private RoleManager mRm = mContext.getSystemService(RoleManager.class);
+
+            RoleObserver(@NonNull @CallbackExecutor Executor executor) {
+                mRm.addOnRoleHoldersChangedListenerAsUser(executor, this, UserHandle.ALL);
+            }
+
+            private @NonNull String getDefaultRecognizer(@NonNull UserHandle user) {
+                ResolveInfo resolveInfo = mPm.resolveServiceAsUser(
+                        new Intent(RecognitionService.SERVICE_INTERFACE),
+                        PackageManager.GET_META_DATA, user.getIdentifier());
+
+                if (resolveInfo == null || resolveInfo.serviceInfo == null) {
+                    Log.w(TAG, "Unable to resolve default voice recognition service.");
+                    return "";
+                }
+
+                return new ComponentName(resolveInfo.serviceInfo.packageName,
+                        resolveInfo.serviceInfo.name).flattenToShortString();
+            }
+
+            /**
+             * Convert the assistant-role holder into settings. The rest of the system uses the
+             * settings.
+             *
+             * @param roleName the name of the role whose holders are changed
+             * @param user the user for this role holder change
+             */
+            @Override
+            public void onRoleHoldersChanged(@NonNull String roleName, @NonNull UserHandle user) {
+                if (!roleName.equals(RoleManager.ROLE_ASSISTANT)) {
+                    return;
+                }
+
+                List<String> roleHolders = mRm.getRoleHoldersAsUser(roleName, user);
+
+                if (roleHolders.isEmpty()) {
+                    Settings.Secure.putString(getContext().getContentResolver(),
+                            Settings.Secure.ASSISTANT, "");
+                    Settings.Secure.putString(getContext().getContentResolver(),
+                            Settings.Secure.VOICE_INTERACTION_SERVICE, "");
+                    Settings.Secure.putString(getContext().getContentResolver(),
+                            Settings.Secure.VOICE_RECOGNITION_SERVICE, getDefaultRecognizer(user));
+                } else {
+                    // Assistant is singleton role
+                    String pkg = roleHolders.get(0);
+
+                    // Try to set role holder as VoiceInteractionService
+                    List<ResolveInfo> services = mPm.queryIntentServicesAsUser(
+                            new Intent(VoiceInteractionService.SERVICE_INTERFACE).setPackage(pkg),
+                            PackageManager.GET_META_DATA, user.getIdentifier());
+
+                    for (ResolveInfo resolveInfo : services) {
+                        ServiceInfo serviceInfo = resolveInfo.serviceInfo;
+
+                        VoiceInteractionServiceInfo voiceInteractionServiceInfo =
+                                new VoiceInteractionServiceInfo(mPm, serviceInfo);
+                        if (!voiceInteractionServiceInfo.getSupportsAssist()) {
+                            continue;
+                        }
+
+                        String serviceComponentName = serviceInfo.getComponentName()
+                                .flattenToShortString();
+
+                        String serviceRecognizerName = new ComponentName(pkg,
+                                voiceInteractionServiceInfo.getRecognitionService())
+                                .flattenToShortString();
+
+                        Settings.Secure.putString(getContext().getContentResolver(),
+                                Settings.Secure.ASSISTANT, serviceComponentName);
+                        Settings.Secure.putString(getContext().getContentResolver(),
+                                Settings.Secure.VOICE_INTERACTION_SERVICE, serviceComponentName);
+                        Settings.Secure.putString(getContext().getContentResolver(),
+                                Settings.Secure.VOICE_RECOGNITION_SERVICE, serviceRecognizerName);
+
+                        return;
+                    }
+
+                    // If no service could be found try to set assist activity
+                    final List<ResolveInfo> activities = mPm.queryIntentActivitiesAsUser(
+                            new Intent(Intent.ACTION_ASSIST).setPackage(pkg),
+                            PackageManager.MATCH_DEFAULT_ONLY, user.getIdentifier());
+
+                    for (ResolveInfo resolveInfo : activities) {
+                        ActivityInfo activityInfo = resolveInfo.activityInfo;
+
+                        Settings.Secure.putString(getContext().getContentResolver(),
+                                Settings.Secure.ASSISTANT,
+                                activityInfo.getComponentName().flattenToShortString());
+                        Settings.Secure.putString(getContext().getContentResolver(),
+                                Settings.Secure.VOICE_INTERACTION_SERVICE, "");
+                        Settings.Secure.putString(getContext().getContentResolver(),
+                                Settings.Secure.VOICE_RECOGNITION_SERVICE,
+                                getDefaultRecognizer(user));
+                    }
+                }
+            }
+        }
+
         class SettingsObserver extends ContentObserver {
             SettingsObserver(Handler handler) {
                 super(handler);
diff --git a/startop/OWNERS b/startop/OWNERS
index 762cd8e..5cf9582 100644
--- a/startop/OWNERS
+++ b/startop/OWNERS
@@ -2,5 +2,5 @@
 chriswailes@google.com
 eholk@google.com
 iam@google.com
-sehr@google.com
 mathieuc@google.com
+sehr@google.com
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/telecomm/java/android/telecom/CallScreeningService.java b/telecomm/java/android/telecom/CallScreeningService.java
index 826ad82..818ebd9 100644
--- a/telecomm/java/android/telecom/CallScreeningService.java
+++ b/telecomm/java/android/telecom/CallScreeningService.java
@@ -16,6 +16,7 @@
 
 package android.telecom;
 
+import android.annotation.IntDef;
 import android.annotation.NonNull;
 import android.annotation.SdkConstant;
 import android.app.Service;
@@ -32,6 +33,9 @@
 import com.android.internal.telecom.ICallScreeningAdapter;
 import com.android.internal.telecom.ICallScreeningService;
 
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+
 /**
  * This service can be implemented by the default dialer (see
  * {@link TelecomManager#getDefaultDialerPackage()}) or a third party app to allow or disallow
@@ -88,6 +92,128 @@
  * </pre>
  */
 public abstract class CallScreeningService extends Service {
+
+    /** @hide */
+    @Retention(RetentionPolicy.SOURCE)
+    @IntDef(
+            prefix = { "CALL_DURATION_" },
+            value = {CALL_DURATION_VERY_SHORT, CALL_DURATION_SHORT, CALL_DURATION_MEDIUM,
+                    CALL_DURATION_LONG})
+    public @interface CallDuration {}
+
+    /**
+     * Call duration reported with {@link #EXTRA_CALL_DURATION} to indicate to the
+     * {@link CallScreeningService} the duration of a call for which the user reported the nuisance
+     * status (see {@link TelecomManager#reportNuisanceCallStatus(Uri, boolean)}).  The
+     * {@link CallScreeningService} can use this as a signal for training nuisance detection
+     * algorithms.  The call duration is reported in coarse grained buckets to minimize exposure of
+     * identifying call log information to the {@link CallScreeningService}.
+     * <p>
+     * Indicates the call was < 3 seconds in duration.
+     */
+    public static final int CALL_DURATION_VERY_SHORT = 1;
+
+    /**
+     * Call duration reported with {@link #EXTRA_CALL_DURATION} to indicate to the
+     * {@link CallScreeningService} the duration of a call for which the user reported the nuisance
+     * status (see {@link TelecomManager#reportNuisanceCallStatus(Uri, boolean)}).  The
+     * {@link CallScreeningService} can use this as a signal for training nuisance detection
+     * algorithms.  The call duration is reported in coarse grained buckets to minimize exposure of
+     * identifying call log information to the {@link CallScreeningService}.
+     * <p>
+     * Indicates the call was greater than 3 seconds, but less than 60 seconds in duration.
+     */
+    public static final int CALL_DURATION_SHORT = 2;
+
+    /**
+     * Call duration reported with {@link #EXTRA_CALL_DURATION} to indicate to the
+     * {@link CallScreeningService} the duration of a call for which the user reported the nuisance
+     * status (see {@link TelecomManager#reportNuisanceCallStatus(Uri, boolean)}).  The
+     * {@link CallScreeningService} can use this as a signal for training nuisance detection
+     * algorithms.  The call duration is reported in coarse grained buckets to minimize exposure of
+     * identifying call log information to the {@link CallScreeningService}.
+     * <p>
+     * Indicates the call was greater than 60 seconds, but less than 120 seconds in duration.
+     */
+    public static final int CALL_DURATION_MEDIUM = 3;
+
+    /**
+     * Call duration reported with {@link #EXTRA_CALL_DURATION} to indicate to the
+     * {@link CallScreeningService} the duration of a call for which the user reported the nuisance
+     * status (see {@link TelecomManager#reportNuisanceCallStatus(Uri, boolean)}).  The
+     * {@link CallScreeningService} can use this as a signal for training nuisance detection
+     * algorithms.  The call duration is reported in coarse grained buckets to minimize exposure of
+     * identifying call log information to the {@link CallScreeningService}.
+     * <p>
+     * Indicates the call was greater than 120 seconds.
+     */
+    public static final int CALL_DURATION_LONG = 4;
+
+    /**
+     * Telecom sends this intent to the {@link CallScreeningService} which the user has chosen to
+     * fill the call screening role when the user indicates through the default dialer whether a
+     * call is a nuisance call or not (see
+     * {@link TelecomManager#reportNuisanceCallStatus(Uri, boolean)}).
+     * <p>
+     * The following extra values are provided for the call:
+     * <ol>
+     *     <li>{@link #EXTRA_CALL_HANDLE} - the handle of the call.</li>
+     *     <li>{@link #EXTRA_IS_NUISANCE} - {@code true} if the user reported the call as a nuisance
+     *     call, {@code false} otherwise.</li>
+     *     <li>{@link #EXTRA_CALL_TYPE} - reports the type of call (incoming, rejected, missed,
+     *     blocked).</li>
+     *     <li>{@link #EXTRA_CALL_DURATION} - the duration of the call (see
+     *     {@link #EXTRA_CALL_DURATION} for valid values).</li>
+     * </ol>
+     * <p>
+     * {@link CallScreeningService} implementations which want to track whether the user reports
+     * calls are nuisance calls should use declare a broadcast receiver in their manifest for this
+     * intent.
+     * <p>
+     * Note: Only {@link CallScreeningService} implementations which have provided
+     * {@link CallIdentification} information for calls at some point will receive this intent.
+     */
+    public static final String ACTION_NUISANCE_CALL_STATUS_CHANGED =
+            "android.telecom.action.NUISANCE_CALL_STATUS_CHANGED";
+
+    /**
+     * Extra used to provide the handle of the call for
+     * {@link #ACTION_NUISANCE_CALL_STATUS_CHANGED}.  The call handle is reported as a
+     * {@link Uri}.
+     */
+    public static final String EXTRA_CALL_HANDLE = "android.telecom.extra.CALL_HANDLE";
+
+    /**
+     * Boolean extra used to indicate whether the user reported a call as a nuisance call.
+     * When {@code true}, the user reported that a call was a nuisance call, {@code false}
+     * otherwise.  Sent with {@link #ACTION_NUISANCE_CALL_STATUS_CHANGED}.
+     */
+    public static final String EXTRA_IS_NUISANCE = "android.telecom.extra.IS_NUISANCE";
+
+    /**
+     * Integer extra used with {@link #ACTION_NUISANCE_CALL_STATUS_CHANGED} to report the type of
+     * call. Valid values are:
+     * <UL>
+     *   <li>{@link android.provider.CallLog.Calls#MISSED_TYPE}</li>
+     *   <li>{@link android.provider.CallLog.Calls#INCOMING_TYPE}</li>
+     *   <li>{@link android.provider.CallLog.Calls#BLOCKED_TYPE}</li>
+     *   <li>{@link android.provider.CallLog.Calls#REJECTED_TYPE}</li>
+     * </UL>
+     */
+    public static final String EXTRA_CALL_TYPE = "android.telecom.extra.CALL_TYPE";
+
+    /**
+     * Integer extra used to with {@link #ACTION_NUISANCE_CALL_STATUS_CHANGED} to report how long
+     * the call lasted.  Valid values are:
+     * <UL>
+     *     <LI>{@link #CALL_DURATION_VERY_SHORT}</LI>
+     *     <LI>{@link #CALL_DURATION_SHORT}</LI>
+     *     <LI>{@link #CALL_DURATION_MEDIUM}</LI>
+     *     <LI>{@link #CALL_DURATION_LONG}</LI>
+     * </UL>
+     */
+    public static final String EXTRA_CALL_DURATION = "android.telecom.extra.CALL_DURATION";
+
     /**
      * The {@link Intent} that must be declared as handled by the service.
      */
diff --git a/telecomm/java/android/telecom/TelecomManager.java b/telecomm/java/android/telecom/TelecomManager.java
index 0fe5e08..12a5344 100644
--- a/telecomm/java/android/telecom/TelecomManager.java
+++ b/telecomm/java/android/telecom/TelecomManager.java
@@ -1970,6 +1970,33 @@
     }
 
     /**
+     * Called by the default dialer to report to Telecom when the user has marked a previous
+     * incoming call as a nuisance call or not.
+     * <p>
+     * Where the user has chosen a {@link CallScreeningService} to fill the call screening role,
+     * Telecom will notify that {@link CallScreeningService} of the user's report.
+     * <p>
+     * Requires that the caller is the default dialer app.
+     *
+     * @param handle The phone number of an incoming call which the user is reporting as either a
+     *               nuisance of non-nuisance call.
+     * @param isNuisanceCall {@code true} if the user is reporting the call as a nuisance call,
+     *                       {@code false} if the user is reporting the call as a non-nuisance call.
+     */
+    @RequiresPermission(android.Manifest.permission.READ_PHONE_STATE)
+    public void reportNuisanceCallStatus(@NonNull Uri handle, boolean isNuisanceCall) {
+        ITelecomService service = getTelecomService();
+        if (service != null) {
+            try {
+                service.reportNuisanceCallStatus(handle, isNuisanceCall,
+                        mContext.getOpPackageName());
+            } catch (RemoteException e) {
+                Log.e(TAG, "Error calling ITelecomService#showCallScreen", e);
+            }
+        }
+    }
+
+    /**
      * Handles {@link Intent#ACTION_CALL} intents trampolined from UserCallActivity.
      * @param intent The {@link Intent#ACTION_CALL} intent to handle.
      * @hide
diff --git a/telecomm/java/com/android/internal/telecom/ITelecomService.aidl b/telecomm/java/com/android/internal/telecom/ITelecomService.aidl
index e1d5c17..5030f90 100644
--- a/telecomm/java/com/android/internal/telecom/ITelecomService.aidl
+++ b/telecomm/java/com/android/internal/telecom/ITelecomService.aidl
@@ -285,6 +285,8 @@
      */
     boolean isInEmergencyCall();
 
+    oneway void reportNuisanceCallStatus(in Uri address, boolean isNuisance, String callingPackage);
+
     /**
      * @see TelecomServiceImpl#handleCallIntent
      */
@@ -299,4 +301,5 @@
     void addOrRemoveTestCallCompanionApp(String packageName, boolean isAdded);
 
     void setTestAutoModeApp(String packageName);
+
 }
diff --git a/telephony/java/android/provider/Telephony.java b/telephony/java/android/provider/Telephony.java
index 15abdb7..5482270 100644
--- a/telephony/java/android/provider/Telephony.java
+++ b/telephony/java/android/provider/Telephony.java
@@ -2375,6 +2375,9 @@
 
         /**
          * Contains message parts.
+         *
+         * To avoid issues where applications might cache a part ID, the ID of a deleted part must
+         * not be reused to point at a new part.
          */
         public static final class Part implements BaseColumns {
 
@@ -2386,6 +2389,12 @@
             }
 
             /**
+             * The {@code content://} style URL for this table. Can be appended with a part ID to
+             * address individual parts.
+             */
+            public static final Uri CONTENT_URI = Uri.withAppendedPath(Mms.CONTENT_URI, "part");
+
+            /**
              * The identifier of the message which this part belongs to.
              * <P>Type: INTEGER</P>
              */
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/CarrierConfigManager.java b/telephony/java/android/telephony/CarrierConfigManager.java
index a33b44c..349880d 100644
--- a/telephony/java/android/telephony/CarrierConfigManager.java
+++ b/telephony/java/android/telephony/CarrierConfigManager.java
@@ -608,11 +608,41 @@
     public static final String KEY_CARRIER_PROMOTE_WFC_ON_CALL_FAIL_BOOL =
             "carrier_promote_wfc_on_call_fail_bool";
 
-    /** Flag specifying whether provisioning is required for VOLTE. */
+    /**
+     * Flag specifying whether provisioning is required for VoLTE, Video Telephony, and WiFi
+     * Calling.
+     */
     public static final String KEY_CARRIER_VOLTE_PROVISIONING_REQUIRED_BOOL
             = "carrier_volte_provisioning_required_bool";
 
     /**
+     * Flag indicating whether or not the IMS MmTel UT capability requires carrier provisioning
+     * before it can be set as enabled.
+     *
+     * If true, the UT capability will be set to false for the newly loaded subscription
+     * and will require the carrier provisioning app to set the persistent provisioning result.
+     * If false, the platform will not wait for provisioning status updates for the UT capability
+     * and enable the UT over IMS capability for the subscription when the subscription is loaded.
+     *
+     * The default value for this key is {@code false}.
+     */
+    public static final String KEY_CARRIER_UT_PROVISIONING_REQUIRED_BOOL =
+            "carrier_ut_provisioning_required_bool";
+
+    /**
+     * Flag indicating whether or not the carrier supports Supplementary Services over the UT
+     * interface for this subscription.
+     *
+     * If true, the device will use Supplementary Services over UT when provisioned (see
+     * {@link #KEY_CARRIER_UT_PROVISIONING_REQUIRED_BOOL}). If false, this device will fallback to
+     * circuit switch for supplementary services and will disable this capability for IMS entirely.
+     *
+     * The default value for this key is {@code true}.
+     */
+    public static final String KEY_CARRIER_SUPPORTS_SS_OVER_UT_BOOL =
+            "carrier_supports_ss_over_ut_bool";
+
+    /**
      * Flag specifying if WFC provisioning depends on VoLTE provisioning.
      *
      * {@code false}: default value; honor actual WFC provisioning state.
@@ -2575,6 +2605,8 @@
         sDefaults.putInt(KEY_CARRIER_DEFAULT_WFC_IMS_ROAMING_MODE_INT, 2);
         sDefaults.putBoolean(KEY_CARRIER_FORCE_DISABLE_ETWS_CMAS_TEST_BOOL, false);
         sDefaults.putBoolean(KEY_CARRIER_VOLTE_PROVISIONING_REQUIRED_BOOL, false);
+        sDefaults.putBoolean(KEY_CARRIER_UT_PROVISIONING_REQUIRED_BOOL, false);
+        sDefaults.putBoolean(KEY_CARRIER_SUPPORTS_SS_OVER_UT_BOOL, true);
         sDefaults.putBoolean(KEY_CARRIER_VOLTE_OVERRIDE_WFC_PROVISIONING_BOOL, false);
         sDefaults.putBoolean(KEY_CARRIER_VOLTE_TTY_SUPPORTED_BOOL, true);
         sDefaults.putBoolean(KEY_CARRIER_ALLOW_TURNOFF_IMS_BOOL, true);
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/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 1378bb0..fae7920 100644
--- a/telephony/java/android/telephony/SmsManager.java
+++ b/telephony/java/android/telephony/SmsManager.java
@@ -16,22 +16,32 @@
 
 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;
 import android.annotation.UnsupportedAppUsage;
 import android.app.ActivityThread;
 import android.app.PendingIntent;
+import android.bluetooth.BluetoothAdapter;
+import android.bluetooth.BluetoothDevice;
+import android.bluetooth.BluetoothMapClient;
+import android.bluetooth.BluetoothProfile;
 import android.content.ActivityNotFoundException;
 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;
 import android.os.ServiceManager;
+import android.telecom.PhoneAccount;
 import android.text.TextUtils;
 import android.util.ArrayMap;
 import android.util.Log;
@@ -44,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
@@ -61,6 +72,8 @@
  */
 public final class SmsManager {
     private static final String TAG = "SmsManager";
+    private static final boolean DBG = false;
+
     /**
      * A psuedo-subId that represents the default subId at any given time. The actual subId it
      * represents changes as the default subId is changed.
@@ -339,6 +352,44 @@
             throw new IllegalArgumentException("Invalid message body");
         }
 
+        // A Manager code accessing another manager is *not* acceptable, in Android.
+        // In this particular case, it is unavoidable because of the following:
+        // If the subscription for this SmsManager instance belongs to a remote SIM
+        // then a listener to get BluetoothMapClient proxy needs to be started up.
+        // Doing that is possible only in a foreground thread or as a system user.
+        // i.e., Can't be done in ISms service.
+        // For that reason, SubscriptionManager needs to be accessed here to determine
+        // if the subscription belongs to a remote SIM.
+        // Ideally, there should be another API in ISms to service messages going thru
+        // remote SIM subscriptions (and ISms should be tweaked to be able to access
+        // BluetoothMapClient proxy)
+        Context context = ActivityThread.currentApplication().getApplicationContext();
+        SubscriptionManager manager = (SubscriptionManager) context
+                .getSystemService(Context.TELEPHONY_SUBSCRIPTION_SERVICE);
+        int subId = getSubscriptionId();
+        SubscriptionInfo info = manager.getActiveSubscriptionInfo(subId);
+        if (DBG) {
+            Log.d(TAG, "for subId: " + subId + ", subscription-info: " + info);
+        }
+        if (info == null) {
+            // There is no subscription for the given subId. That can only mean one thing:
+            // the caller is using a SmsManager instance with an obsolete subscription id.
+            // That is most probably because caller didn't invalidate SmsManager instance
+            // for an already deleted subscription id.
+            Log.e(TAG, "subId: " + subId + " for this SmsManager instance is obsolete.");
+            sendErrorInPendingIntent(sentIntent, SmsManager.RESULT_ERROR_NO_SERVICE);
+        }
+
+        /* If the Subscription associated with this SmsManager instance belongs to a remote-sim,
+         * then send the message thru the remote-sim subscription.
+         */
+        if (info.getSubscriptionType() == SubscriptionManager.SUBSCRIPTION_TYPE_REMOTE_SIM) {
+            if (DBG) Log.d(TAG, "sending message thru bluetooth");
+            sendTextMessageBluetooth(destinationAddress, scAddress, text, sentIntent,
+                    deliveryIntent, info);
+            return;
+        }
+
         try {
             ISms iccISms = getISmsServiceOrThrow();
             iccISms.sendTextForSubscriber(getSubscriptionId(), ActivityThread.currentPackageName(),
@@ -350,6 +401,79 @@
         }
     }
 
+    private void sendTextMessageBluetooth(String destAddr, String scAddress,
+            String text, PendingIntent sentIntent, PendingIntent deliveryIntent,
+            SubscriptionInfo info) {
+        BluetoothAdapter btAdapter = BluetoothAdapter.getDefaultAdapter();
+        if (btAdapter == null) {
+            // No bluetooth service on this platform?
+            sendErrorInPendingIntent(sentIntent, SmsManager.RESULT_ERROR_NO_SERVICE);
+            return;
+        }
+        BluetoothDevice device = btAdapter.getRemoteDevice(info.getIccId());
+        if (device == null) {
+            if (DBG) Log.d(TAG, "Bluetooth device addr invalid: " + info.getIccId());
+            sendErrorInPendingIntent(sentIntent, SmsManager.RESULT_ERROR_NO_SERVICE);
+            return;
+        }
+        btAdapter.getProfileProxy(ActivityThread.currentApplication().getApplicationContext(),
+                new MapMessageSender(destAddr, text, device, sentIntent, deliveryIntent),
+                BluetoothProfile.MAP_CLIENT);
+    }
+
+    private class MapMessageSender implements BluetoothProfile.ServiceListener {
+        final Uri[] mDestAddr;
+        private String mMessage;
+        final BluetoothDevice mDevice;
+        final PendingIntent mSentIntent;
+        final PendingIntent mDeliveryIntent;
+        MapMessageSender(final String destAddr, final String message, final BluetoothDevice device,
+                final PendingIntent sentIntent, final PendingIntent deliveryIntent) {
+            super();
+            mDestAddr = new Uri[] {new Uri.Builder()
+                    .appendPath(destAddr)
+                    .scheme(PhoneAccount.SCHEME_TEL)
+                    .build()};
+            mMessage = message;
+            mDevice = device;
+            mSentIntent = sentIntent;
+            mDeliveryIntent = deliveryIntent;
+        }
+
+        @Override
+        public void onServiceConnected(int profile, BluetoothProfile proxy) {
+            if (DBG) Log.d(TAG, "Service connected");
+            if (profile != BluetoothProfile.MAP_CLIENT) return;
+            BluetoothMapClient mapProfile = (BluetoothMapClient) proxy;
+            if (mMessage != null) {
+                if (DBG) Log.d(TAG, "Sending message thru bluetooth");
+                mapProfile.sendMessage(mDevice, mDestAddr, mMessage, mSentIntent, mDeliveryIntent);
+                mMessage = null;
+            }
+            BluetoothAdapter.getDefaultAdapter()
+                    .closeProfileProxy(BluetoothProfile.MAP_CLIENT, mapProfile);
+        }
+
+        @Override
+        public void onServiceDisconnected(int profile) {
+            if (mMessage != null) {
+                if (DBG) Log.d(TAG, "Bluetooth disconnected before sending the message");
+                sendErrorInPendingIntent(mSentIntent, SmsManager.RESULT_ERROR_NO_SERVICE);
+                mMessage = null;
+            }
+        }
+    }
+
+    private void sendErrorInPendingIntent(PendingIntent intent, int errorCode) {
+        try {
+            intent.send(errorCode);
+        } catch (PendingIntent.CanceledException e) {
+            // PendingIntent is cancelled. ignore sending this error code back to
+            // caller.
+            if (DBG) Log.d(TAG, "PendingIntent.CanceledException: " + e.getMessage());
+        }
+    }
+
     /**
      * Send a text based SMS without writing it into the SMS Provider.
      *
@@ -888,8 +1012,6 @@
         }
     }
 
-
-
     /**
      * Get the SmsManager associated with the default subscription id. The instance will always be
      * associated with the default subscription id, even if the default subscription id is changed.
@@ -2028,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/SubscriptionInfo.java b/telephony/java/android/telephony/SubscriptionInfo.java
index 51d5ab1..a3b3374 100644
--- a/telephony/java/android/telephony/SubscriptionInfo.java
+++ b/telephony/java/android/telephony/SubscriptionInfo.java
@@ -184,6 +184,11 @@
     private int mProfileClass;
 
     /**
+     * Type of subscription
+     */
+    private int mSubscriptionType;
+
+    /**
      * @hide
      */
     public SubscriptionInfo(int id, String iccId, int simSlotIndex, CharSequence displayName,
@@ -206,7 +211,8 @@
             @Nullable String groupUUID, boolean isMetered, int carrierId, int profileClass) {
         this(id, iccId, simSlotIndex, displayName, carrierName, nameSource, iconTint, number,
                 roaming, icon, mcc, mnc, countryIso, isEmbedded, accessRules, cardString, -1,
-                isOpportunistic, groupUUID, isMetered, false, carrierId, profileClass);
+                isOpportunistic, groupUUID, isMetered, false, carrierId, profileClass,
+                SubscriptionManager.SUBSCRIPTION_TYPE_LOCAL_SIM);
     }
 
     /**
@@ -217,7 +223,7 @@
             Bitmap icon, String mcc, String mnc, String countryIso, boolean isEmbedded,
             @Nullable UiccAccessRule[] accessRules, String cardString, int cardId,
             boolean isOpportunistic, @Nullable String groupUUID, boolean isMetered,
-            boolean isGroupDisabled, int carrierid, int profileClass) {
+            boolean isGroupDisabled, int carrierId, int profileClass, int subType) {
         this.mId = id;
         this.mIccId = iccId;
         this.mSimSlotIndex = simSlotIndex;
@@ -239,11 +245,11 @@
         this.mGroupUUID = groupUUID;
         this.mIsMetered = isMetered;
         this.mIsGroupDisabled = isGroupDisabled;
-        this.mCarrierId = carrierid;
+        this.mCarrierId = carrierId;
         this.mProfileClass = profileClass;
+        this.mSubscriptionType = subType;
     }
 
-
     /**
      * @return the subscription ID.
      */
@@ -487,6 +493,16 @@
     }
 
     /**
+     * This method returns the type of a subscription. It can be
+     * {@link SubscriptionManager#SUBSCRIPTION_TYPE_LOCAL_SIM} or
+     * {@link SubscriptionManager#SUBSCRIPTION_TYPE_REMOTE_SIM}.
+     * @return the type of subscription
+     */
+    public @SubscriptionManager.SubscriptionType int getSubscriptionType() {
+        return mSubscriptionType;
+    }
+
+    /**
      * Checks whether the app with the given context is authorized to manage this subscription
      * according to its metadata. Only supported for embedded subscriptions (if {@link #isEmbedded}
      * returns true).
@@ -612,11 +628,12 @@
             boolean isGroupDisabled = source.readBoolean();
             int carrierid = source.readInt();
             int profileClass = source.readInt();
+            int subType = source.readInt();
 
             return new SubscriptionInfo(id, iccId, simSlotIndex, displayName, carrierName,
                     nameSource, iconTint, number, dataRoaming, iconBitmap, mcc, mnc, countryIso,
                     isEmbedded, accessRules, cardString, cardId, isOpportunistic, groupUUID,
-                    isMetered, isGroupDisabled, carrierid, profileClass);
+                    isMetered, isGroupDisabled, carrierid, profileClass, subType);
         }
 
         @Override
@@ -650,6 +667,7 @@
         dest.writeBoolean(mIsGroupDisabled);
         dest.writeInt(mCarrierId);
         dest.writeInt(mProfileClass);
+        dest.writeInt(mSubscriptionType);
     }
 
     @Override
@@ -686,7 +704,8 @@
                 + " cardString=" + cardStringToPrint + " cardId=" + mCardId
                 + " isOpportunistic " + mIsOpportunistic + " mGroupUUID=" + mGroupUUID
                 + " isMetered=" + mIsMetered + " mIsGroupDisabled=" + mIsGroupDisabled
-                + " profileClass=" + mProfileClass + "}";
+                + " profileClass=" + mProfileClass
+                + " subscriptionType=" + mSubscriptionType + "}";
     }
 
     @Override
diff --git a/telephony/java/android/telephony/SubscriptionManager.java b/telephony/java/android/telephony/SubscriptionManager.java
index 9fa4c3c..869cf1c 100644
--- a/telephony/java/android/telephony/SubscriptionManager.java
+++ b/telephony/java/android/telephony/SubscriptionManager.java
@@ -247,7 +247,9 @@
     public static final String UNIQUE_KEY_SUBSCRIPTION_ID = "_id";
 
     /**
-     * TelephonyProvider column name for SIM ICC Identifier
+     * TelephonyProvider column name for a unique identifier for the subscription within the
+     * specific subscription type. For example, it contains SIM ICC Identifier subscriptions
+     * on Local SIMs. and Mac-address for Remote-SIM Subscriptions for Bluetooth devices.
      * <P>Type: TEXT (String)</P>
      */
     /** @hide */
@@ -265,6 +267,63 @@
     public static final int SIM_NOT_INSERTED = -1;
 
     /**
+     * The slot-index for Bluetooth Remote-SIM subscriptions
+     * @hide
+     */
+    public static final int SLOT_INDEX_FOR_REMOTE_SIM_SUB = INVALID_SIM_SLOT_INDEX;
+
+    /**
+     * TelephonyProvider column name Subscription-type.
+     * <P>Type: INTEGER (int)</P> {@link #SUBSCRIPTION_TYPE_LOCAL_SIM} for Local-SIM Subscriptions,
+     * {@link #SUBSCRIPTION_TYPE_REMOTE_SIM} for Remote-SIM Subscriptions.
+     * Default value is 0.
+     */
+    /** @hide */
+    public static final String SUBSCRIPTION_TYPE = "subscription_type";
+
+    /**
+     * This constant is to designate a subscription as a Local-SIM Subscription.
+     * <p> A Local-SIM can be a physical SIM inserted into a sim-slot in the device, or eSIM on the
+     * device.
+     * </p>
+     */
+    public static final int SUBSCRIPTION_TYPE_LOCAL_SIM = 0;
+
+    /**
+     * This constant is to designate a subscription as a Remote-SIM Subscription.
+     * <p>
+     * A Remote-SIM subscription is for a SIM on a phone connected to this device via some
+     * connectivity mechanism, for example bluetooth. Similar to Local SIM, this subscription can
+     * be used for SMS, Voice and data by proxying data through the connected device.
+     * Certain data of the SIM, such as IMEI, are not accessible for Remote SIMs.
+     * </p>
+     *
+     * <p>
+     * A Remote-SIM is available only as long the phone stays connected to this device.
+     * When the phone disconnects, Remote-SIM subscription is removed from this device and is
+     * no longer known. All data associated with the subscription, such as stored SMS, call logs,
+     * contacts etc, are removed from this device.
+     * </p>
+     *
+     * <p>
+     * If the phone re-connects to this device, a new Remote-SIM subscription is created for
+     * the phone. The Subscription Id associated with the new subscription is different from
+     * the Subscription Id of the previous Remote-SIM subscription created (and removed) for the
+     * phone; i.e., new Remote-SIM subscription treats the reconnected phone as a Remote-SIM that
+     * was never seen before.
+     * </p>
+     */
+    public static final int SUBSCRIPTION_TYPE_REMOTE_SIM = 1;
+
+    /** @hide */
+    @Retention(RetentionPolicy.SOURCE)
+    @IntDef(prefix = {"SUBSCRIPTION_TYPE_"},
+        value = {
+            SUBSCRIPTION_TYPE_LOCAL_SIM,
+            SUBSCRIPTION_TYPE_REMOTE_SIM})
+    public @interface SubscriptionType {}
+
+    /**
      * TelephonyProvider column name for user displayed name.
      * <P>Type: TEXT (String)</P>
      */
@@ -1145,7 +1204,7 @@
     }
 
     /**
-     * Get the SubscriptionInfo(s) of the currently inserted SIM(s). The records will be sorted
+     * Get the SubscriptionInfo(s) of the currently active SIM(s). The records will be sorted
      * by {@link SubscriptionInfo#getSimSlotIndex} then by {@link SubscriptionInfo#getSubscriptionId}.
      *
      * <p>Requires Permission: {@link android.Manifest.permission#READ_PHONE_STATE READ_PHONE_STATE}
@@ -1427,17 +1486,7 @@
             logd("[addSubscriptionInfoRecord]- invalid slotIndex");
         }
 
-        try {
-            ISub iSub = ISub.Stub.asInterface(ServiceManager.getService("isub"));
-            if (iSub != null) {
-                // FIXME: This returns 1 on success, 0 on error should should we return it?
-                iSub.addSubInfoRecord(iccId, slotIndex);
-            } else {
-                logd("[addSubscriptionInfoRecord]- ISub service is null");
-            }
-        } catch (RemoteException ex) {
-            // ignore it
-        }
+        addSubscriptionInfoRecord(iccId, null, slotIndex, SUBSCRIPTION_TYPE_LOCAL_SIM);
 
         // FIXME: Always returns null?
         return null;
@@ -1445,6 +1494,79 @@
     }
 
     /**
+     * Add a new SubscriptionInfo to SubscriptionInfo database if needed
+     * @param uniqueId This is the unique identifier for the subscription within the
+     *                 specific subscription type.
+     * @param displayName human-readable name of the device the subscription corresponds to.
+     * @param slotIndex the slot assigned to this subscription. It is ignored for subscriptionType
+     *                  of {@link #SUBSCRIPTION_TYPE_REMOTE_SIM}.
+     * @param subscriptionType the {@link #SUBSCRIPTION_TYPE}
+     * @hide
+     */
+    public void addSubscriptionInfoRecord(String uniqueId, String displayName, int slotIndex,
+            int subscriptionType) {
+        if (VDBG) {
+            logd("[addSubscriptionInfoRecord]+ uniqueId:" + uniqueId
+                    + ", displayName:" + displayName + ", slotIndex:" + slotIndex
+                    + ", subscriptionType: " + subscriptionType);
+        }
+        if (uniqueId == null) {
+            Log.e(LOG_TAG, "[addSubscriptionInfoRecord]- uniqueId is null");
+            return;
+        }
+
+        try {
+            ISub iSub = ISub.Stub.asInterface(ServiceManager.getService("isub"));
+            if (iSub == null) {
+                Log.e(LOG_TAG, "[addSubscriptionInfoRecord]- ISub service is null");
+                return;
+            }
+            int result = iSub.addSubInfo(uniqueId, displayName, slotIndex, subscriptionType);
+            if (result < 0) {
+                Log.e(LOG_TAG, "Adding of subscription didn't succeed: error = " + result);
+            } else {
+                logd("successfully added new subscription");
+            }
+        } catch (RemoteException ex) {
+            // ignore it
+        }
+    }
+
+    /**
+     * Remove SubscriptionInfo record from the SubscriptionInfo database
+     * @param uniqueId This is the unique identifier for the subscription within the specific
+     *                 subscription type.
+     * @param subscriptionType the {@link #SUBSCRIPTION_TYPE}
+     * @hide
+     */
+    public void removeSubscriptionInfoRecord(String uniqueId, int subscriptionType) {
+        if (VDBG) {
+            logd("[removeSubscriptionInfoRecord]+ uniqueId:" + uniqueId
+                    + ", subscriptionType: " + subscriptionType);
+        }
+        if (uniqueId == null) {
+            Log.e(LOG_TAG, "[addSubscriptionInfoRecord]- uniqueId is null");
+            return;
+        }
+
+        try {
+            ISub iSub = ISub.Stub.asInterface(ServiceManager.getService("isub"));
+            if (iSub == null) {
+                Log.e(LOG_TAG, "[removeSubscriptionInfoRecord]- ISub service is null");
+                return;
+            }
+            int result = iSub.removeSubInfo(uniqueId, subscriptionType);
+            if (result < 0) {
+                Log.e(LOG_TAG, "Removal of subscription didn't succeed: error = " + result);
+            } else {
+                logd("successfully removed subscription");
+            }
+        } catch (RemoteException ex) {
+            // ignore it
+        }
+    }
+
+    /**
      * Set SIM icon tint color by simInfo index
      * @param tint the RGB value of icon tint color of the SIM
      * @param subId the unique SubInfoRecord index in database
@@ -2737,6 +2859,95 @@
         }
     }
 
+    /**
+     * Enabled or disable a subscription. This is currently used in the settings page.
+     *
+     * <p>
+     * Permissions android.Manifest.permission.MODIFY_PHONE_STATE is required
+     *
+     * @param enable whether user is turning it on or off.
+     * @param subscriptionId Subscription to be enabled or disabled.
+     *                       It could be a eSIM or pSIM subscription.
+     *
+     * @return whether the operation is successful.
+     *
+     * @hide
+     */
+    @SystemApi
+    @RequiresPermission(android.Manifest.permission.MODIFY_PHONE_STATE)
+    public boolean setSubscriptionEnabled(int subscriptionId, boolean enable) {
+        if (VDBG) {
+            logd("setSubscriptionActivated subId= " + subscriptionId + " enable " + enable);
+        }
+        try {
+            ISub iSub = ISub.Stub.asInterface(ServiceManager.getService("isub"));
+            if (iSub != null) {
+                return iSub.setSubscriptionEnabled(enable, subscriptionId);
+            }
+        } catch (RemoteException ex) {
+            // ignore it
+        }
+
+        return false;
+    }
+
+    /**
+     * Returns whether the subscription is enabled or not. This is different from activated
+     * or deactivated for two aspects. 1) For when user disables a physical subscription, we
+     * actually disable the modem because we can't switch off the subscription. 2) For eSIM,
+     * user may enable one subscription but the system may activate another temporarily. In this
+     * case, user enabled one is different from current active one.
+
+     * @param subscriptionId The subscription it asks about.
+     * @return whether it's enabled or not. {@code true} if user set this subscription enabled
+     * earlier, or user never set subscription enable / disable on this slot explicitly, and
+     * this subscription is currently active. Otherwise, it returns {@code false}.
+     *
+     * @hide
+     */
+    @SystemApi
+    @RequiresPermission(Manifest.permission.READ_PRIVILEGED_PHONE_STATE)
+    public boolean isSubscriptionEnabled(int subscriptionId) {
+        try {
+            ISub iSub = ISub.Stub.asInterface(ServiceManager.getService("isub"));
+            if (iSub != null) {
+                return iSub.isSubscriptionEnabled(subscriptionId);
+            }
+        } catch (RemoteException ex) {
+            // ignore it
+        }
+
+        return false;
+    }
+
+    /**
+     * Get which subscription is enabled on this slot. See {@link #isSubscriptionEnabled(int)}
+     * for more details.
+     *
+     * @param slotIndex which slot it asks about.
+     * @return which subscription is enabled on this slot. If there's no enabled subscription
+     *         in this slot, it will return {@link SubscriptionManager#INVALID_SUBSCRIPTION_ID}.
+     *
+     * @hide
+     */
+    @SystemApi
+    @RequiresPermission(Manifest.permission.READ_PRIVILEGED_PHONE_STATE)
+    public int getEnabledSubscriptionId(int slotIndex) {
+        int subId = INVALID_SUBSCRIPTION_ID;
+
+        try {
+            ISub iSub = ISub.Stub.asInterface(ServiceManager.getService("isub"));
+            if (iSub != null) {
+                subId = iSub.getEnabledSubscriptionId(slotIndex);
+            }
+        } catch (RemoteException ex) {
+            // ignore it
+        }
+
+        if (VDBG) logd("getEnabledSubscriptionId, subId = " + subId);
+        return subId;
+    }
+
     private interface CallISubMethodHelper {
         int callMethod(ISub iSub) throws RemoteException;
     }
diff --git a/telephony/java/android/telephony/TelephonyManager.java b/telephony/java/android/telephony/TelephonyManager.java
index 5133612..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 {
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/euicc/EuiccManager.java b/telephony/java/android/telephony/euicc/EuiccManager.java
index 0fa1b41..bca088e6 100644
--- a/telephony/java/android/telephony/euicc/EuiccManager.java
+++ b/telephony/java/android/telephony/euicc/EuiccManager.java
@@ -91,20 +91,6 @@
             "android.telephony.euicc.action.NOTIFY_CARRIER_SETUP_INCOMPLETE";
 
     /**
-     * Intent action to select a profile to enable before download a new eSIM profile.
-     *
-     * May be called during device provisioning when there are multiple slots having profiles on
-     * them. This Intent launches a screen for all the current existing profiles and let users to
-     * choose which one they want to enable. In this case, the slot contains the profile will be
-     * activated.
-     *
-     * @hide
-     */
-    @SystemApi
-    public static final String ACTION_PROFILE_SELECTION =
-            "android.telephony.euicc.action.PROFILE_SELECTION";
-
-    /**
      * Intent action to provision an embedded subscription.
      *
      * <p>May be called during device provisioning to launch a screen to perform embedded SIM
@@ -325,8 +311,8 @@
     @IntDef(prefix = {"EUICC_ACTIVATION_"}, value = {
             EUICC_ACTIVATION_TYPE_DEFAULT,
             EUICC_ACTIVATION_TYPE_BACKUP,
-            EUICC_ACTIVATION_TYPE_TRANSFER
-
+            EUICC_ACTIVATION_TYPE_TRANSFER,
+            EUICC_ACTIVATION_TYPE_ACCOUNT_REQUIRED,
     })
     public @interface EuiccActivationType{}
 
@@ -360,6 +346,14 @@
     @SystemApi
     public static final int EUICC_ACTIVATION_TYPE_TRANSFER = 3;
 
+    /**
+     * The activation flow of eSIM requiring user account will be started. This can only be used
+     * when there is user account signed in. Otherwise, the flow will be failed.
+     *
+     * @hide
+     */
+    @SystemApi
+    public static final int EUICC_ACTIVATION_TYPE_ACCOUNT_REQUIRED = 4;
 
     /**
      * Euicc OTA update status which can be got by {@link #getOtaStatus}
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 9414abd..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
@@ -86,9 +86,7 @@
 
     /**
      * Prefer registering for IMS over IWLAN if possible if WiFi signal quality is high enough.
-     * @hide
      */
-    @SystemApi
     public static final int WIFI_MODE_WIFI_PREFERRED = 2;
 
     /**
@@ -317,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");
         }
 
@@ -333,7 +328,7 @@
     }
 
     /**
-     * Only visible for testing, use {@link #createForSubscriptionId(Context, int)} instead.
+     * Only visible for testing, use {@link #createForSubscriptionId(int)} instead.
      * @hide
      */
     @VisibleForTesting
@@ -343,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.
      *
@@ -356,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.");
         }
@@ -374,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);
         }
     }
 
@@ -405,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
@@ -421,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.");
         }
@@ -439,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);
         }
     }
 
@@ -798,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 d37198a..b171f79 100644
--- a/telephony/java/android/telephony/ims/ProvisioningManager.java
+++ b/telephony/java/android/telephony/ims/ProvisioningManager.java
@@ -21,13 +21,17 @@
 import android.annotation.NonNull;
 import android.annotation.RequiresPermission;
 import android.annotation.SystemApi;
+import android.annotation.WorkerThread;
 import android.content.Context;
 import android.os.Binder;
 import android.os.RemoteException;
 import android.os.ServiceManager;
+import android.telephony.CarrierConfigManager;
 import android.telephony.SubscriptionManager;
 import android.telephony.ims.aidl.IImsConfigCallback;
+import android.telephony.ims.feature.MmTelFeature;
 import android.telephony.ims.stub.ImsConfigImplBase;
+import android.telephony.ims.stub.ImsRegistrationImplBase;
 
 import com.android.internal.telephony.ITelephony;
 
@@ -38,13 +42,68 @@
  * to changes in these configurations.
  *
  * Note: IMS provisioning keys are defined per carrier or OEM using OMA-DM or other provisioning
- * applications and may vary.
+ * applications and may vary. For compatibility purposes, the first 100 integer values used in
+ * {@link #setProvisioningIntValue(int, int)} have been reserved for existing provisioning keys
+ * previously defined in the Android framework. Some common constants have been defined in this
+ * class to make integrating with other system apps easier. USE WITH CARE!
+ *
+ * To avoid collisions, please use String based configurations when possible:
+ * {@link #setProvisioningStringValue(int, String)} and {@link #getProvisioningStringValue(int)}.
  * @hide
  */
 @SystemApi
 public class ProvisioningManager {
 
     /**
+     * The query from {@link #getProvisioningStringValue(int)} has resulted in an unspecified error.
+     */
+    public static final String STRING_QUERY_RESULT_ERROR_GENERIC =
+            "STRING_QUERY_RESULT_ERROR_GENERIC";
+
+    /**
+     * The query from {@link #getProvisioningStringValue(int)} has resulted in an error because the
+     * ImsService implementation was not ready for provisioning queries.
+     */
+    public static final String STRING_QUERY_RESULT_ERROR_NOT_READY =
+            "STRING_QUERY_RESULT_ERROR_NOT_READY";
+
+    /**
+     * The integer result of provisioning for the queried key is disabled.
+     */
+    public static final int PROVISIONING_VALUE_DISABLED = 0;
+
+    /**
+     * The integer result of provisioning for the queried key is enabled.
+     */
+    public static final int PROVISIONING_VALUE_ENABLED = 1;
+
+
+    /**
+     * Override the user-defined WiFi Roaming enabled setting for this subscription, defined in
+     * {@link SubscriptionManager#WFC_ROAMING_ENABLED_CONTENT_URI}, for the purposes of provisioning
+     * the subscription for WiFi Calling.
+     *
+     * @see #getProvisioningIntValue(int)
+     * @see #setProvisioningIntValue(int, int)
+     */
+    public static final int KEY_VOICE_OVER_WIFI_ROAMING_ENABLED_OVERRIDE = 26;
+
+    /**
+     * Override the user-defined WiFi mode for this subscription, defined in
+     * {@link SubscriptionManager#WFC_MODE_CONTENT_URI}, for the purposes of provisioning
+     * this subscription for WiFi Calling.
+     *
+     * Valid values for this key are:
+     * {@link ImsMmTelManager#WIFI_MODE_WIFI_ONLY},
+     * {@link ImsMmTelManager#WIFI_MODE_CELLULAR_PREFERRED}, or
+     * {@link ImsMmTelManager#WIFI_MODE_WIFI_PREFERRED}.
+     *
+     * @see #getProvisioningIntValue(int)
+     * @see #setProvisioningIntValue(int, int)
+     */
+    public static final int KEY_VOICE_OVER_WIFI_MODE_OVERRIDE = 27;
+
+    /**
      * Callback for IMS provisioning changes.
      */
     public static class Callback {
@@ -113,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");
         }
 
@@ -143,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);
         }
     }
 
@@ -180,10 +240,15 @@
 
     /**
      * Query for the integer value associated with the provided key.
+     *
+     * This operation is blocking and should not be performed on the UI thread.
+     *
      * @param key An integer that represents the provisioning key, which is defined by the OEM.
-     * @return an integer value for the provided key.
+     * @return an integer value for the provided key, or
+     * {@link ImsConfigImplBase#CONFIG_RESULT_UNKNOWN} if the key doesn't exist.
      * @throws IllegalArgumentException if the key provided was invalid.
      */
+    @WorkerThread
     @RequiresPermission(Manifest.permission.READ_PRIVILEGED_PHONE_STATE)
     public int getProvisioningIntValue(int key) {
         try {
@@ -195,10 +260,16 @@
 
     /**
      * Query for the String value associated with the provided key.
-     * @param key An integer that represents the provisioning key, which is defined by the OEM.
-     * @return a String value for the provided key, or {@code null} if the key doesn't exist.
+     *
+     * This operation is blocking and should not be performed on the UI thread.
+     *
+     * @param key A String that represents the provisioning key, which is defined by the OEM.
+     * @return a String value for the provided key, {@code null} if the key doesn't exist, or one
+     * of the following error codes: {@link #STRING_QUERY_RESULT_ERROR_GENERIC},
+     * {@link #STRING_QUERY_RESULT_ERROR_NOT_READY}.
      * @throws IllegalArgumentException if the key provided was invalid.
      */
+    @WorkerThread
     @RequiresPermission(Manifest.permission.READ_PRIVILEGED_PHONE_STATE)
     public String getProvisioningStringValue(int key) {
         try {
@@ -210,10 +281,16 @@
 
     /**
      * Set the integer value associated with the provided key.
+     *
+     * This operation is blocking and should not be performed on the UI thread.
+     *
+     * Use {@link #setProvisioningStringValue(int, String)} with proper namespacing (to be defined
+     * per OEM or carrier) when possible instead to avoid key collision if needed.
      * @param key An integer that represents the provisioning key, which is defined by the OEM.
      * @param value a integer value for the provided key.
      * @return the result of setting the configuration value.
      */
+    @WorkerThread
     @RequiresPermission(Manifest.permission.MODIFY_PHONE_STATE)
     public @ImsConfigImplBase.SetConfigResult int setProvisioningIntValue(int key, int value) {
         try {
@@ -226,10 +303,14 @@
     /**
      * Set the String value associated with the provided key.
      *
-     * @param key An integer that represents the provisioning key, which is defined by the OEM.
+     * This operation is blocking and should not be performed on the UI thread.
+     *
+     * @param key A String that represents the provisioning key, which is defined by the OEM and
+     *     should be appropriately namespaced to avoid collision.
      * @param value a String value for the provided key.
      * @return the result of setting the configuration value.
      */
+    @WorkerThread
     @RequiresPermission(Manifest.permission.MODIFY_PHONE_STATE)
     public @ImsConfigImplBase.SetConfigResult int setProvisioningStringValue(int key,
             String value) {
@@ -240,6 +321,57 @@
         }
     }
 
+    /**
+     * Set the provisioning status for the IMS MmTel capability using the specified subscription.
+     *
+     * Provisioning may or may not be required, depending on the carrier configuration. If
+     * provisioning is not required for the carrier associated with this subscription or the device
+     * does not support the capability/technology combination specified, this operation will be a
+     * no-op.
+     *
+     * @see CarrierConfigManager#KEY_CARRIER_UT_PROVISIONING_REQUIRED_BOOL
+     * @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,
+            @ImsRegistrationImplBase.ImsRegistrationTech int tech,  boolean isProvisioned) {
+        try {
+            getITelephony().setImsProvisioningStatusForCapability(mSubId, capability, tech,
+                    isProvisioned);
+        } catch (RemoteException e) {
+            throw e.rethrowAsRuntimeException();
+        }
+    }
+
+    /**
+     * Get the provisioning status for the IMS MmTel capability specified.
+     *
+     * If provisioning is not required for the queried
+     * {@link MmTelFeature.MmTelCapabilities.MmTelCapability} and
+     * {@link ImsRegistrationImplBase.ImsRegistrationTech} combination specified, this method will
+     * always return {@code true}.
+     *
+     * @see CarrierConfigManager#KEY_CARRIER_UT_PROVISIONING_REQUIRED_BOOL
+     * @see CarrierConfigManager#KEY_CARRIER_VOLTE_PROVISIONING_REQUIRED_BOOL
+     * @return true if the device is provisioned for the capability or does not require
+     * 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,
+            @ImsRegistrationImplBase.ImsRegistrationTech int tech) {
+        try {
+            return getITelephony().getImsProvisioningStatusForCapability(mSubId, capability, tech);
+        } catch (RemoteException e) {
+            throw e.rethrowAsRuntimeException();
+        }
+    }
+
     private static SubscriptionManager getSubscriptionManager(Context context) {
         SubscriptionManager manager = context.getSystemService(SubscriptionManager.class);
         if (manager == null) {
diff --git a/telephony/java/android/telephony/ims/feature/CapabilityChangeRequest.java b/telephony/java/android/telephony/ims/feature/CapabilityChangeRequest.java
index 7c793a5..1ee8563 100644
--- a/telephony/java/android/telephony/ims/feature/CapabilityChangeRequest.java
+++ b/telephony/java/android/telephony/ims/feature/CapabilityChangeRequest.java
@@ -97,6 +97,13 @@
         public @ImsRegistrationImplBase.ImsRegistrationTech int getRadioTech() {
             return radioTech;
         }
+
+        @Override
+        public String toString() {
+            return "CapabilityPair{"
+                    + "mCapability=" + mCapability
+                    + ", radioTech=" + radioTech + '}';
+        }
     }
 
     // Pair contains <radio tech, mCapability>
@@ -212,6 +219,13 @@
         }
     }
 
+    @Override
+    public String toString() {
+        return "CapabilityChangeRequest{"
+                + "mCapabilitiesToEnable=" + mCapabilitiesToEnable
+                + ", mCapabilitiesToDisable=" + mCapabilitiesToDisable + '}';
+    }
+
     /**
      * @hide
      */
diff --git a/telephony/java/com/android/ims/ImsConfig.java b/telephony/java/com/android/ims/ImsConfig.java
index 71a2174..4fc6a19 100644
--- a/telephony/java/com/android/ims/ImsConfig.java
+++ b/telephony/java/com/android/ims/ImsConfig.java
@@ -277,12 +277,14 @@
          * Wi-Fi calling roaming status.
          * Value is in Integer format. ON (1), OFF(0).
          */
-        public static final int VOICE_OVER_WIFI_ROAMING = 26;
+        public static final int VOICE_OVER_WIFI_ROAMING =
+                ProvisioningManager.KEY_VOICE_OVER_WIFI_ROAMING_ENABLED_OVERRIDE;
         /**
          * Wi-Fi calling modem - WfcModeFeatureValueConstants.
          * Value is in Integer format.
          */
-        public static final int VOICE_OVER_WIFI_MODE = 27;
+        public static final int VOICE_OVER_WIFI_MODE =
+                ProvisioningManager.KEY_VOICE_OVER_WIFI_MODE_OVERRIDE;
         /**
          * VOLTE Status for voice over wifi status of Enabled (1), or Disabled (0).
          * Value is in Integer format.
diff --git a/telephony/java/com/android/ims/ImsException.java b/telephony/java/com/android/ims/ImsException.java
index f35e886..fea763e 100644
--- a/telephony/java/com/android/ims/ImsException.java
+++ b/telephony/java/com/android/ims/ImsException.java
@@ -21,8 +21,10 @@
 /**
  * This class defines a general IMS-related exception.
  *
+ * @deprecated Use {@link android.telephony.ims.ImsException} instead.
  * @hide
  */
+@Deprecated
 public class ImsException extends Exception {
 
     /**
diff --git a/telephony/java/com/android/internal/telephony/ISms.aidl b/telephony/java/com/android/internal/telephony/ISms.aidl
index a4eb424..b51eda3 100644
--- a/telephony/java/com/android/internal/telephony/ISms.aidl
+++ b/telephony/java/com/android/internal/telephony/ISms.aidl
@@ -18,6 +18,8 @@
 
 import android.app.PendingIntent;
 import android.net.Uri;
+import android.os.Bundle;
+import android.telephony.IFinancialSmsCallback;
 import com.android.internal.telephony.SmsRawData;
 
 /** Interface for applications to access the ICC phone book.
@@ -560,4 +562,30 @@
      * @param intent PendingIntent to be sent when an SMS is received containing the token.
      */
     String createAppSpecificSmsToken(int subId, String callingPkg, in PendingIntent intent);
+
+    /**
+     * Create an app-only incoming SMS request for the calling package.
+     *
+     * If an incoming text contains the token returned by this method the provided
+     * <code>PendingIntent</code> will be sent containing the SMS data.
+     *
+     * @param subId the SIM id.
+     * @param callingPkg the package name of the calling app.
+     * @param prefixes the caller provided prefixes
+     * @param intent PendingIntent to be sent when a SMS is received containing the token and one
+     *   of the prefixes
+     */
+    String createAppSpecificSmsTokenWithPackageInfo(
+            int subId, String callingPkg, String prefixes, in PendingIntent intent);
+
+    /**
+     * Get sms inbox messages for the calling financial app.
+     *
+     * @param subId the SIM id.
+     * @param callingPkg the package name of the calling app.
+     * @param params parameters to filter the sms messages.
+     * @param callback the callback interface to deliver the result.
+     */
+    void getSmsMessagesForFinancialApp(
+        int subId, String callingPkg, in Bundle params, in IFinancialSmsCallback callback);
 }
diff --git a/telephony/java/com/android/internal/telephony/ISmsImplBase.java b/telephony/java/com/android/internal/telephony/ISmsImplBase.java
index 1cdf44d..12c5c30 100644
--- a/telephony/java/com/android/internal/telephony/ISmsImplBase.java
+++ b/telephony/java/com/android/internal/telephony/ISmsImplBase.java
@@ -18,6 +18,8 @@
 
 import android.app.PendingIntent;
 import android.net.Uri;
+import android.os.Bundle;
+import android.telephony.IFinancialSmsCallback;
 
 import java.util.List;
 
@@ -188,4 +190,16 @@
     public String createAppSpecificSmsToken(int subId, String callingPkg, PendingIntent intent) {
         throw new UnsupportedOperationException();
     }
+
+    @Override
+    public String createAppSpecificSmsTokenWithPackageInfo(
+            int subId, String callingPkg, String prefixes, PendingIntent intent) {
+        throw new UnsupportedOperationException();
+    }
+
+    @Override
+    public void getSmsMessagesForFinancialApp(
+            int subId, String callingPkg, Bundle params, IFinancialSmsCallback callback) {
+        throw new UnsupportedOperationException();
+    }
 }
diff --git a/telephony/java/com/android/internal/telephony/ISub.aidl b/telephony/java/com/android/internal/telephony/ISub.aidl
index 577ddbd..a49d2d9 100755
--- a/telephony/java/com/android/internal/telephony/ISub.aidl
+++ b/telephony/java/com/android/internal/telephony/ISub.aidl
@@ -52,8 +52,8 @@
     /**
      * Get the active SubscriptionInfo associated with the slotIndex
      * @param slotIndex the slot which the subscription is inserted
-     * @param callingPackage The package maing the call.
-     * @return SubscriptionInfo, maybe null if its not active
+     * @param callingPackage The package making the call.
+     * @return SubscriptionInfo, null for Remote-SIMs or non-active slotIndex.
      */
     SubscriptionInfo getActiveSubscriptionInfoForSimSlotIndex(int slotIndex, String callingPackage);
 
@@ -115,6 +115,26 @@
     int addSubInfoRecord(String iccId, int slotIndex);
 
     /**
+     * Add a new subscription info record, if needed
+     * @param uniqueId This is the unique identifier for the subscription within the specific
+     *                 subscription type.
+     * @param displayName human-readable name of the device the subscription corresponds to.
+     * @param slotIndex the slot assigned to this device
+     * @param subscriptionType the type of subscription to be added.
+     * @return 0 if success, < 0 on error.
+     */
+    int addSubInfo(String uniqueId, String displayName, int slotIndex, int subscriptionType);
+
+    /**
+     * Remove subscription info record for the given device.
+     * @param uniqueId This is the unique identifier for the subscription within the specific
+     *                      subscription type.
+     * @param subscriptionType the type of subscription to be removed
+     * @return 0 if success, < 0 on error.
+     */
+    int removeSubInfo(String uniqueId, int subscriptionType);
+
+    /**
      * Set SIM icon tint color by simInfo index
      * @param tint the icon tint color of the SIM
      * @param subId the unique SubscriptionInfo index in database
@@ -256,6 +276,11 @@
 
     String getSubscriptionProperty(int subId, String propKey, String callingPackage);
 
+    boolean setSubscriptionEnabled(boolean enable, int subId);
+
+    boolean isSubscriptionEnabled(int subId);
+
+    int getEnabledSubscriptionId(int slotIndex);
     /**
      * Get the SIM state for the slot index
      * @return SIM state as the ordinal of IccCardConstants.State
diff --git a/telephony/java/com/android/internal/telephony/ITelephony.aidl b/telephony/java/com/android/internal/telephony/ITelephony.aidl
index 9cc173c..8237d39 100644
--- a/telephony/java/com/android/internal/telephony/ITelephony.aidl
+++ b/telephony/java/com/android/internal/telephony/ITelephony.aidl
@@ -1765,6 +1765,24 @@
     void unregisterImsProvisioningChangedCallback(int subId, IImsConfigCallback callback);
 
     /**
+     * Set the provisioning status for the IMS MmTel capability using the specified subscription.
+     */
+    void setImsProvisioningStatusForCapability(int subId, int capability, int tech,
+            boolean isProvisioned);
+
+    /**
+     * Get the provisioning status for the IMS MmTel capability specified.
+     */
+    boolean getImsProvisioningStatusForCapability(int subId, int capability, int tech);
+
+    /** Is the capability and tech flagged as provisioned in the cache */
+    boolean isMmTelCapabilityProvisionedInCache(int subId, int capability, int tech);
+
+    /** Set the provisioning for the capability and tech in the cache */
+    void cacheMmTelCapabilityProvisioning(int subId, int capability, int tech,
+            boolean isProvisioned);
+
+    /**
      * Return an integer containing the provisioning value for the specified provisioning key.
      */
     int getImsProvisioningInt(int subId, int key);
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/telephony/java/com/android/internal/telephony/TelephonyPermissions.java b/telephony/java/com/android/internal/telephony/TelephonyPermissions.java
index 0edc002..c76d153 100644
--- a/telephony/java/com/android/internal/telephony/TelephonyPermissions.java
+++ b/telephony/java/com/android/internal/telephony/TelephonyPermissions.java
@@ -284,10 +284,6 @@
      */
     private static boolean reportAccessDeniedToReadIdentifiers(Context context, int subId, int pid,
             int uid, String callingPackage, String message) {
-        // If the device identifier check is enabled then enforce the new access requirements for
-        // both 1P and 3P apps.
-        boolean enableDeviceIdentifierCheck = Settings.Global.getInt(context.getContentResolver(),
-                Settings.Global.PRIVILEGED_DEVICE_IDENTIFIER_CHECK_ENABLED, 0) == 1;
         // Check if the application is a 3P app; if so then a separate setting is required to relax
         // the check to begin flagging problems with 3P apps early.
         boolean relax3PDeviceIdentifierCheck = Settings.Global.getInt(context.getContentResolver(),
@@ -300,6 +296,11 @@
                 context.getContentResolver(),
                 Settings.Global.PRIVILEGED_DEVICE_IDENTIFIER_NON_PRIV_CHECK_RELAXED, 0) == 1;
         boolean isNonPrivApp = false;
+        // Similar to above support relaxing the check for privileged apps while still enforcing it
+        // for non-privileged and 3P apps.
+        boolean relaxPrivDeviceIdentifierCheck = Settings.Global.getInt(
+                context.getContentResolver(),
+                Settings.Global.PRIVILEGED_DEVICE_IDENTIFIER_PRIV_CHECK_RELAXED, 0) == 1;
         ApplicationInfo callingPackageInfo = null;
         try {
             callingPackageInfo = context.getPackageManager().getApplicationInfo(callingPackage, 0);
@@ -315,37 +316,28 @@
             Log.e(LOG_TAG, "Exception caught obtaining package info for package " + callingPackage,
                     e);
         }
-        Log.wtf(LOG_TAG, "reportAccessDeniedToReadIdentifiers:" + callingPackage + ":" + message
-                + ":is3PApp=" + is3PApp + ":isNonPrivApp=" + isNonPrivApp);
-        // The new Q restrictions for device identifier access will be enforced if any of the
-        // following are true:
-        // - The PRIVILEGED_DEVICE_IDENTIFIER_CHECK_ENABLED setting has been set.
-        // - The app requesting a device identifier is not a preloaded app (3P), and the
-        //   PRIVILEGED_DEVICE_IDENTIFIER_3P_CHECK_RELAXED setting has not been set.
-        // - The app requesting a device identifier is a preloaded app but is not a privileged app,
-        //   and the PRIVILEGED_DEVICE_IDENTIFIER_NON_PRIV_CHECK_RELAXED setting has not been set.
-        if (enableDeviceIdentifierCheck
+        // The new Q restrictions for device identifier access will be enforced for all apps with
+        // settings to individually disable the new restrictions for privileged, preloaded
+        // non-privileged, and 3P apps.
+        if ((!is3PApp && !isNonPrivApp && !relaxPrivDeviceIdentifierCheck)
                 || (is3PApp && !relax3PDeviceIdentifierCheck)
                 || (isNonPrivApp && !relaxNonPrivDeviceIdentifierCheck)) {
-            boolean targetQBehaviorDisabled = Settings.Global.getInt(context.getContentResolver(),
-                    Settings.Global.PRIVILEGED_DEVICE_IDENTIFIER_TARGET_Q_BEHAVIOR_ENABLED, 0) == 0;
-            if (callingPackage != null) {
-                // if the target SDK is pre-Q or the target Q behavior is disabled then check if
-                // the calling package would have previously had access to device identifiers.
-                if (callingPackageInfo != null && (
-                        callingPackageInfo.targetSdkVersion < Build.VERSION_CODES.Q
-                                || targetQBehaviorDisabled)) {
-                    if (context.checkPermission(
-                            android.Manifest.permission.READ_PHONE_STATE,
-                            pid,
-                            uid) == PackageManager.PERMISSION_GRANTED) {
-                        return false;
-                    }
-                    if (SubscriptionManager.isValidSubscriptionId(subId)
-                            && getCarrierPrivilegeStatus(TELEPHONY_SUPPLIER, subId, uid)
-                            == TelephonyManager.CARRIER_PRIVILEGE_STATUS_HAS_ACCESS) {
-                        return false;
-                    }
+            Log.wtf(LOG_TAG, "reportAccessDeniedToReadIdentifiers:" + callingPackage + ":" + message
+                    + ":is3PApp=" + is3PApp + ":isNonPrivApp=" + isNonPrivApp);
+            // if the target SDK is pre-Q then check if the calling package would have previously
+            // had access to device identifiers.
+            if (callingPackageInfo != null && (
+                    callingPackageInfo.targetSdkVersion < Build.VERSION_CODES.Q)) {
+                if (context.checkPermission(
+                        android.Manifest.permission.READ_PHONE_STATE,
+                        pid,
+                        uid) == PackageManager.PERMISSION_GRANTED) {
+                    return false;
+                }
+                if (SubscriptionManager.isValidSubscriptionId(subId)
+                        && getCarrierPrivilegeStatus(TELEPHONY_SUPPLIER, subId, uid)
+                        == TelephonyManager.CARRIER_PRIVILEGE_STATUS_HAS_ACCESS) {
+                    return false;
                 }
             }
             throw new SecurityException(message + ": The user " + uid
diff --git a/test-mock/src/android/test/mock/MockCursor.java b/test-mock/src/android/test/mock/MockCursor.java
index 576f24a..f69db2c 100644
--- a/test-mock/src/android/test/mock/MockCursor.java
+++ b/test-mock/src/android/test/mock/MockCursor.java
@@ -24,6 +24,8 @@
 import android.net.Uri;
 import android.os.Bundle;
 
+import java.util.List;
+
 /**
  * A mock {@link android.database.Cursor} class that isolates the test code from real
  * Cursor implementation.
@@ -226,11 +228,21 @@
     }
 
     @Override
+    public void setNotificationUris(ContentResolver cr, List<Uri> uris) {
+        throw new UnsupportedOperationException("unimplemented mock method");
+    }
+
+    @Override
     public Uri getNotificationUri() {
         throw new UnsupportedOperationException("unimplemented mock method");
     }
 
     @Override
+    public List<Uri> getNotificationUris() {
+        throw new UnsupportedOperationException("unimplemented mock method");
+    }
+
+    @Override
     public void unregisterContentObserver(ContentObserver observer) {
         throw new UnsupportedOperationException("unimplemented mock method");
     }
diff --git a/tests/DexLoggerIntegrationTests/Android.mk b/tests/DexLoggerIntegrationTests/Android.mk
index 979d13a..ee02a72 100644
--- a/tests/DexLoggerIntegrationTests/Android.mk
+++ b/tests/DexLoggerIntegrationTests/Android.mk
@@ -35,7 +35,6 @@
 
 LOCAL_MODULE_TAGS := tests
 LOCAL_MODULE := DexLoggerNativeTestLibrary
-LOCAL_MULTILIB := first
 LOCAL_SRC_FILES := src/cpp/com_android_dcl_Jni.cpp
 LOCAL_C_INCLUDES += \
     $(JNI_H_INCLUDE)
@@ -44,8 +43,6 @@
 
 include $(BUILD_SHARED_LIBRARY)
 
-dexloggertest_so := $(LOCAL_BUILT_MODULE)
-
 # And a standalone native executable that we can exec.
 
 include $(CLEAR_VARS)
@@ -73,11 +70,15 @@
     android-support-test \
     truth-prebuilt \
 
+# Include both versions of the .so if we have 2 arch
+LOCAL_MULTILIB := both
+LOCAL_JNI_SHARED_LIBRARIES := \
+    DexLoggerNativeTestLibrary \
+
 # This gets us the javalib.jar built by DexLoggerTestLibrary above as well as the various
 # native binaries.
 LOCAL_JAVA_RESOURCE_FILES := \
     $(dexloggertest_jar) \
-    $(dexloggertest_so) \
-    $(dexloggertest_executable)
+    $(dexloggertest_executable) \
 
 include $(BUILD_PACKAGE)
diff --git a/tests/DexLoggerIntegrationTests/src/com/android/server/pm/dex/DexLoggerIntegrationTests.java b/tests/DexLoggerIntegrationTests/src/com/android/server/pm/dex/DexLoggerIntegrationTests.java
index d68769b..e92cc56 100644
--- a/tests/DexLoggerIntegrationTests/src/com/android/server/pm/dex/DexLoggerIntegrationTests.java
+++ b/tests/DexLoggerIntegrationTests/src/com/android/server/pm/dex/DexLoggerIntegrationTests.java
@@ -21,6 +21,7 @@
 
 import android.app.UiAutomation;
 import android.content.Context;
+import android.os.Build;
 import android.os.ParcelFileDescriptor;
 import android.os.SystemClock;
 import android.support.test.InstrumentationRegistry;
@@ -147,7 +148,7 @@
         String expectedNameHash =
                 "996223BAD4B4FE75C57A3DEC61DB9C0B38E0A7AD479FC95F33494F4BC55A0F0E";
         String expectedContentHash =
-                copyAndHashResource("/DexLoggerNativeTestLibrary.so", privateCopyFile);
+                copyAndHashResource(libraryPath("DexLoggerNativeTestLibrary.so"), privateCopyFile);
 
         System.load(privateCopyFile.toString());
 
@@ -170,7 +171,7 @@
         String expectedNameHash =
                 "8C39990C560B4F36F83E208E279F678746FE23A790E4C50F92686584EA2041CA";
         String expectedContentHash =
-                copyAndHashResource("/DexLoggerNativeTestLibrary.so", privateCopyFile);
+                copyAndHashResource(libraryPath("DexLoggerNativeTestLibrary.so"), privateCopyFile);
 
         System.load(privateCopyFile.toString());
 
@@ -307,6 +308,12 @@
         return new File(sContext.getDir("dcl", Context.MODE_PRIVATE), name);
     }
 
+    private String libraryPath(final String libraryName) {
+        // This may be deprecated. but it tells us the ABI of this process which is exactly what we
+        // want.
+        return "/lib/" + Build.CPU_ABI + "/" + libraryName;
+    }
+
     private static String copyAndHashResource(String resourcePath, File copyTo) throws Exception {
         MessageDigest hasher = MessageDigest.getInstance("SHA-256");
 
diff --git a/tests/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/Android.mk b/tests/RollbackTest/Android.mk
index 34aa258..780bb24 100644
--- a/tests/RollbackTest/Android.mk
+++ b/tests/RollbackTest/Android.mk
@@ -36,6 +36,17 @@
 include $(BUILD_PACKAGE)
 ROLLBACK_TEST_APP_AV2 := $(LOCAL_INSTALLED_MODULE)
 
+# RollbackTestAppACrashingV2.apk
+include $(CLEAR_VARS)
+LOCAL_MODULE_TAGS := optional
+LOCAL_MODULE_PATH := $(TARGET_OUT_DATA_APPS)
+LOCAL_SDK_VERSION := current
+LOCAL_SRC_FILES := $(call all-java-files-under, TestApp/src)
+LOCAL_MANIFEST_FILE := TestApp/ACrashingV2.xml
+LOCAL_PACKAGE_NAME := RollbackTestAppACrashingV2
+include $(BUILD_PACKAGE)
+ROLLBACK_TEST_APP_A_CRASHING_V2 := $(LOCAL_INSTALLED_MODULE)
+
 # RollbackTestAppBv1.apk
 include $(CLEAR_VARS)
 LOCAL_MODULE_TAGS := optional
@@ -68,6 +79,7 @@
 LOCAL_JAVA_RESOURCE_FILES := \
   $(ROLLBACK_TEST_APP_AV1) \
   $(ROLLBACK_TEST_APP_AV2) \
+  $(ROLLBACK_TEST_APP_A_CRASHING_V2) \
   $(ROLLBACK_TEST_APP_BV1) \
   $(ROLLBACK_TEST_APP_BV2)
 LOCAL_SDK_VERSION := system_current
@@ -77,5 +89,6 @@
 # Clean up local variables
 ROLLBACK_TEST_APP_AV1 :=
 ROLLBACK_TEST_APP_AV2 :=
+ROLLBACK_TEST_APP_A_CRASHING_V2 :=
 ROLLBACK_TEST_APP_BV1 :=
 ROLLBACK_TEST_APP_BV2 :=
diff --git a/tests/RollbackTest/TestApp/ACrashingV2.xml b/tests/RollbackTest/TestApp/ACrashingV2.xml
new file mode 100644
index 0000000..5708d23
--- /dev/null
+++ b/tests/RollbackTest/TestApp/ACrashingV2.xml
@@ -0,0 +1,37 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- 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.
+-->
+
+<manifest xmlns:android="http://schemas.android.com/apk/res/android"
+    package="com.android.tests.rollback.testapp.A"
+    android:versionCode="2"
+    android:versionName="2.0" >
+
+
+    <uses-sdk android:minSdkVersion="19" />
+
+    <application android:label="Rollback Test App A v2">
+        <meta-data android:name="version" android:value="2" />
+        <receiver android:name="com.android.tests.rollback.testapp.ProcessUserData"
+                  android:exported="true" />
+        <activity android:name="com.android.tests.rollback.testapp.CrashingMainActivity">
+            <intent-filter>
+              <action android:name="android.intent.action.MAIN" />
+              <category android:name="android.intent.category.DEFAULT"/>
+              <category android:name="android.intent.category.LAUNCHER" />
+            </intent-filter>
+        </activity>
+    </application>
+</manifest>
diff --git a/services/net/java/android/net/dhcp/DhcpClient.java b/tests/RollbackTest/TestApp/src/com/android/tests/rollback/testapp/CrashingMainActivity.java
similarity index 61%
copy from services/net/java/android/net/dhcp/DhcpClient.java
copy to tests/RollbackTest/TestApp/src/com/android/tests/rollback/testapp/CrashingMainActivity.java
index cddb91f..02a439b 100644
--- a/services/net/java/android/net/dhcp/DhcpClient.java
+++ b/tests/RollbackTest/TestApp/src/com/android/tests/rollback/testapp/CrashingMainActivity.java
@@ -14,16 +14,20 @@
  * limitations under the License.
  */
 
-package android.net.dhcp;
+package com.android.tests.rollback.testapp;
+
+import android.app.Activity;
+import android.os.Bundle;
 
 /**
- * TODO: remove this class after migrating clients.
+ * A crashing test app for testing apk rollback support.
  */
-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 CrashingMainActivity extends Activity {
 
-    public static final int DHCP_SUCCESS = 1;
-    public static final int DHCP_FAILURE = 2;
+    @Override
+    public void onCreate(Bundle savedInstanceState) {
+        super.onCreate(savedInstanceState);
+
+        throw new RuntimeException("Intended force crash");
+    }
 }
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 9d67cea..0493ef8 100644
--- a/tests/RollbackTest/src/com/android/tests/rollback/RollbackTest.java
+++ b/tests/RollbackTest/src/com/android/tests/rollback/RollbackTest.java
@@ -31,17 +31,16 @@
 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;
 import org.junit.Test;
 import org.junit.runner.RunWith;
 import org.junit.runners.JUnit4;
 
+import java.util.List;
 import java.util.concurrent.BlockingQueue;
 import java.util.concurrent.LinkedBlockingQueue;
 import java.util.concurrent.TimeUnit;
@@ -57,6 +56,7 @@
 
     private static final String TEST_APP_A = "com.android.tests.rollback.testapp.A";
     private static final String TEST_APP_B = "com.android.tests.rollback.testapp.B";
+    private static final String INSTRUMENTED_APP = "com.android.tests.rollback";
 
     /**
      * Test basic rollbacks.
@@ -84,8 +84,8 @@
                     Manifest.permission.DELETE_PACKAGES,
                     Manifest.permission.MANAGE_ROLLBACKS);
 
-            // Register a broadcast receiver for notification when the rollback is
-            // done executing.
+            // Register a broadcast receiver for notification when the
+            // rollback has been committed.
             RollbackBroadcastReceiver broadcastReceiver = new RollbackBroadcastReceiver();
             RollbackManager rm = RollbackTestUtils.getRollbackManager();
 
@@ -97,12 +97,11 @@
             // uninstalled and when rollback manager deletes the rollback. Fix it
             // so that's not the case!
             for (int i = 0; i < 5; ++i) {
-                for (RollbackInfo info : rm.getRecentlyExecutedRollbacks()) {
-                    if (TEST_APP_A.equals(info.targetPackage.getPackageName())) {
-                        Log.i(TAG, "Sleeping 1 second to wait for uninstall to take effect.");
-                        Thread.sleep(1000);
-                        break;
-                    }
+                RollbackInfo rollback = getUniqueRollbackInfoForPackage(
+                        rm.getRecentlyCommittedRollbacks(), TEST_APP_A);
+                if (rollback != null) {
+                    Log.i(TAG, "Sleeping 1 second to wait for uninstall to take effect.");
+                    Thread.sleep(1000);
                 }
             }
 
@@ -111,13 +110,11 @@
             // between when the app is uninstalled and when the previously
             // available rollback, if any, is removed.
             Thread.sleep(1000);
-            assertNull(rm.getAvailableRollback(TEST_APP_A));
-            assertFalse(rm.getPackagesWithAvailableRollbacks().contains(TEST_APP_A));
+            assertNull(getUniqueRollbackInfoForPackage(rm.getAvailableRollbacks(), TEST_APP_A));
 
-            // There should be no recently executed rollbacks for this package.
-            for (RollbackInfo info : rm.getRecentlyExecutedRollbacks()) {
-                assertNotEquals(TEST_APP_A, info.targetPackage.getPackageName());
-            }
+            // There should be no recently committed rollbacks for this package.
+            assertNull(getUniqueRollbackInfoForPackage(
+                        rm.getRecentlyCommittedRollbacks(), TEST_APP_A));
 
             // Install v1 of the app (without rollbacks enabled).
             RollbackTestUtils.install("RollbackTestAppAv1.apk", false);
@@ -132,10 +129,9 @@
             // between when the app is installed and when the rollback
             // is made available.
             Thread.sleep(1000);
-            assertTrue(rm.getPackagesWithAvailableRollbacks().contains(TEST_APP_A));
-            RollbackInfo rollback = rm.getAvailableRollback(TEST_APP_A);
-            assertNotNull(rollback);
-            assertPackageRollbackInfoEquals(TEST_APP_A, 2, 1, rollback.targetPackage);
+            RollbackInfo rollback = getUniqueRollbackInfoForPackage(
+                    rm.getAvailableRollbacks(), TEST_APP_A);
+            assertRollbackInfoEquals(TEST_APP_A, 2, 1, rollback);
 
             // We should not have received any rollback requests yet.
             // TODO: Possibly flaky if, by chance, some other app on device
@@ -143,7 +139,7 @@
             assertNull(broadcastReceiver.poll(0, TimeUnit.SECONDS));
 
             // Roll back the app.
-            RollbackTestUtils.rollback(rollback);
+            RollbackTestUtils.rollback(rollback.getRollbackId());
             assertEquals(1, RollbackTestUtils.getInstalledVersion(TEST_APP_A));
 
             // Verify we received a broadcast for the rollback.
@@ -154,15 +150,9 @@
             assertNull(broadcastReceiver.poll(0, TimeUnit.SECONDS));
 
             // Verify the recent rollback has been recorded.
-            rollback = null;
-            for (RollbackInfo r : rm.getRecentlyExecutedRollbacks()) {
-                if (TEST_APP_A.equals(r.targetPackage.getPackageName())) {
-                    assertNull(rollback);
-                    rollback = r;
-                }
-            }
-            assertNotNull(rollback);
-            assertPackageRollbackInfoEquals(TEST_APP_A, 2, 1, rollback.targetPackage);
+            rollback = getUniqueRollbackInfoForPackage(
+                    rm.getRecentlyCommittedRollbacks(), TEST_APP_A);
+            assertRollbackInfoEquals(TEST_APP_A, 2, 1, rollback);
 
             broadcastReceiver.unregister();
             context.unregisterReceiver(enableRollbackReceiver);
@@ -200,31 +190,28 @@
             // is made available.
             Thread.sleep(1000);
 
-            assertTrue(rm.getPackagesWithAvailableRollbacks().contains(TEST_APP_A));
-            RollbackInfo rollbackA = rm.getAvailableRollback(TEST_APP_A);
-            assertNotNull(rollbackA);
-            assertPackageRollbackInfoEquals(TEST_APP_A, 2, 1, rollbackA.targetPackage);
+            RollbackInfo rollbackA = getUniqueRollbackInfoForPackage(
+                    rm.getAvailableRollbacks(), TEST_APP_A);
+            assertRollbackInfoEquals(TEST_APP_A, 2, 1, rollbackA);
 
-            assertTrue(rm.getPackagesWithAvailableRollbacks().contains(TEST_APP_B));
-            RollbackInfo rollbackB = rm.getAvailableRollback(TEST_APP_B);
-            assertNotNull(rollbackB);
-            assertPackageRollbackInfoEquals(TEST_APP_B, 2, 1, rollbackB.targetPackage);
+            RollbackInfo rollbackB = getUniqueRollbackInfoForPackage(
+                    rm.getAvailableRollbacks(), TEST_APP_B);
+            assertRollbackInfoEquals(TEST_APP_B, 2, 1, rollbackB);
 
             // Reload the persisted data.
             rm.reloadPersistedData();
 
             // The apps should still be available for rollback.
-            rollbackA = rm.getAvailableRollback(TEST_APP_A);
-            assertNotNull(rollbackA);
-            assertPackageRollbackInfoEquals(TEST_APP_A, 2, 1, rollbackA.targetPackage);
+            rollbackA = getUniqueRollbackInfoForPackage(
+                    rm.getAvailableRollbacks(), TEST_APP_A);
+            assertRollbackInfoEquals(TEST_APP_A, 2, 1, rollbackA);
 
-            assertTrue(rm.getPackagesWithAvailableRollbacks().contains(TEST_APP_B));
-            rollbackB = rm.getAvailableRollback(TEST_APP_B);
-            assertNotNull(rollbackB);
-            assertPackageRollbackInfoEquals(TEST_APP_B, 2, 1, rollbackB.targetPackage);
+            rollbackB = getUniqueRollbackInfoForPackage(
+                    rm.getAvailableRollbacks(), TEST_APP_B);
+            assertRollbackInfoEquals(TEST_APP_B, 2, 1, rollbackB);
 
             // Rollback of B should not rollback A
-            RollbackTestUtils.rollback(rollbackB);
+            RollbackTestUtils.rollback(rollbackB.getRollbackId());
             assertEquals(2, RollbackTestUtils.getInstalledVersion(TEST_APP_A));
             assertEquals(1, RollbackTestUtils.getInstalledVersion(TEST_APP_B));
         } finally {
@@ -262,31 +249,26 @@
             // is made available.
             Thread.sleep(1000);
 
-            assertTrue(rm.getPackagesWithAvailableRollbacks().contains(TEST_APP_A));
-            RollbackInfo rollbackA = rm.getAvailableRollback(TEST_APP_A);
-            assertNotNull(rollbackA);
-            assertPackageRollbackInfoEquals(TEST_APP_A, 2, 1, rollbackA.targetPackage);
+            RollbackInfo rollbackA = getUniqueRollbackInfoForPackage(
+                    rm.getAvailableRollbacks(), TEST_APP_A);
+            assertRollbackInfoForAandB(rollbackA);
 
-            assertTrue(rm.getPackagesWithAvailableRollbacks().contains(TEST_APP_B));
-            RollbackInfo rollbackB = rm.getAvailableRollback(TEST_APP_B);
-            assertNotNull(rollbackB);
-            assertPackageRollbackInfoEquals(TEST_APP_B, 2, 1, rollbackB.targetPackage);
+            RollbackInfo rollbackB = getUniqueRollbackInfoForPackage(
+                    rm.getAvailableRollbacks(), TEST_APP_B);
+            assertRollbackInfoForAandB(rollbackB);
 
             // Reload the persisted data.
             rm.reloadPersistedData();
 
             // The apps should still be available for rollback.
-            rollbackA = rm.getAvailableRollback(TEST_APP_A);
-            assertNotNull(rollbackA);
-            assertPackageRollbackInfoEquals(TEST_APP_A, 2, 1, rollbackA.targetPackage);
+            rollbackA = getUniqueRollbackInfoForPackage(rm.getAvailableRollbacks(), TEST_APP_A);
+            assertRollbackInfoForAandB(rollbackA);
 
-            assertTrue(rm.getPackagesWithAvailableRollbacks().contains(TEST_APP_B));
-            rollbackB = rm.getAvailableRollback(TEST_APP_B);
-            assertNotNull(rollbackB);
-            assertPackageRollbackInfoEquals(TEST_APP_B, 2, 1, rollbackB.targetPackage);
+            rollbackB = getUniqueRollbackInfoForPackage(rm.getAvailableRollbacks(), TEST_APP_B);
+            assertRollbackInfoForAandB(rollbackB);
 
             // Rollback of B should rollback A as well
-            RollbackTestUtils.rollback(rollbackB);
+            RollbackTestUtils.rollback(rollbackB.getRollbackId());
             assertEquals(1, RollbackTestUtils.getInstalledVersion(TEST_APP_A));
             assertEquals(1, RollbackTestUtils.getInstalledVersion(TEST_APP_B));
         } finally {
@@ -295,10 +277,10 @@
     }
 
     /**
-     * Test that recently executed rollback data is properly persisted.
+     * Test that recently committed rollback data is properly persisted.
      */
     @Test
-    public void testRecentlyExecutedRollbackPersistence() throws Exception {
+    public void testRecentlyCommittedRollbackPersistence() throws Exception {
         try {
             RollbackTestUtils.adoptShellPermissionIdentity(
                     Manifest.permission.INSTALL_PACKAGES,
@@ -317,37 +299,27 @@
             // between when the app is installed and when the rollback
             // is made available.
             Thread.sleep(1000);
-            assertTrue(rm.getPackagesWithAvailableRollbacks().contains(TEST_APP_A));
-            RollbackInfo rollback = rm.getAvailableRollback(TEST_APP_A);
+            RollbackInfo rollback = getUniqueRollbackInfoForPackage(
+                    rm.getAvailableRollbacks(), TEST_APP_A);
 
             // Roll back the app.
-            RollbackTestUtils.rollback(rollback);
+            RollbackTestUtils.rollback(rollback.getRollbackId());
             assertEquals(1, RollbackTestUtils.getInstalledVersion(TEST_APP_A));
 
             // Verify the recent rollback has been recorded.
-            rollback = null;
-            for (RollbackInfo r : rm.getRecentlyExecutedRollbacks()) {
-                if (TEST_APP_A.equals(r.targetPackage.getPackageName())) {
-                    assertNull(rollback);
-                    rollback = r;
-                }
-            }
+            rollback = getUniqueRollbackInfoForPackage(
+                    rm.getRecentlyCommittedRollbacks(), TEST_APP_A);
             assertNotNull(rollback);
-            assertPackageRollbackInfoEquals(TEST_APP_A, 2, 1, rollback.targetPackage);
+            assertRollbackInfoEquals(TEST_APP_A, 2, 1, rollback);
 
             // Reload the persisted data.
             rm.reloadPersistedData();
 
             // Verify the recent rollback is still recorded.
-            rollback = null;
-            for (RollbackInfo r : rm.getRecentlyExecutedRollbacks()) {
-                if (TEST_APP_A.equals(r.targetPackage.getPackageName())) {
-                    assertNull(rollback);
-                    rollback = r;
-                }
-            }
+            rollback = getUniqueRollbackInfoForPackage(
+                    rm.getRecentlyCommittedRollbacks(), TEST_APP_A);
             assertNotNull(rollback);
-            assertPackageRollbackInfoEquals(TEST_APP_A, 2, 1, rollback.targetPackage);
+            assertRollbackInfoEquals(TEST_APP_A, 2, 1, rollback);
         } finally {
             RollbackTestUtils.dropShellPermissionIdentity();
         }
@@ -376,17 +348,16 @@
             // between when the app is installed and when the rollback
             // is made available.
             Thread.sleep(1000);
-            assertTrue(rm.getPackagesWithAvailableRollbacks().contains(TEST_APP_A));
-            RollbackInfo rollback = rm.getAvailableRollback(TEST_APP_A);
-            assertNotNull(rollback);
-            assertPackageRollbackInfoEquals(TEST_APP_A, 2, 1, rollback.targetPackage);
+            RollbackInfo rollback = getUniqueRollbackInfoForPackage(
+                    rm.getAvailableRollbacks(), TEST_APP_A);
+            assertRollbackInfoEquals(TEST_APP_A, 2, 1, rollback);
 
             // Expire the rollback.
             rm.expireRollbackForPackage(TEST_APP_A);
 
             // The rollback should no longer be available.
-            assertNull(rm.getAvailableRollback(TEST_APP_A));
-            assertFalse(rm.getPackagesWithAvailableRollbacks().contains(TEST_APP_A));
+            assertNull(getUniqueRollbackInfoForPackage(
+                        rm.getAvailableRollbacks(), TEST_APP_A));
         } finally {
             RollbackTestUtils.dropShellPermissionIdentity();
         }
@@ -457,8 +428,9 @@
             processUserData(TEST_APP_A);
 
             RollbackManager rm = RollbackTestUtils.getRollbackManager();
-            RollbackInfo rollback = rm.getAvailableRollback(TEST_APP_A);
-            RollbackTestUtils.rollback(rollback);
+            RollbackInfo rollback = getUniqueRollbackInfoForPackage(
+                    rm.getAvailableRollbacks(), TEST_APP_A);
+            RollbackTestUtils.rollback(rollback.getRollbackId());
             processUserData(TEST_APP_A);
         } finally {
             RollbackTestUtils.dropShellPermissionIdentity();
@@ -467,12 +439,12 @@
 
     /**
      * Test restrictions on rollback broadcast sender.
-     * A random app should not be able to send a PACKAGE_ROLLBACK_EXECUTED broadcast.
+     * A random app should not be able to send a ROLLBACK_COMMITTED broadcast.
      */
     @Test
     public void testRollbackBroadcastRestrictions() throws Exception {
         RollbackBroadcastReceiver broadcastReceiver = new RollbackBroadcastReceiver();
-        Intent broadcast = new Intent(Intent.ACTION_PACKAGE_ROLLBACK_EXECUTED);
+        Intent broadcast = new Intent(Intent.ACTION_ROLLBACK_COMMITTED);
         try {
             InstrumentationRegistry.getContext().sendBroadcast(broadcast);
             fail("Succeeded in sending restricted broadcast from app context.");
@@ -514,21 +486,21 @@
             assertEquals(2, RollbackTestUtils.getInstalledVersion(TEST_APP_B));
 
             // Both test apps should now be available for rollback, and the
-            // targetPackage returned for rollback should be correct.
+            // RollbackInfo returned for the rollbacks should be correct.
             // TODO: See if there is a way to remove this race condition
             // between when the app is installed and when the rollback
             // is made available.
             Thread.sleep(1000);
-            RollbackInfo rollbackA = rm.getAvailableRollback(TEST_APP_A);
-            assertNotNull(rollbackA);
-            assertEquals(TEST_APP_A, rollbackA.targetPackage.getPackageName());
+            RollbackInfo rollbackA = getUniqueRollbackInfoForPackage(
+                    rm.getAvailableRollbacks(), TEST_APP_A);
+            assertRollbackInfoEquals(TEST_APP_A, 2, 1, rollbackA);
 
-            RollbackInfo rollbackB = rm.getAvailableRollback(TEST_APP_B);
-            assertNotNull(rollbackB);
-            assertEquals(TEST_APP_B, rollbackB.targetPackage.getPackageName());
+            RollbackInfo rollbackB = getUniqueRollbackInfoForPackage(
+                    rm.getAvailableRollbacks(), TEST_APP_B);
+            assertRollbackInfoEquals(TEST_APP_B, 2, 1, rollbackB);
 
             // Executing rollback should roll back the correct package.
-            RollbackTestUtils.rollback(rollbackA);
+            RollbackTestUtils.rollback(rollbackA.getRollbackId());
             assertEquals(1, RollbackTestUtils.getInstalledVersion(TEST_APP_A));
             assertEquals(2, RollbackTestUtils.getInstalledVersion(TEST_APP_B));
 
@@ -537,7 +509,7 @@
             RollbackTestUtils.install("RollbackTestAppAv2.apk", true);
             assertEquals(2, RollbackTestUtils.getInstalledVersion(TEST_APP_A));
 
-            RollbackTestUtils.rollback(rollbackB);
+            RollbackTestUtils.rollback(rollbackB.getRollbackId());
             assertEquals(2, RollbackTestUtils.getInstalledVersion(TEST_APP_A));
             assertEquals(1, RollbackTestUtils.getInstalledVersion(TEST_APP_B));
         } finally {
@@ -556,21 +528,14 @@
         RollbackManager rm = RollbackTestUtils.getRollbackManager();
 
         try {
-            rm.getAvailableRollback(TEST_APP_A);
+            rm.getAvailableRollbacks();
             fail("expected SecurityException");
         } catch (SecurityException e) {
             // Expected.
         }
 
         try {
-            rm.getPackagesWithAvailableRollbacks();
-            fail("expected SecurityException");
-        } catch (SecurityException e) {
-            // Expected.
-        }
-
-        try {
-            rm.getRecentlyExecutedRollbacks();
+            rm.getRecentlyCommittedRollbacks();
             fail("expected SecurityException");
         } catch (SecurityException e) {
             // Expected.
@@ -579,7 +544,7 @@
         try {
             // TODO: What if the implementation checks arguments for non-null
             // first? Then this test isn't valid.
-            rm.executeRollback(null, null);
+            rm.commitRollback(0, null);
             fail("expected SecurityException");
         } catch (SecurityException e) {
             // Expected.
@@ -627,27 +592,26 @@
             assertEquals(2, RollbackTestUtils.getInstalledVersion(TEST_APP_B));
 
             // TEST_APP_A should now be available for rollback.
-            assertTrue(rm.getPackagesWithAvailableRollbacks().contains(TEST_APP_A));
-            RollbackInfo rollback = rm.getAvailableRollback(TEST_APP_A);
-            assertNotNull(rollback);
-
-            // TODO: Test the dependent apps for rollback are correct once we
-            // support that in the RollbackInfo API.
+            RollbackInfo rollback = getUniqueRollbackInfoForPackage(
+                    rm.getAvailableRollbacks(), TEST_APP_A);
+            assertRollbackInfoForAandB(rollback);
 
             // Rollback the app. It should cause both test apps to be rolled
             // back.
-            RollbackTestUtils.rollback(rollback);
+            RollbackTestUtils.rollback(rollback.getRollbackId());
             assertEquals(1, RollbackTestUtils.getInstalledVersion(TEST_APP_A));
             assertEquals(1, RollbackTestUtils.getInstalledVersion(TEST_APP_B));
 
-            // We should not see a recent rollback listed for TEST_APP_B
-            for (RollbackInfo r : rm.getRecentlyExecutedRollbacks()) {
-                assertNotEquals(TEST_APP_B, r.targetPackage.getPackageName());
-            }
+            // We should see recent rollbacks listed for both A and B.
+            Thread.sleep(1000);
+            RollbackInfo rollbackA = getUniqueRollbackInfoForPackage(
+                    rm.getRecentlyCommittedRollbacks(), TEST_APP_A);
 
-            // TODO: Test the listed dependent apps for the recently executed
-            // rollback are correct once we support that in the RollbackInfo
-            // API.
+            RollbackInfo rollbackB = getUniqueRollbackInfoForPackage(
+                    rm.getRecentlyCommittedRollbacks(), TEST_APP_B);
+            assertRollbackInfoForAandB(rollbackB);
+
+            assertEquals(rollbackA.getRollbackId(), rollbackB.getRollbackId());
         } finally {
             RollbackTestUtils.dropShellPermissionIdentity();
         }
@@ -663,4 +627,106 @@
         assertEquals(packageName, info.getVersionRolledBackTo().getPackageName());
         assertEquals(versionRolledBackTo, info.getVersionRolledBackTo().getLongVersionCode());
     }
+
+    // TODO(zezeozue): Stop ignoring after fixing race between rolling back and testing version
+    /**
+     * Test bad update automatic rollback.
+     */
+    @Ignore("Flaky")
+    @Test
+    public void testBadUpdateRollback() throws Exception {
+        try {
+            RollbackTestUtils.adoptShellPermissionIdentity(
+                    Manifest.permission.INSTALL_PACKAGES,
+                    Manifest.permission.DELETE_PACKAGES,
+                    Manifest.permission.MANAGE_ROLLBACKS);
+            RollbackManager rm = RollbackTestUtils.getRollbackManager();
+
+            // Prep installation of the test apps.
+            RollbackTestUtils.uninstall(TEST_APP_A);
+            RollbackTestUtils.install("RollbackTestAppAv1.apk", false);
+            RollbackTestUtils.install("RollbackTestAppACrashingV2.apk", true);
+            assertEquals(2, RollbackTestUtils.getInstalledVersion(TEST_APP_A));
+
+            RollbackTestUtils.uninstall(TEST_APP_B);
+            RollbackTestUtils.install("RollbackTestAppBv1.apk", false);
+            RollbackTestUtils.install("RollbackTestAppBv2.apk", true);
+            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.
+            // 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 = getUniqueRollbackInfoForPackage(
+                    rm.getAvailableRollbacks(), TEST_APP_A);
+            assertRollbackInfoEquals(TEST_APP_A, 2, 1, rollbackA);
+
+            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++) {
+                RollbackTestUtils.launchPackage(TEST_APP_A);
+                Thread.sleep(1000);
+            }
+            Thread.sleep(1000);
+
+            // TEST_APP_A is automatically rolled back by the RollbackPackageHealthObserver
+            assertEquals(1, RollbackTestUtils.getInstalledVersion(TEST_APP_A));
+            // Instrumented app is still the package installer
+            Context context = InstrumentationRegistry.getContext();
+            String installer = context.getPackageManager().getInstallerPackageName(TEST_APP_A);
+            assertEquals(INSTRUMENTED_APP, installer);
+            // TEST_APP_B is untouched
+            assertEquals(2, RollbackTestUtils.getInstalledVersion(TEST_APP_B));
+        } finally {
+            RollbackTestUtils.dropShellPermissionIdentity();
+        }
+    }
+
+    // Helper function to test the value of a RollbackInfo with single package
+    private void assertRollbackInfoEquals(String packageName,
+            long versionRolledBackFrom, long versionRolledBackTo,
+            RollbackInfo info) {
+        assertNotNull(info);
+        assertEquals(1, info.getPackages().size());
+        assertPackageRollbackInfoEquals(packageName, versionRolledBackFrom, versionRolledBackTo,
+                info.getPackages().get(0));
+    }
+
+    // Helper function to test that the given rollback info is a rollback for
+    // the atomic set {A2, B2} -> {A1, B1}.
+    private void assertRollbackInfoForAandB(RollbackInfo rollback) {
+        assertNotNull(rollback);
+        assertEquals(2, rollback.getPackages().size());
+        if (TEST_APP_A.equals(rollback.getPackages().get(0).getPackageName())) {
+            assertPackageRollbackInfoEquals(TEST_APP_A, 2, 1, rollback.getPackages().get(0));
+            assertPackageRollbackInfoEquals(TEST_APP_B, 2, 1, rollback.getPackages().get(1));
+        } else {
+            assertPackageRollbackInfoEquals(TEST_APP_B, 2, 1, rollback.getPackages().get(0));
+            assertPackageRollbackInfoEquals(TEST_APP_A, 2, 1, rollback.getPackages().get(1));
+        }
+    }
+
+    // Helper function to return the RollbackInfo with a given package in the
+    // list of rollbacks. Throws an assertion failure if there is more than
+    // one such rollback info. Returns null if there are no such rollback
+    // infos.
+    private RollbackInfo getUniqueRollbackInfoForPackage(List<RollbackInfo> rollbacks,
+            String packageName) {
+        RollbackInfo found = null;
+        for (RollbackInfo rollback : rollbacks) {
+            for (PackageRollbackInfo info : rollback.getPackages()) {
+                if (packageName.equals(info.getPackageName())) {
+                    assertNull(found);
+                    found = rollback;
+                    break;
+                }
+            }
+        }
+        return found;
+    }
 }
diff --git a/tests/RollbackTest/src/com/android/tests/rollback/RollbackTestUtils.java b/tests/RollbackTest/src/com/android/tests/rollback/RollbackTestUtils.java
index fbc3d8f..1478657 100644
--- a/tests/RollbackTest/src/com/android/tests/rollback/RollbackTestUtils.java
+++ b/tests/RollbackTest/src/com/android/tests/rollback/RollbackTestUtils.java
@@ -21,7 +21,6 @@
 import android.content.pm.PackageInfo;
 import android.content.pm.PackageInstaller;
 import android.content.pm.PackageManager;
-import android.content.rollback.RollbackInfo;
 import android.content.rollback.RollbackManager;
 import android.support.test.InstrumentationRegistry;
 
@@ -90,12 +89,12 @@
     }
 
     /**
-     * Execute the given rollback.
+     * Commit the given rollback.
      * @throws AssertionError if the rollback fails.
      */
-    static void rollback(RollbackInfo rollback) throws InterruptedException {
+    static void rollback(int rollbackId) throws InterruptedException {
         RollbackManager rm = getRollbackManager();
-        rm.executeRollback(rollback, LocalIntentSender.getIntentSender());
+        rm.commitRollback(rollbackId, LocalIntentSender.getIntentSender());
         assertStatusSuccess(LocalIntentSender.getIntentSenderResult());
     }
 
@@ -135,6 +134,17 @@
         assertStatusSuccess(LocalIntentSender.getIntentSenderResult());
     }
 
+    /** Launches {@code packageName} with {@link Intent#ACTION_MAIN}. */
+    static void launchPackage(String packageName)
+            throws InterruptedException, IOException {
+        Context context = InstrumentationRegistry.getContext();
+        Intent intent = new Intent(Intent.ACTION_MAIN);
+        intent.setPackage(packageName);
+        intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
+        intent.addCategory(Intent.CATEGORY_LAUNCHER);
+        context.startActivity(intent);
+    }
+
     /**
      * Installs the apks with the given resource names as an atomic set.
      *
diff --git a/tests/UsageStatsTest/AndroidManifest.xml b/tests/UsageStatsTest/AndroidManifest.xml
index 4b1c1bd..fefd993 100644
--- a/tests/UsageStatsTest/AndroidManifest.xml
+++ b/tests/UsageStatsTest/AndroidManifest.xml
@@ -11,6 +11,7 @@
 
     <uses-permission android:name="android.permission.PACKAGE_USAGE_STATS" />
     <uses-permission android:name="android.permission.OBSERVE_APP_USAGE" />
+    <uses-permission android:name="android.permission.SUSPEND_APPS" />
 
     <application android:label="Usage Access Test">
         <activity android:name=".UsageStatsActivity"
diff --git a/tests/UsageStatsTest/res/menu/main.xml b/tests/UsageStatsTest/res/menu/main.xml
index 612267c..272e0f4 100644
--- a/tests/UsageStatsTest/res/menu/main.xml
+++ b/tests/UsageStatsTest/res/menu/main.xml
@@ -6,4 +6,6 @@
         android:title="Call isAppInactive()"/>
     <item android:id="@+id/set_app_limit"
         android:title="Set App Limit" />
+    <item android:id="@+id/set_app_usage_limit"
+          android:title="Set App Usage Limit" />
 </menu>
diff --git a/tests/UsageStatsTest/src/com/android/tests/usagestats/UsageStatsActivity.java b/tests/UsageStatsTest/src/com/android/tests/usagestats/UsageStatsActivity.java
index 3c628f6..0105893 100644
--- a/tests/UsageStatsTest/src/com/android/tests/usagestats/UsageStatsActivity.java
+++ b/tests/UsageStatsTest/src/com/android/tests/usagestats/UsageStatsActivity.java
@@ -21,6 +21,8 @@
 import android.app.PendingIntent;
 import android.app.usage.UsageStats;
 import android.app.usage.UsageStatsManager;
+import android.content.ClipData;
+import android.content.ClipboardManager;
 import android.content.Context;
 import android.content.DialogInterface;
 import android.content.Intent;
@@ -49,6 +51,8 @@
     private static final long USAGE_STATS_PERIOD = 1000 * 60 * 60 * 24 * 14;
     private static final String EXTRA_KEY_TIMEOUT = "com.android.tests.usagestats.extra.TIMEOUT";
     private UsageStatsManager mUsageStatsManager;
+    private ClipboardManager mClipboard;
+    private ClipData mClip;
     private Adapter mAdapter;
     private Comparator<UsageStats> mComparator = new Comparator<UsageStats>() {
         @Override
@@ -61,6 +65,7 @@
     protected void onCreate(Bundle savedInstanceState) {
         super.onCreate(savedInstanceState);
         mUsageStatsManager = (UsageStatsManager) getSystemService(Context.USAGE_STATS_SERVICE);
+        mClipboard = (ClipboardManager) getSystemService(Context.CLIPBOARD_SERVICE);
         mAdapter = new Adapter();
         setListAdapter(mAdapter);
         Bundle extras = getIntent().getExtras();
@@ -98,6 +103,8 @@
             case R.id.set_app_limit:
                 callSetAppLimit();
                 return true;
+            case R.id.set_app_usage_limit:
+                callSetAppUsageLimit();
             default:
                 return super.onOptionsItemSelected(item);
         }
@@ -170,6 +177,40 @@
         builder.show();
     }
 
+    private void callSetAppUsageLimit() {
+        final AlertDialog.Builder builder = new AlertDialog.Builder(this);
+        builder.setTitle("Enter package name");
+        final EditText input = new EditText(this);
+        input.setInputType(InputType.TYPE_CLASS_TEXT);
+        input.setHint("com.android.tests.usagestats");
+        builder.setView(input);
+
+        builder.setPositiveButton("OK", new DialogInterface.OnClickListener() {
+            @Override
+            public void onClick(DialogInterface dialog, int which) {
+                final String packageName = input.getText().toString().trim();
+                if (!TextUtils.isEmpty(packageName)) {
+                    String[] packages = packageName.split(",");
+                    Intent intent = new Intent(Intent.ACTION_MAIN);
+                    intent.setClass(UsageStatsActivity.this, UsageStatsActivity.class);
+                    intent.setPackage(getPackageName());
+                    intent.putExtra(EXTRA_KEY_TIMEOUT, true);
+                    mUsageStatsManager.registerAppUsageLimitObserver(1, packages,
+                            60, TimeUnit.SECONDS, PendingIntent.getActivity(UsageStatsActivity.this,
+                                    1, intent, 0));
+                }
+            }
+        });
+        builder.setNegativeButton("Cancel", new DialogInterface.OnClickListener() {
+            @Override
+            public void onClick(DialogInterface dialog, int which) {
+                dialog.cancel();
+            }
+        });
+
+        builder.show();
+    }
+
     private void showInactive(String packageName) {
         final AlertDialog.Builder builder = new AlertDialog.Builder(this);
         builder.setMessage(
@@ -232,6 +273,21 @@
             holder.packageName.setText(mStats.get(position).getPackageName());
             holder.usageTime.setText(DateUtils.formatDuration(
                     mStats.get(position).getTotalTimeInForeground()));
+
+            //copy package name to the clipboard for convenience
+            holder.packageName.setOnLongClickListener(new View.OnLongClickListener() {
+                @Override
+                public boolean onLongClick(View v) {
+                    String text = holder.packageName.getText().toString();
+                    mClip = ClipData.newPlainText("package_name", text);
+                    mClipboard.setPrimaryClip(mClip);
+
+                    Toast.makeText(getApplicationContext(), "package name copied to clipboard",
+                            Toast.LENGTH_SHORT).show();
+                    return true;
+                }
+            });
+
             return convertView;
         }
     }
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/ConnectivityServiceTest.java b/tests/net/java/com/android/server/ConnectivityServiceTest.java
index dda4481..923c7dd 100644
--- a/tests/net/java/com/android/server/ConnectivityServiceTest.java
+++ b/tests/net/java/com/android/server/ConnectivityServiceTest.java
@@ -107,6 +107,8 @@
 import android.net.INetworkStatsService;
 import android.net.InterfaceConfiguration;
 import android.net.IpPrefix;
+import android.net.IpSecManager;
+import android.net.IpSecManager.UdpEncapsulationSocket;
 import android.net.LinkAddress;
 import android.net.LinkProperties;
 import android.net.MatchAllNetworkSpecifier;
@@ -121,7 +123,9 @@
 import android.net.NetworkSpecifier;
 import android.net.NetworkStack;
 import android.net.NetworkUtils;
+import android.net.ProxyInfo;
 import android.net.RouteInfo;
+import android.net.SocketKeepalive;
 import android.net.UidRange;
 import android.net.metrics.IpConnectivityLog;
 import android.net.shared.NetworkMonitorUtils;
@@ -158,6 +162,7 @@
 import com.android.server.connectivity.IpConnectivityMetrics;
 import com.android.server.connectivity.MockableSystemProperties;
 import com.android.server.connectivity.Nat464Xlat;
+import com.android.server.connectivity.ProxyTracker;
 import com.android.server.connectivity.Tethering;
 import com.android.server.connectivity.Vpn;
 import com.android.server.net.NetworkPinner;
@@ -186,6 +191,8 @@
 import java.util.Objects;
 import java.util.Set;
 import java.util.concurrent.CountDownLatch;
+import java.util.concurrent.Executor;
+import java.util.concurrent.Executors;
 import java.util.concurrent.LinkedBlockingQueue;
 import java.util.concurrent.TimeUnit;
 import java.util.concurrent.atomic.AtomicBoolean;
@@ -402,8 +409,8 @@
         private final ConditionVariable mPreventReconnectReceived = new ConditionVariable();
         private int mScore;
         private NetworkAgent mNetworkAgent;
-        private int mStartKeepaliveError = PacketKeepalive.ERROR_HARDWARE_UNSUPPORTED;
-        private int mStopKeepaliveError = PacketKeepalive.NO_KEEPALIVE;
+        private int mStartKeepaliveError = SocketKeepalive.ERROR_HARDWARE_UNSUPPORTED;
+        private int mStopKeepaliveError = SocketKeepalive.NO_KEEPALIVE;
         private Integer mExpectedKeepaliveSlot = null;
         // Contains the redirectUrl from networkStatus(). Before reading, wait for
         // mNetworkStatusReceived.
@@ -1002,6 +1009,11 @@
         }
 
         @Override
+        protected ProxyTracker makeProxyTracker() {
+            return mock(ProxyTracker.class);
+        }
+
+        @Override
         protected int reserveNetId() {
             while (true) {
                 final int netId = super.reserveNetId();
@@ -1023,6 +1035,11 @@
             }
         }
 
+        @Override
+        protected boolean queryUserAccess(int uid, int netId) {
+            return true;
+        }
+
         public Nat464Xlat getNat464Xlat(MockNetworkAgent mna) {
             return getNetworkAgentInfoForNetwork(mna.getNetwork()).clatd;
         }
@@ -3548,6 +3565,80 @@
         }
     }
 
+    private static class TestSocketKeepaliveCallback extends SocketKeepalive.Callback {
+
+        public enum CallbackType { ON_STARTED, ON_STOPPED, ON_ERROR };
+
+        private class CallbackValue {
+            public CallbackType callbackType;
+            public int error;
+
+            CallbackValue(CallbackType type) {
+                this.callbackType = type;
+                this.error = SocketKeepalive.SUCCESS;
+                assertTrue("onError callback must have error", type != CallbackType.ON_ERROR);
+            }
+
+            CallbackValue(CallbackType type, int error) {
+                this.callbackType = type;
+                this.error = error;
+                assertEquals("error can only be set for onError", type, CallbackType.ON_ERROR);
+            }
+
+            @Override
+            public boolean equals(Object o) {
+                return o instanceof CallbackValue
+                        && this.callbackType == ((CallbackValue) o).callbackType
+                        && this.error == ((CallbackValue) o).error;
+            }
+
+            @Override
+            public String toString() {
+                return String.format("%s(%s, %d)", getClass().getSimpleName(), callbackType,
+                        error);
+            }
+        }
+
+        private LinkedBlockingQueue<CallbackValue> mCallbacks = new LinkedBlockingQueue<>();
+
+        @Override
+        public void onStarted() {
+            mCallbacks.add(new CallbackValue(CallbackType.ON_STARTED));
+        }
+
+        @Override
+        public void onStopped() {
+            mCallbacks.add(new CallbackValue(CallbackType.ON_STOPPED));
+        }
+
+        @Override
+        public void onError(int error) {
+            mCallbacks.add(new CallbackValue(CallbackType.ON_ERROR, error));
+        }
+
+        private void expectCallback(CallbackValue callbackValue) {
+            try {
+                assertEquals(
+                        callbackValue,
+                        mCallbacks.poll(TIMEOUT_MS, TimeUnit.MILLISECONDS));
+            } catch (InterruptedException e) {
+                fail(callbackValue.callbackType + " callback not seen after " + TIMEOUT_MS + " ms");
+            }
+        }
+
+        public void expectStarted() {
+            expectCallback(new CallbackValue(CallbackType.ON_STARTED));
+        }
+
+        public void expectStopped() {
+            expectCallback(new CallbackValue(CallbackType.ON_STOPPED));
+        }
+
+        public void expectError(int error) {
+            expectCallback(new CallbackValue(CallbackType.ON_ERROR, error));
+        }
+    }
+
     private Network connectKeepaliveNetwork(LinkProperties lp) {
         // Ensure the network is disconnected before we do anything.
         if (mWiFiNetworkAgent != null) {
@@ -3695,6 +3786,145 @@
     }
 
     @Test
+    public void testNattSocketKeepalives() throws Exception {
+        // TODO: 1. Move this outside of ConnectivityServiceTest.
+        //       2. Add helper function to test against newSingleThreadExecutor as well as inline
+        //          executor.
+        //       3. Make test to verify that Nat-T keepalive socket is created by IpSecService.
+        final int srcPort = 12345;
+        final InetAddress myIPv4 = InetAddress.getByName("192.0.2.129");
+        final InetAddress notMyIPv4 = InetAddress.getByName("192.0.2.35");
+        final InetAddress myIPv6 = InetAddress.getByName("2001:db8::1");
+        final InetAddress dstIPv4 = InetAddress.getByName("8.8.8.8");
+        final InetAddress dstIPv6 = InetAddress.getByName("2001:4860:4860::8888");
+
+        final int validKaInterval = 15;
+        final int invalidKaInterval = 9;
+
+        final IpSecManager mIpSec = (IpSecManager) mContext.getSystemService(Context.IPSEC_SERVICE);
+        final UdpEncapsulationSocket testSocket = mIpSec.openUdpEncapsulationSocket(srcPort);
+
+        final Executor executor = Executors.newSingleThreadExecutor();
+
+        LinkProperties lp = new LinkProperties();
+        lp.setInterfaceName("wlan12");
+        lp.addLinkAddress(new LinkAddress(myIPv6, 64));
+        lp.addLinkAddress(new LinkAddress(myIPv4, 25));
+        lp.addRoute(new RouteInfo(InetAddress.getByName("fe80::1234")));
+        lp.addRoute(new RouteInfo(InetAddress.getByName("192.0.2.254")));
+
+        Network notMyNet = new Network(61234);
+        Network myNet = connectKeepaliveNetwork(lp);
+
+        TestSocketKeepaliveCallback callback = new TestSocketKeepaliveCallback();
+        SocketKeepalive ka;
+
+        // Attempt to start keepalives with invalid parameters and check for errors.
+        // Invalid network.
+        ka = mCm.createSocketKeepalive(notMyNet, testSocket, myIPv4, dstIPv4, executor, callback);
+        ka.start(validKaInterval);
+        callback.expectError(SocketKeepalive.ERROR_INVALID_NETWORK);
+
+        // Invalid interval.
+        ka = mCm.createSocketKeepalive(myNet, testSocket, myIPv4, dstIPv4, executor, callback);
+        ka.start(invalidKaInterval);
+        callback.expectError(SocketKeepalive.ERROR_INVALID_INTERVAL);
+
+        // Invalid destination.
+        ka = mCm.createSocketKeepalive(myNet, testSocket, myIPv4, dstIPv6, executor, callback);
+        ka.start(validKaInterval);
+        callback.expectError(SocketKeepalive.ERROR_INVALID_IP_ADDRESS);
+
+        // Invalid source;
+        ka = mCm.createSocketKeepalive(myNet, testSocket, myIPv6, dstIPv4, executor, callback);
+        ka.start(validKaInterval);
+        callback.expectError(SocketKeepalive.ERROR_INVALID_IP_ADDRESS);
+
+        // NAT-T is only supported for IPv4.
+        ka = mCm.createSocketKeepalive(myNet, testSocket, myIPv6, dstIPv6, executor, callback);
+        ka.start(validKaInterval);
+        callback.expectError(SocketKeepalive.ERROR_INVALID_IP_ADDRESS);
+
+        // Sanity check before testing started keepalive.
+        ka = mCm.createSocketKeepalive(myNet, testSocket, myIPv4, dstIPv4, executor, callback);
+        ka.start(validKaInterval);
+        callback.expectError(SocketKeepalive.ERROR_HARDWARE_UNSUPPORTED);
+
+        // Check that a started keepalive can be stopped.
+        mWiFiNetworkAgent.setStartKeepaliveError(SocketKeepalive.SUCCESS);
+        ka = mCm.createSocketKeepalive(myNet, testSocket, myIPv4, dstIPv4, executor, callback);
+        ka.start(validKaInterval);
+        callback.expectStarted();
+        mWiFiNetworkAgent.setStopKeepaliveError(SocketKeepalive.SUCCESS);
+        ka.stop();
+        callback.expectStopped();
+
+        // Check that deleting the IP address stops the keepalive.
+        LinkProperties bogusLp = new LinkProperties(lp);
+        ka = mCm.createSocketKeepalive(myNet, testSocket, myIPv4, dstIPv4, executor, callback);
+        ka.start(validKaInterval);
+        callback.expectStarted();
+        bogusLp.removeLinkAddress(new LinkAddress(myIPv4, 25));
+        bogusLp.addLinkAddress(new LinkAddress(notMyIPv4, 25));
+        mWiFiNetworkAgent.sendLinkProperties(bogusLp);
+        callback.expectError(SocketKeepalive.ERROR_INVALID_IP_ADDRESS);
+        mWiFiNetworkAgent.sendLinkProperties(lp);
+
+        // Check that a started keepalive is stopped correctly when the network disconnects.
+        ka = mCm.createSocketKeepalive(myNet, testSocket, myIPv4, dstIPv4, executor, callback);
+        ka.start(validKaInterval);
+        callback.expectStarted();
+        mWiFiNetworkAgent.disconnect();
+        waitFor(mWiFiNetworkAgent.getDisconnectedCV());
+        callback.expectError(SocketKeepalive.ERROR_INVALID_NETWORK);
+
+        // ... and that stopping it after that has no adverse effects.
+        waitForIdle();
+        final Network myNetAlias = myNet;
+        assertNull(mCm.getNetworkCapabilities(myNetAlias));
+        ka.stop();
+
+        // Reconnect.
+        myNet = connectKeepaliveNetwork(lp);
+        mWiFiNetworkAgent.setStartKeepaliveError(SocketKeepalive.SUCCESS);
+
+        // Check things work as expected when the keepalive is stopped and the network disconnects.
+        ka = mCm.createSocketKeepalive(myNet, testSocket, myIPv4, dstIPv4, executor, callback);
+        ka.start(validKaInterval);
+        callback.expectStarted();
+        ka.stop();
+        mWiFiNetworkAgent.disconnect();
+        waitFor(mWiFiNetworkAgent.getDisconnectedCV());
+        waitForIdle();
+        callback.expectStopped();
+
+        // Reconnect.
+        myNet = connectKeepaliveNetwork(lp);
+        mWiFiNetworkAgent.setStartKeepaliveError(SocketKeepalive.SUCCESS);
+
+        // Check that keepalive slots start from 1 and increment. The first one gets slot 1.
+        mWiFiNetworkAgent.setExpectedKeepaliveSlot(1);
+        ka = mCm.createSocketKeepalive(myNet, testSocket, myIPv4, dstIPv4, executor, callback);
+        ka.start(validKaInterval);
+        callback.expectStarted();
+
+        // The second one gets slot 2.
+        mWiFiNetworkAgent.setExpectedKeepaliveSlot(2);
+        final UdpEncapsulationSocket testSocket2 = mIpSec.openUdpEncapsulationSocket(6789);
+        TestSocketKeepaliveCallback callback2 = new TestSocketKeepaliveCallback();
+        SocketKeepalive ka2 =
+                mCm.createSocketKeepalive(myNet, testSocket2, myIPv4, dstIPv4, executor, callback2);
+        ka2.start(validKaInterval);
+        callback2.expectStarted();
+
+        ka.stop();
+        callback.expectStopped();
+
+        ka2.stop();
+        callback2.expectStopped();
+    }
+
+    @Test
     public void testGetCaptivePortalServerUrl() throws Exception {
         String url = mCm.getCaptivePortalServerUrl();
         assertEquals("http://connectivitycheck.gstatic.com/generate_204", url);
@@ -4914,4 +5144,84 @@
         mCellNetworkAgent.sendLinkProperties(lp);
         verifyTcpBufferSizeChange(TEST_TCP_BUFFER_SIZES);
     }
+
+    @Test
+    public void testGetGlobalProxyForNetwork() {
+        final ProxyInfo testProxyInfo = ProxyInfo.buildDirectProxy("test", 8888);
+        mWiFiNetworkAgent = new MockNetworkAgent(TRANSPORT_WIFI);
+        final Network wifiNetwork = mWiFiNetworkAgent.getNetwork();
+        when(mService.mProxyTracker.getGlobalProxy()).thenReturn(testProxyInfo);
+        assertEquals(testProxyInfo, mService.getProxyForNetwork(wifiNetwork));
+    }
+
+    @Test
+    public void testGetProxyForActiveNetwork() {
+        final ProxyInfo testProxyInfo = ProxyInfo.buildDirectProxy("test", 8888);
+        mWiFiNetworkAgent = new MockNetworkAgent(TRANSPORT_WIFI);
+        mWiFiNetworkAgent.connect(true);
+        waitForIdle();
+        assertNull(mService.getProxyForNetwork(null));
+
+        final LinkProperties testLinkProperties = new LinkProperties();
+        testLinkProperties.setHttpProxy(testProxyInfo);
+
+        mWiFiNetworkAgent.sendLinkProperties(testLinkProperties);
+        waitForIdle();
+
+        assertEquals(testProxyInfo, mService.getProxyForNetwork(null));
+    }
+
+    @Test
+    public void testGetProxyForVPN() {
+        final ProxyInfo testProxyInfo = ProxyInfo.buildDirectProxy("test", 8888);
+
+        // Set up a WiFi network with no proxy
+        mWiFiNetworkAgent = new MockNetworkAgent(TRANSPORT_WIFI);
+        mWiFiNetworkAgent.connect(true);
+        waitForIdle();
+        assertNull(mService.getProxyForNetwork(null));
+
+        // Set up a VPN network with a proxy
+        final int uid = Process.myUid();
+        final MockNetworkAgent vpnNetworkAgent = new MockNetworkAgent(TRANSPORT_VPN);
+        final ArraySet<UidRange> ranges = new ArraySet<>();
+        ranges.add(new UidRange(uid, uid));
+        mMockVpn.setUids(ranges);
+        LinkProperties testLinkProperties = new LinkProperties();
+        testLinkProperties.setHttpProxy(testProxyInfo);
+        vpnNetworkAgent.sendLinkProperties(testLinkProperties);
+        waitForIdle();
+
+        // Connect to VPN with proxy
+        mMockVpn.setNetworkAgent(vpnNetworkAgent);
+        vpnNetworkAgent.connect(true);
+        mMockVpn.connect();
+        waitForIdle();
+
+        // Test that the VPN network returns a proxy, and the WiFi does not.
+        assertEquals(testProxyInfo, mService.getProxyForNetwork(vpnNetworkAgent.getNetwork()));
+        assertEquals(testProxyInfo, mService.getProxyForNetwork(null));
+        assertNull(mService.getProxyForNetwork(mWiFiNetworkAgent.getNetwork()));
+
+        // Test that the VPN network returns no proxy when it is set to null.
+        testLinkProperties.setHttpProxy(null);
+        vpnNetworkAgent.sendLinkProperties(testLinkProperties);
+        waitForIdle();
+        assertNull(mService.getProxyForNetwork(vpnNetworkAgent.getNetwork()));
+        assertNull(mService.getProxyForNetwork(null));
+
+        // Set WiFi proxy and check that the vpn proxy is still null.
+        testLinkProperties.setHttpProxy(testProxyInfo);
+        mWiFiNetworkAgent.sendLinkProperties(testLinkProperties);
+        waitForIdle();
+        assertNull(mService.getProxyForNetwork(null));
+
+        // Disconnect from VPN and check that the active network, which is now the WiFi, has the
+        // correct proxy setting.
+        vpnNetworkAgent.disconnect();
+        waitForIdle();
+        assertEquals(mWiFiNetworkAgent.getNetwork(), mCm.getActiveNetwork());
+        assertEquals(testProxyInfo, mService.getProxyForNetwork(mWiFiNetworkAgent.getNetwork()));
+        assertEquals(testProxyInfo, mService.getProxyForNetwork(null));
+    }
 }
diff --git a/tests/net/java/com/android/server/connectivity/NetworkNotificationManagerTest.java b/tests/net/java/com/android/server/connectivity/NetworkNotificationManagerTest.java
index 125fe725..273b8fc 100644
--- a/tests/net/java/com/android/server/connectivity/NetworkNotificationManagerTest.java
+++ b/tests/net/java/com/android/server/connectivity/NetworkNotificationManagerTest.java
@@ -17,6 +17,7 @@
 package com.android.server.connectivity;
 
 import static com.android.server.connectivity.NetworkNotificationManager.NotificationType.*;
+
 import static org.mockito.Mockito.any;
 import static org.mockito.Mockito.anyInt;
 import static org.mockito.Mockito.eq;
@@ -34,26 +35,24 @@
 import android.content.res.Resources;
 import android.net.NetworkCapabilities;
 import android.net.NetworkInfo;
-import android.support.test.runner.AndroidJUnit4;
 import android.support.test.filters.SmallTest;
+import android.support.test.runner.AndroidJUnit4;
 import android.telephony.TelephonyManager;
 
 import com.android.server.connectivity.NetworkNotificationManager.NotificationType;
 
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.mockito.ArgumentCaptor;
+import org.mockito.Mock;
+import org.mockito.MockitoAnnotations;
+
 import java.util.ArrayList;
 import java.util.Arrays;
 import java.util.Collections;
 import java.util.List;
 
-import org.junit.runner.RunWith;
-import org.junit.Before;
-import org.junit.Test;
-import org.mockito.ArgumentCaptor;
-import org.mockito.Mock;
-import org.mockito.Mockito;
-import org.mockito.MockitoAnnotations;
-
-
 @RunWith(AndroidJUnit4.class)
 @SmallTest
 public class NetworkNotificationManagerTest {
@@ -194,4 +193,54 @@
         mManager.clearNotification(id);
         verify(mNotificationManager, times(1)).cancelAsUser(eq(tag), eq(SIGN_IN.eventId), any());
     }
+
+    @Test
+    public void testSameLevelNotifications() {
+        final int id = 101;
+        final String tag = NetworkNotificationManager.tagFor(id);
+
+        mManager.showNotification(id, LOGGED_IN, mWifiNai, mCellNai, null, false);
+        verify(mNotificationManager, times(1))
+                .notifyAsUser(eq(tag), eq(LOGGED_IN.eventId), any(), any());
+
+        mManager.showNotification(id, LOST_INTERNET, mWifiNai, mCellNai, null, false);
+        verify(mNotificationManager, times(1))
+                .notifyAsUser(eq(tag), eq(LOST_INTERNET.eventId), any(), any());
+    }
+
+    @Test
+    public void testClearNotificationByType() {
+        final int id = 101;
+        final String tag = NetworkNotificationManager.tagFor(id);
+
+        // clearNotification(int id, NotificationType notifyType) will check if given type is equal
+        // to previous type or not. If they are equal then clear the notification; if they are not
+        // equal then return.
+
+        mManager.showNotification(id, LOGGED_IN, mWifiNai, mCellNai, null, false);
+        verify(mNotificationManager, times(1))
+                .notifyAsUser(eq(tag), eq(LOGGED_IN.eventId), any(), any());
+
+        // Previous notification is LOGGED_IN and given type is LOGGED_IN too. The notification
+        // should be cleared.
+        mManager.clearNotification(id, LOGGED_IN);
+        verify(mNotificationManager, times(1))
+                .cancelAsUser(eq(tag), eq(LOGGED_IN.eventId), any());
+
+        mManager.showNotification(id, LOGGED_IN, mWifiNai, mCellNai, null, false);
+        verify(mNotificationManager, times(2))
+                .notifyAsUser(eq(tag), eq(LOGGED_IN.eventId), any(), any());
+
+        // LOST_INTERNET notification popup after LOGGED_IN notification.
+        mManager.showNotification(id, LOST_INTERNET, mWifiNai, mCellNai, null, false);
+        verify(mNotificationManager, times(1))
+                .notifyAsUser(eq(tag), eq(LOST_INTERNET.eventId), any(), any());
+
+        // Previous notification is LOST_INTERNET and given type is LOGGED_IN. The notification
+        // shouldn't be cleared.
+        mManager.clearNotification(id, LOGGED_IN);
+        // LOST_INTERNET shouldn't be cleared.
+        verify(mNotificationManager, never())
+                .cancelAsUser(eq(tag), eq(LOST_INTERNET.eventId), any());
+    }
 }
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 2e43150..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;
@@ -53,7 +54,11 @@
   // Load the binary xml tree
   android::ResXMLTree tree;
   std::unique_ptr<LoadedApk> apk = LoadedApk::LoadApkFromPath(out_convert_apk, &diag);
-  AssertLoadXml(apk.get(), "res/xml/test.xml", &tree);
+
+  std::unique_ptr<io::IData> data = OpenFileAsData(apk.get(), "res/xml/test.xml");
+  ASSERT_THAT(data, Ne(nullptr));
+
+  AssertLoadXml(apk.get(), data.get(), &tree);
 
   // Check that the raw string index has not been assigned
   EXPECT_THAT(tree.getAttributeValueStringID(0), Eq(-1));
@@ -87,7 +92,11 @@
   // Load the binary xml tree
   android::ResXMLTree tree;
   std::unique_ptr<LoadedApk> apk = LoadedApk::LoadApkFromPath(out_convert_apk, &diag);
-  AssertLoadXml(apk.get(), "res/xml/test.xml", &tree);
+
+  std::unique_ptr<io::IData> data = OpenFileAsData(apk.get(), "res/xml/test.xml");
+  ASSERT_THAT(data, Ne(nullptr));
+
+  AssertLoadXml(apk.get(), data.get(), &tree);
 
   // Check that the raw string index has been set to the correct string pool entry
   int32_t raw_index = tree.getAttributeValueStringID(0);
@@ -95,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/cmd/Link_test.cpp b/tools/aapt2/cmd/Link_test.cpp
index 3c8b72d..9ea93f6 100644
--- a/tools/aapt2/cmd/Link_test.cpp
+++ b/tools/aapt2/cmd/Link_test.cpp
@@ -43,7 +43,11 @@
   // Load the binary xml tree
   android::ResXMLTree tree;
   std::unique_ptr<LoadedApk> apk = LoadedApk::LoadApkFromPath(out_apk, &diag);
-  AssertLoadXml(apk.get(), "res/xml/test.xml", &tree);
+
+  std::unique_ptr<io::IData> data = OpenFileAsData(apk.get(), "res/xml/test.xml");
+  ASSERT_THAT(data, Ne(nullptr));
+
+  AssertLoadXml(apk.get(), data.get(), &tree);
 
   // Check that the raw string index has not been assigned
   EXPECT_THAT(tree.getAttributeValueStringID(0), Eq(-1));
@@ -67,7 +71,11 @@
   // Load the binary xml tree
   android::ResXMLTree tree;
   std::unique_ptr<LoadedApk> apk = LoadedApk::LoadApkFromPath(out_apk, &diag);
-  AssertLoadXml(apk.get(), "res/xml/test.xml", &tree);
+
+  std::unique_ptr<io::IData> data = OpenFileAsData(apk.get(), "res/xml/test.xml");
+  ASSERT_THAT(data, Ne(nullptr));
+
+  AssertLoadXml(apk.get(), data.get(), &tree);
 
   // Check that the raw string index has been set to the correct string pool entry
   int32_t raw_index = tree.getAttributeValueStringID(0);
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/aapt2/test/Fixture.cpp b/tools/aapt2/test/Fixture.cpp
index aae79fa..3fcdfb7 100644
--- a/tools/aapt2/test/Fixture.cpp
+++ b/tools/aapt2/test/Fixture.cpp
@@ -133,16 +133,18 @@
   return manifest_file;
 }
 
-void CommandTestFixture::AssertLoadXml(LoadedApk *apk, const android::StringPiece &xml_path,
+std::unique_ptr<io::IData> CommandTestFixture::OpenFileAsData(LoadedApk* apk,
+                                                              const android::StringPiece& path) {
+  return apk
+      ->GetFileCollection()
+      ->FindFile(path)
+      ->OpenAsData();
+}
+
+void CommandTestFixture::AssertLoadXml(LoadedApk* apk, const io::IData* data,
                                        android::ResXMLTree *out_tree) {
   ASSERT_THAT(apk, Ne(nullptr));
 
-  io::IFile* file = apk->GetFileCollection()->FindFile(xml_path);
-  ASSERT_THAT(file, Ne(nullptr));
-
-  std::unique_ptr<io::IData> data = file->OpenAsData();
-  ASSERT_THAT(data, Ne(nullptr));
-
   out_tree->setTo(data->data(), data->size());
   ASSERT_THAT(out_tree->getError(), Eq(android::OK));
   while (out_tree->next() != android::ResXMLTree::START_TAG) {
diff --git a/tools/aapt2/test/Fixture.h b/tools/aapt2/test/Fixture.h
index 89d3b7b..3079c75 100644
--- a/tools/aapt2/test/Fixture.h
+++ b/tools/aapt2/test/Fixture.h
@@ -83,8 +83,12 @@
   // Creates a minimal android manifest within the test directory and returns the file path.
   std::string GetDefaultManifest();
 
+  // Returns pointer to data inside APK files
+  std::unique_ptr<io::IData> OpenFileAsData(LoadedApk* apk,
+                                            const android::StringPiece& path);
+
   // Asserts that loading the tree from the specified file in the apk succeeds.
-  void AssertLoadXml(LoadedApk* apk, const android::StringPiece& xml_path,
+  void AssertLoadXml(LoadedApk* apk, const io::IData* data,
                      android::ResXMLTree* out_tree);
 
  private:
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/Android.bp b/tools/processors/view_inspector/Android.bp
index 9b5df56..06ff05e 100644
--- a/tools/processors/view_inspector/Android.bp
+++ b/tools/processors/view_inspector/Android.bp
@@ -1,6 +1,8 @@
-java_library_host {
+java_plugin {
     name: "view-inspector-annotation-processor",
 
+    processor_class: "android.processor.view.inspector.PlatformInspectableProcessor",
+
     srcs: ["src/java/**/*.java"],
     java_resource_dirs: ["src/resources"],
 
diff --git a/tools/signedconfig/debug_key.pem b/tools/signedconfig/debug_key.pem
index 0af577b..17a1dff 100644
--- a/tools/signedconfig/debug_key.pem
+++ b/tools/signedconfig/debug_key.pem
@@ -1,5 +1,5 @@
 -----BEGIN EC PRIVATE KEY-----
-MHcCAQEEIEfgtO+KPOoqJqTnqkDDKkAcOzyvtovsUO/ShLE6y4XRoAoGCCqGSM49
-AwEHoUQDQgAEaAn2XVifsLTHg616nTsOMVmlhBoECGbTEBTKKvdd2hO60pj1pnU8
-SMkhYfaNxZuKgw9LNvOwlFwStboIYeZ3lQ==
+MHcCAQEEIFbNNr1/TsFlvnmH1z6e0xyact9t7PDs+VFWc7QFtoRcoAoGCCqGSM49
+AwEHoUQDQgAEmJKs4lSn+XRhMQmMid+Zbhbu13YrU1haIhVC5296InRu1x7A8PV1
+ejQyisBODGgRY6pqkAHRncBCYcgg5wIIJg==
 -----END EC PRIVATE KEY-----
diff --git a/tools/signedconfig/debug_public.pem b/tools/signedconfig/debug_public.pem
index f61f813..d9f0d38 100644
--- a/tools/signedconfig/debug_public.pem
+++ b/tools/signedconfig/debug_public.pem
@@ -1,4 +1,4 @@
 -----BEGIN PUBLIC KEY-----
-MFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAEaAn2XVifsLTHg616nTsOMVmlhBoE
-CGbTEBTKKvdd2hO60pj1pnU8SMkhYfaNxZuKgw9LNvOwlFwStboIYeZ3lQ==
+MFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAEmJKs4lSn+XRhMQmMid+Zbhbu13Yr
+U1haIhVC5296InRu1x7A8PV1ejQyisBODGgRY6pqkAHRncBCYcgg5wIIJg==
 -----END PUBLIC KEY-----
diff --git a/tools/signedconfig/debug_sign.sh b/tools/signedconfig/debug_sign.sh
index 28e5428..3a2814a 100755
--- a/tools/signedconfig/debug_sign.sh
+++ b/tools/signedconfig/debug_sign.sh
@@ -2,5 +2,5 @@
 # Script to sign data with the debug keys. Outputs base64 for embedding into
 # APK metadata.
 
-openssl dgst -sha256 -sign $(dirname $0)/debug_key.pem  $1 | base64 -w 0
+openssl dgst -sha256 -sign $(dirname $0)/debug_key.pem  <(echo -n "$1" | base64 -d) | base64 -w 0
 echo
diff --git a/wifi/java/android/net/wifi/IWifiManager.aidl b/wifi/java/android/net/wifi/IWifiManager.aidl
index 46c4191..d549799 100644
--- a/wifi/java/android/net/wifi/IWifiManager.aidl
+++ b/wifi/java/android/net/wifi/IWifiManager.aidl
@@ -28,6 +28,7 @@
 import android.net.wifi.INetworkRequestMatchCallback;
 import android.net.wifi.ISoftApCallback;
 import android.net.wifi.ITrafficStateCallback;
+import android.net.wifi.IWifiUsabilityStatsListener;
 import android.net.wifi.PasspointManagementObjectDefinition;
 import android.net.wifi.ScanResult;
 import android.net.wifi.WifiActivityEnergyInfo;
@@ -186,6 +187,10 @@
 
     void unregisterSoftApCallback(int callbackIdentifier);
 
+    void addWifiUsabilityStatsListener(in IBinder binder, in IWifiUsabilityStatsListener listener, int listenerIdentifier);
+
+    void removeWifiUsabilityStatsListener(int listenerIdentifier);
+
     void registerTrafficStateCallback(in IBinder binder, in ITrafficStateCallback callback, int callbackIdentifier);
 
     void unregisterTrafficStateCallback(int callbackIdentifier);
@@ -209,5 +214,6 @@
         in IDppCallback callback);
 
     void stopDppSession();
-}
 
+    void updateWifiUsabilityScore(int seqNum, int score, int predictionHorizonSec);
+}
diff --git a/wifi/java/android/net/wifi/IWifiUsabilityStatsListener.aidl b/wifi/java/android/net/wifi/IWifiUsabilityStatsListener.aidl
new file mode 100644
index 0000000..284ffaa
--- /dev/null
+++ b/wifi/java/android/net/wifi/IWifiUsabilityStatsListener.aidl
@@ -0,0 +1,41 @@
+/*
+ * 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.wifi;
+
+import android.net.wifi.WifiUsabilityStatsEntry;
+
+/**
+ * Interface for Wi-Fi usability stats listener.
+ *
+ * @hide
+ */
+oneway interface IWifiUsabilityStatsListener
+{
+    /**
+     * Service to manager callback providing current Wi-Fi usability stats.
+     *
+     * @param seqNum The sequence number of stats, used to derive the timing of updated Wi-Fi
+     *               usability statistics, set by framework and shall be incremented by one
+     *               after each update.
+     * @param isSameBssidAndFreq The flag to indicate whether the BSSID and the frequency of
+     *                           network stays the same or not relative to the last update of
+     *                           Wi-Fi usability stats.
+     * @param stats The updated Wi-Fi usability statistics.
+     */
+    void onStatsUpdated(int seqNum, boolean isSameBssidAndFreq,
+            in WifiUsabilityStatsEntry stats);
+}
diff --git a/wifi/java/android/net/wifi/WifiConfiguration.java b/wifi/java/android/net/wifi/WifiConfiguration.java
index d2d711f..96493de 100644
--- a/wifi/java/android/net/wifi/WifiConfiguration.java
+++ b/wifi/java/android/net/wifi/WifiConfiguration.java
@@ -787,6 +787,18 @@
     public boolean trusted;
 
     /**
+     * This Wifi configuration is created from a {@link WifiNetworkSuggestion}
+     * @hide
+     */
+    public boolean fromWifiNetworkSuggestion;
+
+    /**
+     * This Wifi configuration is created from a {@link WifiNetworkSpecifier}
+     * @hide
+     */
+    public boolean fromWifiNetworkSpecifier;
+
+    /**
      * Indicates if the creator of this configuration has expressed that it
      * should be considered metered.
      *
@@ -1668,6 +1680,8 @@
         ephemeral = false;
         osu = false;
         trusted = true; // Networks are considered trusted by default.
+        fromWifiNetworkSuggestion = false;
+        fromWifiNetworkSpecifier = false;
         meteredHint = false;
         meteredOverride = METERED_OVERRIDE_NONE;
         useExternalScores = false;
@@ -1779,10 +1793,13 @@
         if (this.ephemeral) sbuf.append(" ephemeral");
         if (this.osu) sbuf.append(" osu");
         if (this.trusted) sbuf.append(" trusted");
+        if (this.fromWifiNetworkSuggestion) sbuf.append(" fromWifiNetworkSuggestion");
+        if (this.fromWifiNetworkSpecifier) sbuf.append(" fromWifiNetworkSpecifier");
         if (this.meteredHint) sbuf.append(" meteredHint");
         if (this.useExternalScores) sbuf.append(" useExternalScores");
         if (this.didSelfAdd || this.selfAdded || this.validatedInternetAccess
-                || this.ephemeral || this.trusted || this.meteredHint || this.useExternalScores) {
+                || this.ephemeral || this.trusted || this.fromWifiNetworkSuggestion
+                || this.fromWifiNetworkSpecifier || this.meteredHint || this.useExternalScores) {
             sbuf.append("\n");
         }
         if (this.meteredOverride != METERED_OVERRIDE_NONE) {
@@ -2270,6 +2287,8 @@
             ephemeral = source.ephemeral;
             osu = source.osu;
             trusted = source.trusted;
+            fromWifiNetworkSuggestion = source.fromWifiNetworkSuggestion;
+            fromWifiNetworkSpecifier = source.fromWifiNetworkSpecifier;
             meteredHint = source.meteredHint;
             meteredOverride = source.meteredOverride;
             useExternalScores = source.useExternalScores;
@@ -2347,6 +2366,8 @@
         dest.writeInt(isLegacyPasspointConfig ? 1 : 0);
         dest.writeInt(ephemeral ? 1 : 0);
         dest.writeInt(trusted ? 1 : 0);
+        dest.writeInt(fromWifiNetworkSuggestion ? 1 : 0);
+        dest.writeInt(fromWifiNetworkSpecifier ? 1 : 0);
         dest.writeInt(meteredHint ? 1 : 0);
         dest.writeInt(meteredOverride);
         dest.writeInt(useExternalScores ? 1 : 0);
@@ -2418,6 +2439,8 @@
                 config.isLegacyPasspointConfig = in.readInt() != 0;
                 config.ephemeral = in.readInt() != 0;
                 config.trusted = in.readInt() != 0;
+                config.fromWifiNetworkSuggestion =  in.readInt() != 0;
+                config.fromWifiNetworkSpecifier =  in.readInt() != 0;
                 config.meteredHint = in.readInt() != 0;
                 config.meteredOverride = in.readInt();
                 config.useExternalScores = in.readInt() != 0;
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 1fd45e7..0668239 100644
--- a/wifi/java/android/net/wifi/WifiManager.java
+++ b/wifi/java/android/net/wifi/WifiManager.java
@@ -16,7 +16,7 @@
 
 package android.net.wifi;
 
-import static android.Manifest.permission.ACCESS_COARSE_LOCATION;
+import static android.Manifest.permission.ACCESS_FINE_LOCATION;
 import static android.Manifest.permission.ACCESS_WIFI_STATE;
 import static android.Manifest.permission.READ_WIFI_CREDENTIAL;
 
@@ -950,8 +950,7 @@
      * which was created with {@link WifiNetworkConfigBuilder#setIsAppInteractionRequired()} flag
      * set.
      * <p>
-     * Note: The broadcast is sent to the app only if it holds either one of
-     * {@link android.Manifest.permission#ACCESS_COARSE_LOCATION ACCESS_COARSE_LOCATION} or
+     * Note: The broadcast is sent to the app only if it holds
      * {@link android.Manifest.permission#ACCESS_FINE_LOCATION ACCESS_FINE_LOCATION} permission.
      *
      * @see #EXTRA_NETWORK_SUGGESTION
@@ -1183,7 +1182,7 @@
      * containing configurations which they created.
      */
     @Deprecated
-    @RequiresPermission(allOf = {ACCESS_COARSE_LOCATION, ACCESS_WIFI_STATE})
+    @RequiresPermission(allOf = {ACCESS_FINE_LOCATION, ACCESS_WIFI_STATE})
     public List<WifiConfiguration> getConfiguredNetworks() {
         try {
             ParceledListSlice<WifiConfiguration> parceledList =
@@ -1199,7 +1198,7 @@
 
     /** @hide */
     @SystemApi
-    @RequiresPermission(allOf = {ACCESS_COARSE_LOCATION, ACCESS_WIFI_STATE, READ_WIFI_CREDENTIAL})
+    @RequiresPermission(allOf = {ACCESS_FINE_LOCATION, ACCESS_WIFI_STATE, READ_WIFI_CREDENTIAL})
     public List<WifiConfiguration> getPrivilegedConfiguredNetworks() {
         try {
             ParceledListSlice<WifiConfiguration> parceledList =
@@ -1405,7 +1404,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.
@@ -1429,7 +1427,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
@@ -1606,7 +1603,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) {
@@ -1636,7 +1632,6 @@
      * @param callback Callback for network match events
      * @hide
      */
-    @SystemApi
     @RequiresPermission(android.Manifest.permission.NETWORK_SETTINGS)
     public void unregisterNetworkRequestMatchCallback(
             @NonNull NetworkRequestMatchCallback callback) {
@@ -1656,8 +1651,7 @@
      * When the device decides to connect to one of the provided network suggestions, platform sends
      * a directed broadcast {@link #ACTION_WIFI_NETWORK_SUGGESTION_POST_CONNECTION} to the app if
      * the network was created with {@link WifiNetworkConfigBuilder#setIsAppInteractionRequired()}
-     * flag set and the app holds either one of
-     * {@link android.Manifest.permission#ACCESS_COARSE_LOCATION ACCESS_COARSE_LOCATION} or
+     * flag set and the app holds
      * {@link android.Manifest.permission#ACCESS_FINE_LOCATION ACCESS_FINE_LOCATION} permission.
      *<p>
      * NOTE:
@@ -2290,7 +2284,6 @@
     /**
      * Return the results of the latest access point scan.
      * @return the list of access points found in the most recent scan. An app must hold
-     * {@link android.Manifest.permission#ACCESS_COARSE_LOCATION ACCESS_COARSE_LOCATION} or
      * {@link android.Manifest.permission#ACCESS_FINE_LOCATION ACCESS_FINE_LOCATION} permission
      * in order to get valid results.
      */
@@ -2601,7 +2594,7 @@
      * <p>
      * Applications need to have the following permissions to start LocalOnlyHotspot: {@link
      * android.Manifest.permission#CHANGE_WIFI_STATE} and {@link
-     * android.Manifest.permission#ACCESS_COARSE_LOCATION ACCESS_COARSE_LOCATION}.  Callers without
+     * android.Manifest.permission#ACCESS_FINE_LOCATION ACCESS_FINE_LOCATION}.  Callers without
      * the permissions will trigger a {@link java.lang.SecurityException}.
      * <p>
      * @param callback LocalOnlyHotspotCallback for the application to receive updates about
@@ -2684,7 +2677,7 @@
      * {@link LocalOnlyHotspotObserver#onStopped()} callbacks.
      * <p>
      * Applications should have the
-     * {@link android.Manifest.permission#ACCESS_COARSE_LOCATION ACCESS_COARSE_LOCATION}
+     * {@link android.Manifest.permission#ACCESS_FINE_LOCATION ACCESS_FINE_LOCATION}
      * permission.  Callers without the permission will trigger a
      * {@link java.lang.SecurityException}.
      * <p>
@@ -4777,4 +4770,114 @@
             });
         }
     }
-}
+
+    /**
+     * Interface for Wi-Fi usability statistics listener. Should be implemented by applications and
+     * set when calling {@link WifiManager#addWifiUsabilityStatsListener(Executor,
+     * WifiUsabilityStatsListener)}.
+     *
+     * @hide
+     */
+    @SystemApi
+    public interface WifiUsabilityStatsListener {
+        /**
+         * Called when Wi-Fi usability statistics is updated.
+         *
+         * @param seqNum The sequence number of statistics, used to derive the timing of updated
+         *               Wi-Fi usability statistics, set by framework and incremented by one after
+         *               each update.
+         * @param isSameBssidAndFreq The flag to indicate whether the BSSID and the frequency of
+         *                           network stays the same or not relative to the last update of
+         *                           Wi-Fi usability stats.
+         * @param stats The updated Wi-Fi usability statistics.
+         */
+        void onStatsUpdated(int seqNum, boolean isSameBssidAndFreq,
+                WifiUsabilityStatsEntry stats);
+    }
+
+    /**
+     * Adds a listener for Wi-Fi usability statistics. See {@link WifiUsabilityStatsListener}.
+     * Multiple listeners can be added. Callers will be invoked periodically by framework to
+     * inform clients about the current Wi-Fi usability statistics. Callers can remove a previously
+     * added listener using {@link removeWifiUsabilityStatsListener}.
+     *
+     * @param executor The executor on which callback will be invoked.
+     * @param listener Listener for Wifi usability statistics.
+     *
+     * @hide
+     */
+    @SystemApi
+    @RequiresPermission(android.Manifest.permission.WIFI_UPDATE_USABILITY_STATS_SCORE)
+    public void addWifiUsabilityStatsListener(@NonNull @CallbackExecutor Executor executor,
+            @NonNull WifiUsabilityStatsListener listener) {
+        if (executor == null) throw new IllegalArgumentException("executor cannot be null");
+        if (listener == null) throw new IllegalArgumentException("listener cannot be null");
+        if (mVerboseLoggingEnabled) {
+            Log.v(TAG, "addWifiUsabilityStatsListener: listener=" + listener);
+        }
+        try {
+            mService.addWifiUsabilityStatsListener(new Binder(),
+                    new IWifiUsabilityStatsListener.Stub() {
+                        @Override
+                        public void onStatsUpdated(int seqNum, boolean isSameBssidAndFreq,
+                                WifiUsabilityStatsEntry stats) {
+                            if (mVerboseLoggingEnabled) {
+                                Log.v(TAG, "WifiUsabilityStatsListener: onStatsUpdated: seqNum="
+                                        + seqNum);
+                            }
+                            Binder.withCleanCallingIdentity(() ->
+                                    executor.execute(() -> listener.onStatsUpdated(seqNum,
+                                            isSameBssidAndFreq, stats)));
+                        }
+                    },
+                    listener.hashCode()
+            );
+        } catch (RemoteException e) {
+            throw e.rethrowFromSystemServer();
+        }
+    }
+
+    /**
+     * Allow callers to remove a previously registered listener. After calling this method,
+     * applications will no longer receive Wi-Fi usability statistics.
+     *
+     * @param listener Listener to remove the Wi-Fi usability statistics.
+     *
+     * @hide
+     */
+    @SystemApi
+    @RequiresPermission(android.Manifest.permission.WIFI_UPDATE_USABILITY_STATS_SCORE)
+    public void removeWifiUsabilityStatsListener(@NonNull WifiUsabilityStatsListener listener) {
+        if (listener == null) throw new IllegalArgumentException("listener cannot be null");
+        if (mVerboseLoggingEnabled) {
+            Log.v(TAG, "removeWifiUsabilityStatsListener: listener=" + listener);
+        }
+        try {
+            mService.removeWifiUsabilityStatsListener(listener.hashCode());
+        } catch (RemoteException e) {
+            throw e.rethrowFromSystemServer();
+        }
+    }
+
+    /**
+     * Provide a Wi-Fi usability score information to be recorded (but not acted upon) by the
+     * framework. The Wi-Fi usability score is derived from {@link WifiUsabilityStatsListener}
+     * where a score is matched to Wi-Fi usability statistics using the sequence number. The score
+     * is used to quantify whether Wi-Fi is usable in a future time.
+     *
+     * @param seqNum Sequence number of the Wi-Fi usability score.
+     * @param score The Wi-Fi usability score.
+     * @param predictionHorizonSec Prediction horizon of the Wi-Fi usability score.
+     *
+     * @hide
+     */
+    @SystemApi
+    @RequiresPermission(android.Manifest.permission.WIFI_UPDATE_USABILITY_STATS_SCORE)
+    public void updateWifiUsabilityScore(int seqNum, int score, int predictionHorizonSec) {
+        try {
+            mService.updateWifiUsabilityScore(seqNum, score, predictionHorizonSec);
+        } catch (RemoteException e) {
+            throw e.rethrowFromSystemServer();
+        }
+    }
+}
\ No newline at end of file
diff --git a/services/net/java/android/net/dhcp/DhcpClient.java b/wifi/java/android/net/wifi/WifiUsabilityStatsEntry.aidl
similarity index 61%
copy from services/net/java/android/net/dhcp/DhcpClient.java
copy to wifi/java/android/net/wifi/WifiUsabilityStatsEntry.aidl
index cddb91f..839af54 100644
--- a/services/net/java/android/net/dhcp/DhcpClient.java
+++ b/wifi/java/android/net/wifi/WifiUsabilityStatsEntry.aidl
@@ -14,16 +14,6 @@
  * limitations under the License.
  */
 
-package android.net.dhcp;
+package android.net.wifi;
 
-/**
- * TODO: remove this class after migrating clients.
- */
-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;
-}
+parcelable WifiUsabilityStatsEntry;
diff --git a/wifi/java/android/net/wifi/WifiUsabilityStatsEntry.java b/wifi/java/android/net/wifi/WifiUsabilityStatsEntry.java
new file mode 100644
index 0000000..c796e29
--- /dev/null
+++ b/wifi/java/android/net/wifi/WifiUsabilityStatsEntry.java
@@ -0,0 +1,148 @@
+/*
+ * 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.wifi;
+
+import android.annotation.SystemApi;
+import android.os.Parcel;
+import android.os.Parcelable;
+
+/**
+ * This class makes a subset of
+ * com.android.server.wifi.nano.WifiMetricsProto.WifiUsabilityStatsEntry parcelable.
+ *
+ * @hide
+ */
+@SystemApi
+public final class WifiUsabilityStatsEntry implements Parcelable {
+    /** Absolute milliseconds from device boot when these stats were sampled */
+    public final long timeStampMs;
+    /** The RSSI (in dBm) at the sample time */
+    public final int rssi;
+    /** Link speed at the sample time in Mbps */
+    public final int linkSpeedMbps;
+    /** The total number of tx success counted from the last radio chip reset */
+    public final long totalTxSuccess;
+    /** The total number of MPDU data packet retries counted from the last radio chip reset */
+    public final long totalTxRetries;
+    /** The total number of tx bad counted from the last radio chip reset */
+    public final long totalTxBad;
+    /** The total number of rx success counted from the last radio chip reset */
+    public final long totalRxSuccess;
+    /** The total time the wifi radio is on in ms counted from the last radio chip reset */
+    public final long totalRadioOnTimeMs;
+    /** The total time the wifi radio is doing tx in ms counted from the last radio chip reset */
+    public final long totalRadioTxTimeMs;
+    /** The total time the wifi radio is doing rx in ms counted from the last radio chip reset */
+    public final long totalRadioRxTimeMs;
+    /** The total time spent on all types of scans in ms counted from the last radio chip reset */
+    public final long totalScanTimeMs;
+    /** The total time spent on nan scans in ms counted from the last radio chip reset */
+    public final long totalNanScanTimeMs;
+    /** The total time spent on background scans in ms counted from the last radio chip reset */
+    public final long totalBackgroundScanTimeMs;
+    /** The total time spent on roam scans in ms counted from the last radio chip reset */
+    public final long totalRoamScanTimeMs;
+    /** The total time spent on pno scans in ms counted from the last radio chip reset */
+    public final long totalPnoScanTimeMs;
+    /** The total time spent on hotspot2.0 scans and GAS exchange in ms counted from the last radio
+     * chip reset */
+    public final long totalHotspot2ScanTimeMs;
+    /** The total time CCA is on busy status on the current frequency in ms counted from the last
+     * radio chip reset */
+    public final long totalCcaBusyFreqTimeMs;
+    /** The total radio on time of the current frequency from the last radio chip reset */
+    public final long totalRadioOnFreqTimeMs;
+    /** The total number of beacons received from the last radio chip reset */
+    public final long totalBeaconRx;
+
+    /** Constructor function {@hide} */
+    public WifiUsabilityStatsEntry(long timeStampMs, int rssi,
+            int linkSpeedMbps, long totalTxSuccess, long totalTxRetries,
+            long totalTxBad, long totalRxSuccess, long totalRadioOnTimeMs,
+            long totalRadioTxTimeMs, long totalRadioRxTimeMs, long totalScanTimeMs,
+            long totalNanScanTimeMs, long totalBackgroundScanTimeMs, long totalRoamScanTimeMs,
+            long totalPnoScanTimeMs, long totalHotspot2ScanTimeMs, long totalCcaBusyFreqTimeMs,
+            long totalRadioOnFreqTimeMs, long totalBeaconRx) {
+        this.timeStampMs = timeStampMs;
+        this.rssi = rssi;
+        this.linkSpeedMbps = linkSpeedMbps;
+        this.totalTxSuccess = totalTxSuccess;
+        this.totalTxRetries = totalTxRetries;
+        this.totalTxBad = totalTxBad;
+        this.totalRxSuccess = totalRxSuccess;
+        this.totalRadioOnTimeMs = totalRadioOnTimeMs;
+        this.totalRadioTxTimeMs = totalRadioTxTimeMs;
+        this.totalRadioRxTimeMs = totalRadioRxTimeMs;
+        this.totalScanTimeMs = totalScanTimeMs;
+        this.totalNanScanTimeMs = totalNanScanTimeMs;
+        this.totalBackgroundScanTimeMs = totalBackgroundScanTimeMs;
+        this.totalRoamScanTimeMs = totalRoamScanTimeMs;
+        this.totalPnoScanTimeMs = totalPnoScanTimeMs;
+        this.totalHotspot2ScanTimeMs = totalHotspot2ScanTimeMs;
+        this.totalCcaBusyFreqTimeMs = totalCcaBusyFreqTimeMs;
+        this.totalRadioOnFreqTimeMs = totalRadioOnFreqTimeMs;
+        this.totalBeaconRx = totalBeaconRx;
+    }
+
+    /** Implement the Parcelable interface */
+    public int describeContents() {
+        return 0;
+    }
+
+    /** Implement the Parcelable interface */
+    public void writeToParcel(Parcel dest, int flags) {
+        dest.writeLong(timeStampMs);
+        dest.writeInt(rssi);
+        dest.writeInt(linkSpeedMbps);
+        dest.writeLong(totalTxSuccess);
+        dest.writeLong(totalTxRetries);
+        dest.writeLong(totalTxBad);
+        dest.writeLong(totalRxSuccess);
+        dest.writeLong(totalRadioOnTimeMs);
+        dest.writeLong(totalRadioTxTimeMs);
+        dest.writeLong(totalRadioRxTimeMs);
+        dest.writeLong(totalScanTimeMs);
+        dest.writeLong(totalNanScanTimeMs);
+        dest.writeLong(totalBackgroundScanTimeMs);
+        dest.writeLong(totalRoamScanTimeMs);
+        dest.writeLong(totalPnoScanTimeMs);
+        dest.writeLong(totalHotspot2ScanTimeMs);
+        dest.writeLong(totalCcaBusyFreqTimeMs);
+        dest.writeLong(totalRadioOnFreqTimeMs);
+        dest.writeLong(totalBeaconRx);
+    }
+
+    /** Implement the Parcelable interface */
+    public static final Creator<WifiUsabilityStatsEntry> CREATOR =
+            new Creator<WifiUsabilityStatsEntry>() {
+        public WifiUsabilityStatsEntry createFromParcel(Parcel in) {
+            return new WifiUsabilityStatsEntry(
+                    in.readLong(), in.readInt(),
+                    in.readInt(), in.readLong(), in.readLong(),
+                    in.readLong(), in.readLong(), in.readLong(),
+                    in.readLong(), in.readLong(), in.readLong(),
+                    in.readLong(), in.readLong(), in.readLong(),
+                    in.readLong(), in.readLong(), in.readLong(),
+                    in.readLong(), in.readLong()
+            );
+        }
+
+        public WifiUsabilityStatsEntry[] newArray(int size) {
+            return new WifiUsabilityStatsEntry[size];
+        }
+    };
+}
diff --git a/wifi/java/android/net/wifi/aware/IdentityChangedListener.java b/wifi/java/android/net/wifi/aware/IdentityChangedListener.java
index 81a06e8..a8b19b3 100644
--- a/wifi/java/android/net/wifi/aware/IdentityChangedListener.java
+++ b/wifi/java/android/net/wifi/aware/IdentityChangedListener.java
@@ -30,7 +30,7 @@
 public class IdentityChangedListener {
     /**
      * @param mac The MAC address of the Aware discovery interface. The application must have the
-     * {@link android.Manifest.permission#ACCESS_COARSE_LOCATION} to get the actual MAC address,
+     * {@link android.Manifest.permission#ACCESS_FINE_LOCATION} to get the actual MAC address,
      *            otherwise all 0's will be provided.
      */
     public void onIdentityChanged(byte[] mac) {
diff --git a/wifi/java/android/net/wifi/aware/WifiAwareManager.java b/wifi/java/android/net/wifi/aware/WifiAwareManager.java
index 1fa1fd5..8aef7a2 100644
--- a/wifi/java/android/net/wifi/aware/WifiAwareManager.java
+++ b/wifi/java/android/net/wifi/aware/WifiAwareManager.java
@@ -231,7 +231,7 @@
      * <p>
      * This version of the API attaches a listener to receive the MAC address of the Aware interface
      * on startup and whenever it is updated (it is randomized at regular intervals for privacy).
-     * The application must have the {@link android.Manifest.permission#ACCESS_COARSE_LOCATION}
+     * The application must have the {@link android.Manifest.permission#ACCESS_FINE_LOCATION}
      * permission to execute this attach request. Otherwise, use the
      * {@link #attach(AttachCallback, Handler)} version. Note that aside from permission
      * requirements this listener will wake up the host at regular intervals causing higher power
diff --git a/wifi/java/android/net/wifi/aware/WifiAwareSession.java b/wifi/java/android/net/wifi/aware/WifiAwareSession.java
index 5f8841c..245b304 100644
--- a/wifi/java/android/net/wifi/aware/WifiAwareSession.java
+++ b/wifi/java/android/net/wifi/aware/WifiAwareSession.java
@@ -133,7 +133,7 @@
      *      An application must use the {@link DiscoverySession#close()} to
      *      terminate the publish discovery session once it isn't needed. This will free
      *      resources as well terminate any on-air transmissions.
-     * <p>The application must have the {@link android.Manifest.permission#ACCESS_COARSE_LOCATION}
+     * <p>The application must have the {@link android.Manifest.permission#ACCESS_FINE_LOCATION}
      * permission to start a publish discovery session.
      *
      * @param publishConfig The {@link PublishConfig} specifying the
@@ -179,7 +179,7 @@
      *      An application must use the {@link DiscoverySession#close()} to
      *      terminate the subscribe discovery session once it isn't needed. This will free
      *      resources as well terminate any on-air transmissions.
-     * <p>The application must have the {@link android.Manifest.permission#ACCESS_COARSE_LOCATION}
+     * <p>The application must have the {@link android.Manifest.permission#ACCESS_FINE_LOCATION}
      * permission to start a subscribe discovery session.
      *
      * @param subscribeConfig The {@link SubscribeConfig} specifying the
diff --git a/wifi/java/android/net/wifi/aware/package.html b/wifi/java/android/net/wifi/aware/package.html
index d5d962f6..c4f2e1f 100644
--- a/wifi/java/android/net/wifi/aware/package.html
+++ b/wifi/java/android/net/wifi/aware/package.html
@@ -15,7 +15,7 @@
 <ul>
     <li>{@link android.Manifest.permission#ACCESS_WIFI_STATE}</li>
     <li>{@link android.Manifest.permission#CHANGE_WIFI_STATE}</li>
-    <li>{@link android.Manifest.permission#ACCESS_COARSE_LOCATION}</li>
+    <li>{@link android.Manifest.permission#ACCESS_FINE_LOCATION}</li>
 </ul>
 
 <p class="note"><strong>Note:</strong> Not all Android-powered devices support Wi-Fi Aware
diff --git a/wifi/java/android/net/wifi/p2p/WifiP2pManager.java b/wifi/java/android/net/wifi/p2p/WifiP2pManager.java
index 1bed914..052ab99 100644
--- a/wifi/java/android/net/wifi/p2p/WifiP2pManager.java
+++ b/wifi/java/android/net/wifi/p2p/WifiP2pManager.java
@@ -1139,7 +1139,7 @@
      * @param c is the channel created at {@link #initialize}
      * @param listener for callbacks on success or failure. Can be null.
      */
-    @RequiresPermission(android.Manifest.permission.ACCESS_COARSE_LOCATION)
+    @RequiresPermission(android.Manifest.permission.ACCESS_FINE_LOCATION)
     public void discoverPeers(Channel c, ActionListener listener) {
         checkChannel(c);
         c.mAsyncChannel.sendMessage(DISCOVER_PEERS, 0, c.putListener(listener));
@@ -1183,7 +1183,7 @@
      * @param config options as described in {@link WifiP2pConfig} class
      * @param listener for callbacks on success or failure. Can be null.
      */
-    @RequiresPermission(android.Manifest.permission.ACCESS_COARSE_LOCATION)
+    @RequiresPermission(android.Manifest.permission.ACCESS_FINE_LOCATION)
     public void connect(Channel c, WifiP2pConfig config, ActionListener listener) {
         checkChannel(c);
         checkP2pConfig(config);
@@ -1225,7 +1225,7 @@
      * @param c is the channel created at {@link #initialize}
      * @param listener for callbacks on success or failure. Can be null.
      */
-    @RequiresPermission(android.Manifest.permission.ACCESS_COARSE_LOCATION)
+    @RequiresPermission(android.Manifest.permission.ACCESS_FINE_LOCATION)
     public void createGroup(Channel c, ActionListener listener) {
         checkChannel(c);
         c.mAsyncChannel.sendMessage(CREATE_GROUP, WifiP2pGroup.PERSISTENT_NET_ID,
@@ -1256,7 +1256,7 @@
      * @param config the configuration of a p2p group.
      * @param listener for callbacks on success or failure. Can be null.
      */
-    @RequiresPermission(android.Manifest.permission.ACCESS_COARSE_LOCATION)
+    @RequiresPermission(android.Manifest.permission.ACCESS_FINE_LOCATION)
     public void createGroup(@NonNull Channel c,
             @Nullable WifiP2pConfig config,
             @Nullable ActionListener listener) {
@@ -1344,7 +1344,7 @@
      * @param servInfo is a local service information.
      * @param listener for callbacks on success or failure. Can be null.
      */
-    @RequiresPermission(android.Manifest.permission.ACCESS_COARSE_LOCATION)
+    @RequiresPermission(android.Manifest.permission.ACCESS_FINE_LOCATION)
     public void addLocalService(Channel c, WifiP2pServiceInfo servInfo, ActionListener listener) {
         checkChannel(c);
         checkServiceInfo(servInfo);
@@ -1454,7 +1454,7 @@
      * @param c is the channel created at {@link #initialize}
      * @param listener for callbacks on success or failure. Can be null.
      */
-    @RequiresPermission(android.Manifest.permission.ACCESS_COARSE_LOCATION)
+    @RequiresPermission(android.Manifest.permission.ACCESS_FINE_LOCATION)
     public void discoverServices(Channel c, ActionListener listener) {
         checkChannel(c);
         c.mAsyncChannel.sendMessage(DISCOVER_SERVICES, 0, c.putListener(listener));
@@ -1530,7 +1530,7 @@
      * @param c is the channel created at {@link #initialize}
      * @param listener for callback when peer list is available. Can be null.
      */
-    @RequiresPermission(android.Manifest.permission.ACCESS_COARSE_LOCATION)
+    @RequiresPermission(android.Manifest.permission.ACCESS_FINE_LOCATION)
     public void requestPeers(Channel c, PeerListListener listener) {
         checkChannel(c);
         c.mAsyncChannel.sendMessage(REQUEST_PEERS, 0, c.putListener(listener));
@@ -1553,7 +1553,7 @@
      * @param c is the channel created at {@link #initialize}
      * @param listener for callback when group info is available. Can be null.
      */
-    @RequiresPermission(android.Manifest.permission.ACCESS_COARSE_LOCATION)
+    @RequiresPermission(android.Manifest.permission.ACCESS_FINE_LOCATION)
     public void requestGroupInfo(Channel c, GroupInfoListener listener) {
         checkChannel(c);
         c.mAsyncChannel.sendMessage(REQUEST_GROUP_INFO, 0, c.putListener(listener));
diff --git a/wifi/java/com/android/server/wifi/BaseWifiService.java b/wifi/java/com/android/server/wifi/BaseWifiService.java
index b3ac9f1..c236c7a 100644
--- a/wifi/java/com/android/server/wifi/BaseWifiService.java
+++ b/wifi/java/com/android/server/wifi/BaseWifiService.java
@@ -26,6 +26,7 @@
 import android.net.wifi.ISoftApCallback;
 import android.net.wifi.ITrafficStateCallback;
 import android.net.wifi.IWifiManager;
+import android.net.wifi.IWifiUsabilityStatsListener;
 import android.net.wifi.ScanResult;
 import android.net.wifi.WifiActivityEnergyInfo;
 import android.net.wifi.WifiConfiguration;
@@ -464,4 +465,20 @@
     public void stopDppSession() throws RemoteException {
         throw new UnsupportedOperationException();
     }
+
+    @Override
+    public void addWifiUsabilityStatsListener(
+            IBinder binder, IWifiUsabilityStatsListener listener, int listenerIdentifier) {
+        throw new UnsupportedOperationException();
+    }
+
+    @Override
+    public void removeWifiUsabilityStatsListener(int listenerIdentifier) {
+        throw new UnsupportedOperationException();
+    }
+
+    @Override
+    public void updateWifiUsabilityScore(int seqNum, int score, int predictionHorizonSec) {
+        throw new UnsupportedOperationException();
+    }
 }
diff --git a/wifi/tests/src/android/net/wifi/WifiConfigurationTest.java b/wifi/tests/src/android/net/wifi/WifiConfigurationTest.java
index 7bff68a..449423f 100644
--- a/wifi/tests/src/android/net/wifi/WifiConfigurationTest.java
+++ b/wifi/tests/src/android/net/wifi/WifiConfigurationTest.java
@@ -60,6 +60,8 @@
         config.setPasspointManagementObjectTree(cookie);
         config.trusted = false;
         config.updateIdentifier = "1234";
+        config.fromWifiNetworkSpecifier = true;
+        config.fromWifiNetworkSuggestion = true;
         MacAddress macBeforeParcel = config.getOrCreateRandomizedMacAddress();
         Parcel parcelW = Parcel.obtain();
         config.writeToParcel(parcelW, 0);
@@ -76,6 +78,8 @@
         assertEquals(macBeforeParcel, reconfig.getOrCreateRandomizedMacAddress());
         assertEquals(config.updateIdentifier, reconfig.updateIdentifier);
         assertFalse(reconfig.trusted);
+        assertTrue(config.fromWifiNetworkSpecifier);
+        assertTrue(config.fromWifiNetworkSuggestion);
 
         Parcel parcelWW = Parcel.obtain();
         reconfig.writeToParcel(parcelWW, 0);
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());
     }
 }
diff --git a/wifi/tests/src/android/net/wifi/WifiManagerTest.java b/wifi/tests/src/android/net/wifi/WifiManagerTest.java
index 4fbef5a..5c2f626 100644
--- a/wifi/tests/src/android/net/wifi/WifiManagerTest.java
+++ b/wifi/tests/src/android/net/wifi/WifiManagerTest.java
@@ -61,6 +61,7 @@
 import android.net.wifi.WifiManager.NetworkRequestUserSelectionCallback;
 import android.net.wifi.WifiManager.SoftApCallback;
 import android.net.wifi.WifiManager.TrafficStateCallback;
+import android.net.wifi.WifiManager.WifiUsabilityStatsListener;
 import android.os.Handler;
 import android.os.IBinder;
 import android.os.Message;
@@ -80,6 +81,7 @@
 import java.util.HashMap;
 import java.util.List;
 import java.util.Map;
+import java.util.concurrent.Executor;
 
 /**
  * Unit tests for {@link android.net.wifi.WifiManager}.
@@ -103,7 +105,9 @@
     @Mock SoftApCallback mSoftApCallback;
     @Mock TrafficStateCallback mTrafficStateCallback;
     @Mock NetworkRequestMatchCallback mNetworkRequestMatchCallback;
+    @Mock WifiUsabilityStatsListener mWifiUsabilityStatsListener;
 
+    private Executor mExecutor;
     private Handler mHandler;
     private TestLooper mLooper;
     private WifiManager mWifiManager;
@@ -1342,4 +1346,40 @@
         assertArrayEquals(TEST_MAC_ADDRESSES, mWifiManager.getFactoryMacAddresses());
         verify(mWifiService).getFactoryMacAddresses();
     }
+
+    /**
+     * Verify the call to addWifiUsabilityStatsListener goes to WifiServiceImpl.
+     */
+    @Test
+    public void addWifiUsabilityStatsListeneroesToWifiServiceImpl() throws Exception {
+        mExecutor = new SynchronousExecutor();
+        mWifiManager.addWifiUsabilityStatsListener(mExecutor, mWifiUsabilityStatsListener);
+        verify(mWifiService).addWifiUsabilityStatsListener(any(IBinder.class),
+                any(IWifiUsabilityStatsListener.Stub.class), anyInt());
+    }
+
+    /**
+     * Verify the call to removeWifiUsabilityStatsListener goes to WifiServiceImpl.
+     */
+    @Test
+    public void removeWifiUsabilityListenerGoesToWifiServiceImpl() throws Exception {
+        ArgumentCaptor<Integer> listenerIdentifier = ArgumentCaptor.forClass(Integer.class);
+        mExecutor = new SynchronousExecutor();
+        mWifiManager.addWifiUsabilityStatsListener(mExecutor, mWifiUsabilityStatsListener);
+        verify(mWifiService).addWifiUsabilityStatsListener(any(IBinder.class),
+                any(IWifiUsabilityStatsListener.Stub.class), listenerIdentifier.capture());
+
+        mWifiManager.removeWifiUsabilityStatsListener(mWifiUsabilityStatsListener);
+        verify(mWifiService).removeWifiUsabilityStatsListener(
+                eq((int) listenerIdentifier.getValue()));
+    }
+
+    /**
+     * Defined for testing purpose.
+     */
+    class SynchronousExecutor implements Executor {
+        public void execute(Runnable r) {
+            r.run();
+        }
+    }
 }
diff --git a/wifi/tests/src/android/net/wifi/WifiUsabilityStatsEntryTest.java b/wifi/tests/src/android/net/wifi/WifiUsabilityStatsEntryTest.java
new file mode 100644
index 0000000..a947b55
--- /dev/null
+++ b/wifi/tests/src/android/net/wifi/WifiUsabilityStatsEntryTest.java
@@ -0,0 +1,104 @@
+/*
+ * 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.wifi;
+
+import static org.junit.Assert.assertEquals;
+import static org.mockito.Mockito.validateMockitoUsage;
+
+import android.os.Parcel;
+
+import androidx.test.filters.SmallTest;
+
+import org.junit.After;
+import org.junit.Before;
+import org.junit.Test;
+import org.mockito.MockitoAnnotations;
+
+
+/**
+ * Unit tests for {@link android.net.wifi.WifiUsabilityStatsEntry}.
+ */
+@SmallTest
+public class WifiUsabilityStatsEntryTest {
+
+    /**
+     * Setup before tests.
+     */
+    @Before
+    public void setUp() throws Exception {
+        MockitoAnnotations.initMocks(this);
+    }
+
+    /**
+     * Clean up after tests.
+     */
+    @After
+    public void cleanup() {
+        validateMockitoUsage();
+    }
+
+    /**
+     * Verify parcel read/write for Wifi usability stats result.
+     */
+    @Test
+    public void verifyStatsResultWriteAndThenRead() throws Exception {
+        WifiUsabilityStatsEntry writeResult = createResult();
+        WifiUsabilityStatsEntry readResult = parcelWriteRead(writeResult);
+        assertWifiUsabilityStatsEntryEquals(writeResult, readResult);
+    }
+
+    /**
+     * Write the provided {@link WifiUsabilityStatsEntry} to a parcel and deserialize it.
+     */
+    private static WifiUsabilityStatsEntry parcelWriteRead(
+            WifiUsabilityStatsEntry writeResult) throws Exception {
+        Parcel parcel = Parcel.obtain();
+        writeResult.writeToParcel(parcel, 0);
+        parcel.setDataPosition(0);    // Rewind data position back to the beginning for read.
+        return WifiUsabilityStatsEntry.CREATOR.createFromParcel(parcel);
+    }
+
+    private static WifiUsabilityStatsEntry createResult() {
+        return new WifiUsabilityStatsEntry(
+                0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18
+        );
+    }
+
+    private static void assertWifiUsabilityStatsEntryEquals(
+            WifiUsabilityStatsEntry expected,
+            WifiUsabilityStatsEntry actual) {
+        assertEquals(expected.timeStampMs, actual.timeStampMs);
+        assertEquals(expected.rssi, actual.rssi);
+        assertEquals(expected.linkSpeedMbps, actual.linkSpeedMbps);
+        assertEquals(expected.totalTxSuccess, actual.totalTxSuccess);
+        assertEquals(expected.totalTxRetries, actual.totalTxRetries);
+        assertEquals(expected.totalTxBad, actual.totalTxBad);
+        assertEquals(expected.totalRxSuccess, actual.totalRxSuccess);
+        assertEquals(expected.totalRadioOnTimeMs, actual.totalRadioOnTimeMs);
+        assertEquals(expected.totalRadioTxTimeMs, actual.totalRadioTxTimeMs);
+        assertEquals(expected.totalRadioRxTimeMs, actual.totalRadioRxTimeMs);
+        assertEquals(expected.totalScanTimeMs, actual.totalScanTimeMs);
+        assertEquals(expected.totalNanScanTimeMs, actual.totalNanScanTimeMs);
+        assertEquals(expected.totalBackgroundScanTimeMs, actual.totalBackgroundScanTimeMs);
+        assertEquals(expected.totalRoamScanTimeMs, actual.totalRoamScanTimeMs);
+        assertEquals(expected.totalPnoScanTimeMs, actual.totalPnoScanTimeMs);
+        assertEquals(expected.totalHotspot2ScanTimeMs, actual.totalHotspot2ScanTimeMs);
+        assertEquals(expected.totalCcaBusyFreqTimeMs, actual.totalCcaBusyFreqTimeMs);
+        assertEquals(expected.totalRadioOnFreqTimeMs, actual.totalRadioOnFreqTimeMs);
+        assertEquals(expected.totalBeaconRx, actual.totalBeaconRx);
+    }
+}