Merge "Hide distracting pkg notifications"
diff --git a/Android.bp b/Android.bp
index 286be82..d4a04cc 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",
@@ -191,6 +194,10 @@
         "core/java/android/hardware/input/IInputDevicesChangedListener.aidl",
         "core/java/android/hardware/input/ITabletModeChangedListener.aidl",
         "core/java/android/hardware/iris/IIrisService.aidl",
+        "core/java/android/hardware/location/IActivityRecognitionHardware.aidl",
+        "core/java/android/hardware/location/IActivityRecognitionHardwareClient.aidl",
+        "core/java/android/hardware/location/IActivityRecognitionHardwareSink.aidl",
+        "core/java/android/hardware/location/IActivityRecognitionHardwareWatcher.aidl",
         "core/java/android/hardware/location/IGeofenceHardware.aidl",
         "core/java/android/hardware/location/IGeofenceHardwareCallback.aidl",
         "core/java/android/hardware/location/IGeofenceHardwareMonitorCallback.aidl",
@@ -207,7 +214,6 @@
         "core/java/android/hardware/soundtrigger/IRecognitionStatusCallback.aidl",
         "core/java/android/hardware/usb/IUsbManager.aidl",
         "core/java/android/hardware/usb/IUsbSerialReader.aidl",
-        "core/java/android/net/ICaptivePortal.aidl",
         "core/java/android/net/IConnectivityManager.aidl",
         "core/java/android/hardware/ISensorPrivacyListener.aidl",
         "core/java/android/hardware/ISensorPrivacyManager.aidl",
@@ -468,14 +474,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",
@@ -501,11 +504,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",
@@ -520,8 +519,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",
@@ -634,6 +631,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",
@@ -688,6 +686,7 @@
             "location/java",
             "lowpan/java",
             "media/java",
+            "media/apex/java",
             "media/mca/effect/java",
             "media/mca/filterfw/java",
             "media/mca/filterpacks/java",
@@ -720,8 +719,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,
@@ -729,15 +726,27 @@
         "ext",
     ],
 
+    jarjar_rules: "jarjar_rules_hidl.txt",
+
     static_libs: [
         "apex_aidl_interface-java",
         "networkstack-aidl-interfaces-java",
         "framework-protos",
+        "game-driver-protos",
         "mediaplayer2-protos",
         "android.hidl.base-V1.0-java",
         "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",
@@ -745,15 +754,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",
@@ -762,8 +769,6 @@
     required: [
         // TODO: remove gps_debug when the build system propagates "required" properly.
         "gps_debug.conf",
-        // Loaded with System.loadLibrary by android.view.textclassifier
-        "libmedia2_jni",
     ],
 
     dxflags: [
@@ -799,11 +804,7 @@
     name: "framework-annotation-proc",
     defaults: ["framework-defaults"],
     // Use UsedByApps annotation processor
-    annotation_processors: ["unsupportedappusage-annotation-processor"],
-    // b/25860419: annotation processors must be explicitly specified for grok
-    annotation_processor_classes: [
-        "android.processor.unsupportedappusage.UsedByAppsProcessor",
-    ],
+    plugins: ["unsupportedappusage-annotation-processor"],
 }
 
 // A host library including just UnsupportedAppUsage.java so that the annotation
@@ -884,6 +885,7 @@
     srcs: [
         "core/java/android/net/ApfCapabilitiesParcelable.aidl",
         "core/java/android/net/DhcpResultsParcelable.aidl",
+        "core/java/android/net/ICaptivePortal.aidl",
         "core/java/android/net/INetworkMonitor.aidl",
         "core/java/android/net/INetworkMonitorCallbacks.aidl",
         "core/java/android/net/IIpMemoryStore.aidl",
@@ -1261,7 +1263,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",
     ],
@@ -1323,7 +1325,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,
@@ -1769,6 +1771,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",
@@ -1777,6 +1780,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 9053a00..bb6dbeb 100644
--- a/api/current.txt
+++ b/api/current.txt
@@ -3941,7 +3941,8 @@
     method public boolean isBackgroundRestricted();
     method @Deprecated public boolean isInLockTaskMode();
     method public boolean isLowRamDevice();
-    method public static boolean isRunningInTestHarness();
+    method @Deprecated public static boolean isRunningInTestHarness();
+    method public static boolean isRunningInUserTestHarness();
     method public static boolean isUserAMonkey();
     method @RequiresPermission(android.Manifest.permission.KILL_BACKGROUND_PROCESSES) public void killBackgroundProcesses(String);
     method @RequiresPermission(android.Manifest.permission.REORDER_TASKS) public void moveTaskToFront(int, int);
@@ -5465,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;
@@ -5476,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);
   }
 
@@ -5792,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);
@@ -6577,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);
@@ -6607,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);
@@ -6697,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);
@@ -6719,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>);
@@ -6786,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";
@@ -6822,6 +6827,7 @@
     field public static final String EXTRA_ADD_EXPLANATION = "android.app.extra.ADD_EXPLANATION";
     field public static final String EXTRA_DELEGATION_SCOPES = "android.app.extra.DELEGATION_SCOPES";
     field public static final String EXTRA_DEVICE_ADMIN = "android.app.extra.DEVICE_ADMIN";
+    field @RequiresPermission(android.Manifest.permission.GET_AND_REQUEST_SCREEN_LOCK_COMPLEXITY) public static final String EXTRA_PASSWORD_COMPLEXITY = "android.app.extra.PASSWORD_COMPLEXITY";
     field public static final String EXTRA_PROVISIONING_ACCOUNT_TO_MIGRATE = "android.app.extra.PROVISIONING_ACCOUNT_TO_MIGRATE";
     field public static final String EXTRA_PROVISIONING_ADMIN_EXTRAS_BUNDLE = "android.app.extra.PROVISIONING_ADMIN_EXTRAS_BUNDLE";
     field public static final String EXTRA_PROVISIONING_DEVICE_ADMIN_COMPONENT_NAME = "android.app.extra.PROVISIONING_DEVICE_ADMIN_COMPONENT_NAME";
@@ -6929,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 {
@@ -8608,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);
@@ -8703,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
@@ -9625,7 +9640,7 @@
 
   public abstract class Context {
     ctor public Context();
-    method public abstract boolean bindIsolatedService(@RequiresPermission android.content.Intent, @NonNull android.content.ServiceConnection, int, @NonNull String);
+    method public boolean bindIsolatedService(@RequiresPermission android.content.Intent, @NonNull android.content.ServiceConnection, int, @NonNull String);
     method public abstract boolean bindService(@RequiresPermission android.content.Intent, @NonNull android.content.ServiceConnection, int);
     method @CheckResult(suggest="#enforceCallingOrSelfPermission(String,String)") public abstract int checkCallingOrSelfPermission(@NonNull String);
     method @CheckResult(suggest="#enforceCallingOrSelfUriPermission(Uri,int,String)") public abstract int checkCallingOrSelfUriPermission(android.net.Uri, int);
@@ -9678,7 +9693,7 @@
     method public abstract java.io.File getNoBackupFilesDir();
     method public abstract java.io.File getObbDir();
     method public abstract java.io.File[] getObbDirs();
-    method public abstract String getOpPackageName();
+    method public String getOpPackageName();
     method public abstract String getPackageCodePath();
     method public abstract android.content.pm.PackageManager getPackageManager();
     method public abstract String getPackageName();
@@ -9745,7 +9760,7 @@
     method public abstract void unbindService(@NonNull android.content.ServiceConnection);
     method public void unregisterComponentCallbacks(android.content.ComponentCallbacks);
     method public abstract void unregisterReceiver(android.content.BroadcastReceiver);
-    method public abstract void updateServiceGroup(@NonNull android.content.ServiceConnection, int, int);
+    method public void updateServiceGroup(@NonNull android.content.ServiceConnection, int, int);
     field public static final String ACCESSIBILITY_SERVICE = "accessibility";
     field public static final String ACCOUNT_SERVICE = "account";
     field public static final String ACTIVITY_SERVICE = "activity";
@@ -9839,7 +9854,6 @@
   public class ContextWrapper extends android.content.Context {
     ctor public ContextWrapper(android.content.Context);
     method protected void attachBaseContext(android.content.Context);
-    method public boolean bindIsolatedService(android.content.Intent, android.content.ServiceConnection, int, String);
     method public boolean bindService(android.content.Intent, android.content.ServiceConnection, int);
     method public int checkCallingOrSelfPermission(String);
     method public int checkCallingOrSelfUriPermission(android.net.Uri, int);
@@ -9889,7 +9903,6 @@
     method public java.io.File getNoBackupFilesDir();
     method public java.io.File getObbDir();
     method public java.io.File[] getObbDirs();
-    method public String getOpPackageName();
     method public String getPackageCodePath();
     method public android.content.pm.PackageManager getPackageManager();
     method public String getPackageName();
@@ -9945,7 +9958,6 @@
     method public boolean stopService(android.content.Intent);
     method public void unbindService(android.content.ServiceConnection);
     method public void unregisterReceiver(android.content.BroadcastReceiver);
-    method public void updateServiceGroup(android.content.ServiceConnection, int, int);
   }
 
   @Deprecated public class CursorLoader extends android.content.AsyncTaskLoader<android.database.Cursor> {
@@ -10214,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";
@@ -10221,7 +10234,7 @@
     field public static final String ACTION_MY_PACKAGE_REPLACED = "android.intent.action.MY_PACKAGE_REPLACED";
     field public static final String ACTION_MY_PACKAGE_SUSPENDED = "android.intent.action.MY_PACKAGE_SUSPENDED";
     field public static final String ACTION_MY_PACKAGE_UNSUSPENDED = "android.intent.action.MY_PACKAGE_UNSUSPENDED";
-    field public static final String ACTION_NEW_OUTGOING_CALL = "android.intent.action.NEW_OUTGOING_CALL";
+    field @Deprecated public static final String ACTION_NEW_OUTGOING_CALL = "android.intent.action.NEW_OUTGOING_CALL";
     field public static final String ACTION_OPEN_DOCUMENT = "android.intent.action.OPEN_DOCUMENT";
     field public static final String ACTION_OPEN_DOCUMENT_TREE = "android.intent.action.OPEN_DOCUMENT_TREE";
     field public static final String ACTION_PACKAGES_SUSPENDED = "android.intent.action.PACKAGES_SUSPENDED";
@@ -10349,6 +10362,7 @@
     field public static final int EXTRA_DOCK_STATE_LE_DESK = 3; // 0x3
     field public static final int EXTRA_DOCK_STATE_UNDOCKED = 0; // 0x0
     field public static final String EXTRA_DONT_KILL_APP = "android.intent.extra.DONT_KILL_APP";
+    field public static final String EXTRA_DURATION_MILLIS = "android.intent.extra.DURATION_MILLIS";
     field public static final String EXTRA_EMAIL = "android.intent.extra.EMAIL";
     field public static final String EXTRA_EXCLUDE_COMPONENTS = "android.intent.extra.EXCLUDE_COMPONENTS";
     field public static final String EXTRA_FROM_STORAGE = "android.intent.extra.FROM_STORAGE";
@@ -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();
@@ -11500,7 +11525,7 @@
     method public abstract java.util.List<android.content.pm.PermissionGroupInfo> getAllPermissionGroups(int);
     method public abstract android.graphics.drawable.Drawable getApplicationBanner(android.content.pm.ApplicationInfo);
     method public abstract android.graphics.drawable.Drawable getApplicationBanner(String) throws android.content.pm.PackageManager.NameNotFoundException;
-    method public abstract int getApplicationEnabledSetting(String);
+    method public abstract int getApplicationEnabledSetting(@NonNull String);
     method public abstract android.graphics.drawable.Drawable getApplicationIcon(android.content.pm.ApplicationInfo);
     method public abstract android.graphics.drawable.Drawable getApplicationIcon(String) throws android.content.pm.PackageManager.NameNotFoundException;
     method public abstract android.content.pm.ApplicationInfo getApplicationInfo(String, int) throws android.content.pm.PackageManager.NameNotFoundException;
@@ -11508,7 +11533,7 @@
     method public abstract android.graphics.drawable.Drawable getApplicationLogo(android.content.pm.ApplicationInfo);
     method public abstract android.graphics.drawable.Drawable getApplicationLogo(String) throws android.content.pm.PackageManager.NameNotFoundException;
     method @Nullable public abstract android.content.pm.ChangedPackages getChangedPackages(@IntRange(from=0) int);
-    method public abstract int getComponentEnabledSetting(android.content.ComponentName);
+    method public abstract int getComponentEnabledSetting(@NonNull android.content.ComponentName);
     method public abstract android.graphics.drawable.Drawable getDefaultActivityIcon();
     method public abstract android.graphics.drawable.Drawable getDrawable(String, @DrawableRes int, android.content.pm.ApplicationInfo);
     method public abstract java.util.List<android.content.pm.ApplicationInfo> getInstalledApplications(int);
@@ -11574,8 +11599,8 @@
     method public abstract android.content.pm.ProviderInfo resolveContentProvider(String, int);
     method public abstract android.content.pm.ResolveInfo resolveService(android.content.Intent, int);
     method public abstract void setApplicationCategoryHint(@NonNull String, int);
-    method @RequiresPermission(value=android.Manifest.permission.CHANGE_COMPONENT_ENABLED_STATE, conditional=true) public abstract void setApplicationEnabledSetting(String, int, int);
-    method @RequiresPermission(value=android.Manifest.permission.CHANGE_COMPONENT_ENABLED_STATE, conditional=true) public abstract void setComponentEnabledSetting(android.content.ComponentName, int, int);
+    method @RequiresPermission(value=android.Manifest.permission.CHANGE_COMPONENT_ENABLED_STATE, conditional=true) public abstract void setApplicationEnabledSetting(@NonNull String, int, int);
+    method @RequiresPermission(value=android.Manifest.permission.CHANGE_COMPONENT_ENABLED_STATE, conditional=true) public abstract void setComponentEnabledSetting(@NonNull android.content.ComponentName, int, int);
     method public abstract void setInstallerPackageName(String, String);
     method public abstract void updateInstantAppCookie(@Nullable byte[]);
     method public abstract void verifyPendingInstall(int, int);
@@ -11655,6 +11680,7 @@
     field public static final String FEATURE_SCREEN_LANDSCAPE = "android.hardware.screen.landscape";
     field public static final String FEATURE_SCREEN_PORTRAIT = "android.hardware.screen.portrait";
     field public static final String FEATURE_SECURELY_REMOVES_USERS = "android.software.securely_removes_users";
+    field public static final String FEATURE_SECURE_LOCK_SCREEN = "android.software.secure_lock_screen";
     field public static final String FEATURE_SENSOR_ACCELEROMETER = "android.hardware.sensor.accelerometer";
     field public static final String FEATURE_SENSOR_AMBIENT_TEMPERATURE = "android.hardware.sensor.ambient_temperature";
     field public static final String FEATURE_SENSOR_BAROMETER = "android.hardware.sensor.barometer";
@@ -11674,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";
@@ -14618,8 +14645,8 @@
   public class Picture {
     ctor public Picture();
     ctor public Picture(android.graphics.Picture);
-    method public android.graphics.Canvas beginRecording(int, int);
-    method public void draw(android.graphics.Canvas);
+    method @NonNull public android.graphics.Canvas beginRecording(int, int);
+    method public void draw(@NonNull android.graphics.Canvas);
     method public void endRecording();
     method public int getHeight();
     method public int getWidth();
@@ -14871,7 +14898,7 @@
   }
 
   public final class RenderNode {
-    ctor public RenderNode(String);
+    ctor public RenderNode(@Nullable String);
     method public int computeApproximateMemoryUsage();
     method public void discardDisplayList();
     method public void endRecording();
@@ -17077,6 +17104,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;
@@ -22256,7 +22284,7 @@
     method public void onUpdateExtractingViews(android.view.inputmethod.EditorInfo);
     method public void onUpdateExtractingVisibility(android.view.inputmethod.EditorInfo);
     method public void onUpdateSelection(int, int, int, int, int, int);
-    method public void onViewClicked(boolean);
+    method @Deprecated public void onViewClicked(boolean);
     method public void onWindowHidden();
     method public void onWindowShown();
     method public void requestHideSelf(int);
@@ -24638,6 +24666,7 @@
     method @android.media.MediaDrm.SecurityLevel public int getSecurityLevel(@NonNull byte[]);
     method public static boolean isCryptoSchemeSupported(@NonNull java.util.UUID);
     method public static boolean isCryptoSchemeSupported(@NonNull java.util.UUID, @NonNull String);
+    method public static boolean isCryptoSchemeSupported(@NonNull java.util.UUID, @NonNull String, @android.media.MediaDrm.SecurityLevel int);
     method @NonNull public byte[] openSession() throws android.media.NotProvisionedException, android.media.ResourceBusyException;
     method @NonNull public byte[] openSession(@android.media.MediaDrm.SecurityLevel int) throws android.media.NotProvisionedException, android.media.ResourceBusyException;
     method @Nullable public byte[] provideKeyResponse(@NonNull byte[], @NonNull byte[]) throws android.media.DeniedByServerException, android.media.NotProvisionedException;
@@ -25407,6 +25436,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();
@@ -25915,6 +25945,22 @@
     method @Nullable public android.media.Session2Command.Result onSessionCommand(@NonNull android.media.MediaSession2, @NonNull android.media.MediaSession2.ControllerInfo, @NonNull android.media.Session2Command, @Nullable android.os.Bundle);
   }
 
+  public abstract class MediaSession2Service extends android.app.Service {
+    ctor public MediaSession2Service();
+    method public final void addSession(@NonNull android.media.MediaSession2);
+    method @NonNull public final java.util.List<android.media.MediaSession2> getSessions();
+    method @CallSuper @Nullable public android.os.IBinder onBind(@NonNull android.content.Intent);
+    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);
+  }
+
+  public static class MediaSession2Service.MediaNotification {
+    ctor public MediaSession2Service.MediaNotification(int, @NonNull android.app.Notification);
+    method @NonNull public android.app.Notification getNotification();
+    method public int getNotificationId();
+  }
+
   public final class MediaSync {
     ctor public MediaSync();
     method @NonNull public android.view.Surface createInputSurface();
@@ -26577,12 +26623,9 @@
     field public static final int SUCCESS = 0; // 0x0
   }
 
-  public static final class AudioEffect.Descriptor implements android.os.Parcelable {
+  public static class AudioEffect.Descriptor {
     ctor public AudioEffect.Descriptor();
     ctor public AudioEffect.Descriptor(String, String, String, String, String);
-    method public int describeContents();
-    method public void writeToParcel(android.os.Parcel, int);
-    field public static final android.os.Parcelable.Creator<android.media.audiofx.AudioEffect.Descriptor> CREATOR;
     field public String connectMode;
     field public String implementor;
     field public String name;
@@ -27432,6 +27475,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);
   }
@@ -27446,7 +27490,6 @@
 
   public static final class MediaSessionManager.RemoteUserInfo {
     ctor public MediaSessionManager.RemoteUserInfo(@NonNull String, int, int);
-    ctor public MediaSessionManager.RemoteUserInfo(String, int, int, android.os.IBinder);
     method public String getPackageName();
     method public int getPid();
     method public int getUid();
@@ -28455,6 +28498,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();
@@ -28564,6 +28608,28 @@
     field public int serverAddress;
   }
 
+  public final class DnsResolver {
+    method public static android.net.DnsResolver getInstance();
+    method public void query(@Nullable android.net.Network, @NonNull byte[], int, @NonNull android.os.Handler, @NonNull android.net.DnsResolver.RawAnswerListener) throws android.system.ErrnoException;
+    method public void query(@Nullable android.net.Network, @NonNull String, int, int, int, @NonNull android.os.Handler, @NonNull android.net.DnsResolver.RawAnswerListener) throws android.system.ErrnoException;
+    method public void query(@Nullable android.net.Network, @NonNull String, int, @NonNull android.os.Handler, @NonNull android.net.DnsResolver.InetAddressAnswerListener) throws android.system.ErrnoException;
+    field public static final int CLASS_IN = 1; // 0x1
+    field public static final int FLAG_EMPTY = 0; // 0x0
+    field public static final int FLAG_NO_CACHE_LOOKUP = 4; // 0x4
+    field public static final int FLAG_NO_CACHE_STORE = 2; // 0x2
+    field public static final int FLAG_NO_RETRY = 1; // 0x1
+    field public static final int TYPE_A = 1; // 0x1
+    field public static final int TYPE_AAAA = 28; // 0x1c
+  }
+
+  public static interface DnsResolver.InetAddressAnswerListener {
+    method public void onAnswer(@NonNull java.util.List<java.net.InetAddress>);
+  }
+
+  public static interface DnsResolver.RawAnswerListener {
+    method public void onAnswer(@Nullable byte[]);
+  }
+
   public class InetAddresses {
     method public static boolean isNumericAddress(String);
     method public static java.net.InetAddress parseNumericAddress(String);
@@ -28912,24 +28978,24 @@
     field public static final android.os.Parcelable.Creator<android.net.RouteInfo> CREATOR;
   }
 
-  public class SSLCertificateSocketFactory extends javax.net.ssl.SSLSocketFactory {
+  @Deprecated public class SSLCertificateSocketFactory extends javax.net.ssl.SSLSocketFactory {
     ctor @Deprecated public SSLCertificateSocketFactory(int);
-    method public java.net.Socket createSocket(java.net.Socket, String, int, boolean) throws java.io.IOException;
-    method public java.net.Socket createSocket(java.net.InetAddress, int, java.net.InetAddress, int) throws java.io.IOException;
-    method public java.net.Socket createSocket(java.net.InetAddress, int) throws java.io.IOException;
-    method public java.net.Socket createSocket(String, int, java.net.InetAddress, int) throws java.io.IOException;
-    method public java.net.Socket createSocket(String, int) throws java.io.IOException;
-    method public static javax.net.SocketFactory getDefault(int);
-    method public static javax.net.ssl.SSLSocketFactory getDefault(int, android.net.SSLSessionCache);
-    method public String[] getDefaultCipherSuites();
-    method public static javax.net.ssl.SSLSocketFactory getInsecure(int, android.net.SSLSessionCache);
-    method public byte[] getNpnSelectedProtocol(java.net.Socket);
-    method public String[] getSupportedCipherSuites();
-    method public void setHostname(java.net.Socket, String);
-    method public void setKeyManagers(javax.net.ssl.KeyManager[]);
-    method public void setNpnProtocols(byte[][]);
-    method public void setTrustManagers(javax.net.ssl.TrustManager[]);
-    method public void setUseSessionTickets(java.net.Socket, boolean);
+    method @Deprecated public java.net.Socket createSocket(java.net.Socket, String, int, boolean) throws java.io.IOException;
+    method @Deprecated public java.net.Socket createSocket(java.net.InetAddress, int, java.net.InetAddress, int) throws java.io.IOException;
+    method @Deprecated public java.net.Socket createSocket(java.net.InetAddress, int) throws java.io.IOException;
+    method @Deprecated public java.net.Socket createSocket(String, int, java.net.InetAddress, int) throws java.io.IOException;
+    method @Deprecated public java.net.Socket createSocket(String, int) throws java.io.IOException;
+    method @Deprecated public static javax.net.SocketFactory getDefault(int);
+    method @Deprecated public static javax.net.ssl.SSLSocketFactory getDefault(int, android.net.SSLSessionCache);
+    method @Deprecated public String[] getDefaultCipherSuites();
+    method @Deprecated public static javax.net.ssl.SSLSocketFactory getInsecure(int, android.net.SSLSessionCache);
+    method @Deprecated public byte[] getNpnSelectedProtocol(java.net.Socket);
+    method @Deprecated public String[] getSupportedCipherSuites();
+    method @Deprecated public void setHostname(java.net.Socket, String);
+    method @Deprecated public void setKeyManagers(javax.net.ssl.KeyManager[]);
+    method @Deprecated public void setNpnProtocols(byte[][]);
+    method @Deprecated public void setTrustManagers(javax.net.ssl.TrustManager[]);
+    method @Deprecated public void setUseSessionTickets(java.net.Socket, boolean);
   }
 
   public final class SSLSessionCache {
@@ -28937,6 +29003,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();
@@ -29118,6 +29207,8 @@
 
   public class VpnService extends android.app.Service {
     ctor public VpnService();
+    method public final boolean isAlwaysOn();
+    method public final boolean isLockdownEnabled();
     method public android.os.IBinder onBind(android.content.Intent);
     method public void onRevoke();
     method public static android.content.Intent prepare(android.content.Context);
@@ -29145,6 +29236,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[]);
@@ -29753,7 +29845,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();
@@ -30244,26 +30336,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);
@@ -35211,11 +35303,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 {
@@ -37894,18 +37991,18 @@
     method public static android.provider.DocumentsContract.Path findDocumentPath(android.content.ContentInterface, android.net.Uri) throws java.io.FileNotFoundException;
     method @Deprecated public static android.provider.DocumentsContract.Path findDocumentPath(android.content.ContentResolver, android.net.Uri) throws java.io.FileNotFoundException;
     method public static String getDocumentId(android.net.Uri);
-    method public static android.os.Bundle getDocumentMetadata(android.content.ContentInterface, android.net.Uri) throws java.io.FileNotFoundException;
+    method @Nullable public static android.os.Bundle getDocumentMetadata(@NonNull android.content.ContentInterface, @NonNull android.net.Uri) throws java.io.FileNotFoundException;
     method @Deprecated public static android.os.Bundle getDocumentMetadata(android.content.ContentResolver, android.net.Uri) throws java.io.FileNotFoundException;
     method public static android.graphics.Bitmap getDocumentThumbnail(android.content.ContentInterface, android.net.Uri, android.graphics.Point, android.os.CancellationSignal) throws java.io.FileNotFoundException;
     method @Deprecated public static android.graphics.Bitmap getDocumentThumbnail(android.content.ContentResolver, android.net.Uri, android.graphics.Point, android.os.CancellationSignal) throws java.io.FileNotFoundException;
     method public static String getRootId(android.net.Uri);
     method public static String getSearchDocumentsQuery(android.net.Uri);
     method public static String getTreeDocumentId(android.net.Uri);
-    method public static boolean isChildDocument(android.content.ContentInterface, android.net.Uri, android.net.Uri) throws java.io.FileNotFoundException;
+    method public static boolean isChildDocument(@NonNull android.content.ContentInterface, @NonNull android.net.Uri, @NonNull android.net.Uri) throws java.io.FileNotFoundException;
     method @Deprecated public static boolean isChildDocument(android.content.ContentResolver, android.net.Uri, android.net.Uri) throws java.io.FileNotFoundException;
     method public static boolean isDocumentUri(android.content.Context, @Nullable android.net.Uri);
-    method public static boolean isRootUri(android.content.Context, @Nullable android.net.Uri);
-    method public static boolean isRootsUri(android.content.Context, @Nullable android.net.Uri);
+    method public static boolean isRootUri(@NonNull android.content.Context, @Nullable android.net.Uri);
+    method public static boolean isRootsUri(@NonNull android.content.Context, @Nullable android.net.Uri);
     method public static boolean isTreeUri(android.net.Uri);
     method public static android.net.Uri moveDocument(android.content.ContentInterface, android.net.Uri, android.net.Uri, android.net.Uri) throws java.io.FileNotFoundException;
     method @Deprecated public static android.net.Uri moveDocument(android.content.ContentResolver, android.net.Uri, android.net.Uri, android.net.Uri) throws java.io.FileNotFoundException;
@@ -37976,6 +38073,7 @@
     field public static final String COLUMN_FLAGS = "flags";
     field public static final String COLUMN_ICON = "icon";
     field public static final String COLUMN_MIME_TYPES = "mime_types";
+    field public static final String COLUMN_QUERY_ARGS = "query_args";
     field public static final String COLUMN_ROOT_ID = "root_id";
     field public static final String COLUMN_SUMMARY = "summary";
     field public static final String COLUMN_TITLE = "title";
@@ -37998,7 +38096,7 @@
     method public void deleteDocument(String) throws java.io.FileNotFoundException;
     method public void ejectRoot(String);
     method public android.provider.DocumentsContract.Path findDocumentPath(@Nullable String, String) throws java.io.FileNotFoundException;
-    method @Nullable public android.os.Bundle getDocumentMetadata(String) throws java.io.FileNotFoundException;
+    method @Nullable public android.os.Bundle getDocumentMetadata(@NonNull String) throws java.io.FileNotFoundException;
     method public String[] getDocumentStreamTypes(String, String);
     method public String getDocumentType(String) throws java.io.FileNotFoundException;
     method public final String getType(android.net.Uri);
@@ -38023,7 +38121,7 @@
     method public android.database.Cursor queryRecentDocuments(String, String[], @Nullable android.os.Bundle, @Nullable android.os.CancellationSignal) throws java.io.FileNotFoundException;
     method public abstract android.database.Cursor queryRoots(String[]) throws java.io.FileNotFoundException;
     method public android.database.Cursor querySearchDocuments(String, String, String[]) throws java.io.FileNotFoundException;
-    method public android.database.Cursor querySearchDocuments(String, String[], android.os.Bundle) throws java.io.FileNotFoundException;
+    method public android.database.Cursor querySearchDocuments(@NonNull String, @Nullable String[], @NonNull android.os.Bundle) throws java.io.FileNotFoundException;
     method public void removeDocument(String, String) throws java.io.FileNotFoundException;
     method public String renameDocument(String, String) throws java.io.FileNotFoundException;
     method public final void revokeDocumentPermission(String);
@@ -38155,6 +38253,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 {
@@ -38361,28 +38461,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 {
@@ -38434,24 +38534,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 {
@@ -38649,6 +38749,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";
   }
 
@@ -39032,6 +39133,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";
@@ -41293,6 +41395,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_IMPORTANCE = "key_importance";
+    field public static final String KEY_SMART_ACTIONS = "key_smart_actions";
+    field public static final String KEY_SMART_REPLIES = "key_smart_replies";
+    field public static final String KEY_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);
@@ -41339,6 +41457,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();
@@ -41419,6 +41555,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();
@@ -41437,6 +41575,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);
@@ -41631,6 +41800,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);
@@ -41640,9 +41810,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 {
@@ -43003,6 +43179,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";
   }
 
@@ -43596,6 +43781,7 @@
     method @RequiresPermission(android.Manifest.permission.READ_PHONE_STATE) public java.util.List<android.telecom.PhoneAccountHandle> getSelfManagedPhoneAccounts();
     method public android.telecom.PhoneAccountHandle getSimCallManager();
     method public String getSystemDialerPackage();
+    method @RequiresPermission(android.Manifest.permission.READ_PHONE_STATE) public android.telecom.PhoneAccountHandle getUserSelectedOutgoingPhoneAccount();
     method @RequiresPermission(android.Manifest.permission.READ_PHONE_STATE) public String getVoiceMailNumber(android.telecom.PhoneAccountHandle);
     method @RequiresPermission(android.Manifest.permission.MODIFY_PHONE_STATE) public boolean handleMmi(String);
     method @RequiresPermission(android.Manifest.permission.MODIFY_PHONE_STATE) public boolean handleMmi(String, android.telecom.PhoneAccountHandle);
@@ -43607,6 +43793,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);
@@ -43854,7 +44041,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";
@@ -44155,6 +44344,7 @@
   public final class CellSignalStrengthGsm extends android.telephony.CellSignalStrength implements android.os.Parcelable {
     method public int describeContents();
     method public int getAsuLevel();
+    method public int getBitErrorRate();
     method public int getDbm();
     method public int getLevel();
     method public int getTimingAdvance();
@@ -44456,16 +44646,16 @@
 
   public class SignalStrength implements android.os.Parcelable {
     method public int describeContents();
-    method public int getCdmaDbm();
-    method public int getCdmaEcio();
+    method @Deprecated public int getCdmaDbm();
+    method @Deprecated public int getCdmaEcio();
     method @NonNull public java.util.List<android.telephony.CellSignalStrength> getCellSignalStrengths();
-    method public int getEvdoDbm();
-    method public int getEvdoEcio();
-    method public int getEvdoSnr();
-    method public int getGsmBitErrorRate();
-    method public int getGsmSignalStrength();
+    method @Deprecated public int getEvdoDbm();
+    method @Deprecated public int getEvdoEcio();
+    method @Deprecated public int getEvdoSnr();
+    method @Deprecated public int getGsmBitErrorRate();
+    method @Deprecated public int getGsmSignalStrength();
     method public int getLevel();
-    method public boolean isGsm();
+    method @Deprecated public boolean isGsm();
     method public void writeToParcel(android.os.Parcel, int);
     field public static final int INVALID = 2147483647; // 0x7fffffff
   }
@@ -44617,6 +44807,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);
@@ -44667,6 +44858,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 {
@@ -45023,7 +45216,9 @@
     field public static final int PROTOCOL_IP = 0; // 0x0
     field public static final int PROTOCOL_IPV4V6 = 2; // 0x2
     field public static final int PROTOCOL_IPV6 = 1; // 0x1
+    field public static final int PROTOCOL_NON_IP = 4; // 0x4
     field public static final int PROTOCOL_PPP = 3; // 0x3
+    field public static final int PROTOCOL_UNSTRUCTURED = 5; // 0x5
     field public static final int TYPE_CBS = 128; // 0x80
     field public static final int TYPE_DEFAULT = 17; // 0x11
     field public static final int TYPE_DUN = 8; // 0x8
@@ -49672,6 +49867,7 @@
   }
 
   public class Surface implements android.os.Parcelable {
+    ctor public Surface(android.view.SurfaceControl);
     ctor public Surface(android.graphics.SurfaceTexture);
     method public int describeContents();
     method public boolean isValid();
@@ -49694,6 +49890,38 @@
     ctor public Surface.OutOfResourcesException(String);
   }
 
+  public final class SurfaceControl implements android.os.Parcelable {
+    method public int describeContents();
+    method public boolean isValid();
+    method public void readFromParcel(android.os.Parcel);
+    method public void release();
+    method public void writeToParcel(android.os.Parcel, int);
+    field public static final android.os.Parcelable.Creator<android.view.SurfaceControl> CREATOR;
+  }
+
+  public static class SurfaceControl.Builder {
+    ctor public SurfaceControl.Builder();
+    method public android.view.SurfaceControl build();
+    method public android.view.SurfaceControl.Builder setBufferSize(@IntRange(from=0) int, @IntRange(from=0) int);
+    method @NonNull public android.view.SurfaceControl.Builder setFormat(int);
+    method public android.view.SurfaceControl.Builder setName(String);
+    method @NonNull public android.view.SurfaceControl.Builder setOpaque(boolean);
+    method @NonNull public android.view.SurfaceControl.Builder setParent(@Nullable android.view.SurfaceControl);
+  }
+
+  public static class SurfaceControl.Transaction implements java.io.Closeable {
+    ctor public SurfaceControl.Transaction();
+    method public void apply();
+    method public void close();
+    method @NonNull public android.view.SurfaceControl.Transaction merge(@NonNull android.view.SurfaceControl.Transaction);
+    method @NonNull public android.view.SurfaceControl.Transaction reparent(@NonNull android.view.SurfaceControl, @Nullable android.view.SurfaceControl);
+    method @NonNull public android.view.SurfaceControl.Transaction setAlpha(@NonNull android.view.SurfaceControl, @FloatRange(from=0.0, to=1.0) float);
+    method @NonNull public android.view.SurfaceControl.Transaction setBufferSize(@NonNull android.view.SurfaceControl, @IntRange(from=0) int, @IntRange(from=0) int);
+    method @NonNull public android.view.SurfaceControl.Transaction setGeometry(@NonNull android.view.SurfaceControl, @Nullable android.graphics.Rect, @Nullable android.graphics.Rect, int);
+    method @NonNull public android.view.SurfaceControl.Transaction setLayer(@NonNull android.view.SurfaceControl, @IntRange(from=java.lang.Integer.MIN_VALUE, to=java.lang.Integer.MAX_VALUE) int);
+    method @NonNull public android.view.SurfaceControl.Transaction setVisibility(@NonNull android.view.SurfaceControl, boolean);
+  }
+
   public interface SurfaceHolder {
     method public void addCallback(android.view.SurfaceHolder.Callback);
     method public android.view.Surface getSurface();
@@ -49738,6 +49966,7 @@
     ctor public SurfaceView(android.content.Context, android.util.AttributeSet, int, int);
     method public boolean gatherTransparentRegion(android.graphics.Region);
     method public android.view.SurfaceHolder getHolder();
+    method public android.view.SurfaceControl getSurfaceControl();
     method public void setSecure(boolean);
     method public void setZOrderMediaOverlay(boolean);
     method public void setZOrderOnTop(boolean);
@@ -52543,6 +52772,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);
   }
@@ -52559,12 +52789,12 @@
     method @NonNull public final android.view.contentcapture.ContentCaptureSession createContentCaptureSession(@NonNull android.view.contentcapture.ContentCaptureContext);
     method public final void destroy();
     method public final android.view.contentcapture.ContentCaptureSessionId getContentCaptureSessionId();
-    method @NonNull public android.view.autofill.AutofillId newAutofillId(@NonNull android.view.autofill.AutofillId, int);
-    method @NonNull public final android.view.ViewStructure newVirtualViewStructure(@NonNull android.view.autofill.AutofillId, int);
+    method @NonNull public android.view.autofill.AutofillId newAutofillId(@NonNull android.view.autofill.AutofillId, long);
+    method @NonNull public final android.view.ViewStructure newVirtualViewStructure(@NonNull android.view.autofill.AutofillId, long);
     method public final void notifyViewAppeared(@NonNull android.view.ViewStructure);
     method public final void notifyViewDisappeared(@NonNull android.view.autofill.AutofillId);
     method public final void notifyViewTextChanged(@NonNull android.view.autofill.AutofillId, @Nullable CharSequence, int);
-    method public final void notifyViewsDisappeared(@NonNull android.view.autofill.AutofillId, @NonNull int[]);
+    method public final void notifyViewsDisappeared(@NonNull android.view.autofill.AutofillId, @NonNull long[]);
   }
 
   public final class ContentCaptureSessionId implements android.os.Parcelable {
@@ -52903,8 +53133,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);
@@ -52922,7 +53152,7 @@
     method public void updateCursorAnchorInfo(android.view.View, android.view.inputmethod.CursorAnchorInfo);
     method public void updateExtractedText(android.view.View, int, android.view.inputmethod.ExtractedText);
     method public void updateSelection(android.view.View, int, int, int, int);
-    method public void viewClicked(android.view.View);
+    method @Deprecated public void viewClicked(android.view.View);
     field public static final int HIDE_IMPLICIT_ONLY = 1; // 0x1
     field public static final int HIDE_NOT_ALWAYS = 2; // 0x2
     field public static final int RESULT_HIDDEN = 3; // 0x3
@@ -53001,6 +53231,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);
   }
@@ -53021,7 +53261,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);
@@ -53106,8 +53346,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 {
@@ -53258,6 +53498,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);
@@ -53335,7 +53576,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();
@@ -53348,6 +53589,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
@@ -53382,7 +53624,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);
@@ -53393,6 +53635,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 {
@@ -56769,7 +57012,7 @@
     method public boolean isCursorVisible();
     method public boolean isElegantTextHeight();
     method public boolean isFallbackLineSpacing();
-    method public final boolean isHorizontallyScrolling();
+    method public final boolean isHorizontallyScrollable();
     method public boolean isInputMethodTarget();
     method public boolean isSingleLine();
     method public boolean isSuggestionsEnabled();
@@ -62143,20 +62386,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/removed.txt b/api/removed.txt
index 2c567e0..9f4b041 100644
--- a/api/removed.txt
+++ b/api/removed.txt
@@ -239,8 +239,8 @@
   }
 
   public class Picture {
-    method @Deprecated public static android.graphics.Picture createFromStream(java.io.InputStream);
-    method @Deprecated public void writeToStream(java.io.OutputStream);
+    method @Deprecated public static android.graphics.Picture createFromStream(@NonNull java.io.InputStream);
+    method @Deprecated public void writeToStream(@NonNull java.io.OutputStream);
   }
 
   @Deprecated public class PixelXorXfermode extends android.graphics.Xfermode {
@@ -325,7 +325,7 @@
   @IntDef({0x0, 0xa, 0x14, 0x1e}) @java.lang.annotation.Retention(java.lang.annotation.RetentionPolicy.SOURCE) public static @interface NetworkBadging.Badging {
   }
 
-  public class SSLCertificateSocketFactory extends javax.net.ssl.SSLSocketFactory {
+  @Deprecated public class SSLCertificateSocketFactory extends javax.net.ssl.SSLSocketFactory {
     method @Deprecated public static org.apache.http.conn.ssl.SSLSocketFactory getHttpSocketFactory(int, android.net.SSLSessionCache);
   }
 
diff --git a/api/system-current.txt b/api/system-current.txt
index 8cde52b..30d42a23 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
   }
 
@@ -882,8 +883,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 +898,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 +907,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 +930,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 +1053,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 {
@@ -1095,6 +1097,8 @@
   public static final class UsageEvents.Event {
     method public int getInstanceId();
     method public String getNotificationChannelId();
+    method @Nullable public String getTaskRootClassName();
+    method @Nullable public String getTaskRootPackageName();
     field public static final int NOTIFICATION_INTERRUPTION = 12; // 0xc
     field public static final int NOTIFICATION_SEEN = 10; // 0xa
     field public static final int SLICE_PINNED = 14; // 0xe
@@ -1109,6 +1113,8 @@
   public final class UsageStatsManager {
     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);
@@ -1116,6 +1122,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);
@@ -1124,6 +1131,8 @@
     field public static final String EXTRA_TIME_USED = "android.app.usage.extra.TIME_USED";
     field public static final int STANDBY_BUCKET_EXEMPTED = 5; // 0x5
     field public static final int STANDBY_BUCKET_NEVER = 50; // 0x32
+    field public static final int USAGE_SOURCE_CURRENT_ACTIVITY = 2; // 0x2
+    field public static final int USAGE_SOURCE_TASK_ROOT_ACTIVITY = 1; // 0x1
   }
 
 }
@@ -1136,19 +1145,50 @@
     method @RequiresPermission(android.Manifest.permission.BLUETOOTH_ADMIN) public boolean enableNoAutoConnect();
     method public boolean isBleScanAlwaysAvailable();
     method public boolean isLeEnabled();
+    method @RequiresPermission(android.Manifest.permission.BLUETOOTH_PRIVILEGED) public boolean registerMetadataListener(android.bluetooth.BluetoothDevice, android.bluetooth.BluetoothAdapter.MetadataListener, android.os.Handler);
+    method @RequiresPermission(android.Manifest.permission.BLUETOOTH_PRIVILEGED) public boolean unregisterMetadataListener(android.bluetooth.BluetoothDevice);
     field public static final String ACTION_BLE_STATE_CHANGED = "android.bluetooth.adapter.action.BLE_STATE_CHANGED";
     field public static final String ACTION_REQUEST_BLE_SCAN_ALWAYS_AVAILABLE = "android.bluetooth.adapter.action.REQUEST_BLE_SCAN_ALWAYS_AVAILABLE";
   }
 
+  public abstract static class BluetoothAdapter.MetadataListener {
+    ctor public BluetoothAdapter.MetadataListener();
+    method public void onMetadataChanged(android.bluetooth.BluetoothDevice, int, String);
+  }
+
   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
+    field public static final int METADATA_IS_UNTHETHERED_HEADSET = 6; // 0x6
+    field public static final int METADATA_MAIN_ICON = 5; // 0x5
+    field public static final int METADATA_MANUFACTURER_NAME = 0; // 0x0
+    field public static final int METADATA_MAX_LENGTH = 2048; // 0x800
+    field public static final int METADATA_MODEL_NAME = 1; // 0x1
+    field public static final int METADATA_SOFTWARE_VERSION = 2; // 0x2
+    field public static final int METADATA_UNTHETHERED_CASE_BATTERY = 12; // 0xc
+    field public static final int METADATA_UNTHETHERED_CASE_CHARGING = 15; // 0xf
+    field public static final int METADATA_UNTHETHERED_CASE_ICON = 9; // 0x9
+    field public static final int METADATA_UNTHETHERED_LEFT_BATTERY = 10; // 0xa
+    field public static final int METADATA_UNTHETHERED_LEFT_CHARGING = 13; // 0xd
+    field public static final int METADATA_UNTHETHERED_LEFT_ICON = 7; // 0x7
+    field public static final int METADATA_UNTHETHERED_RIGHT_BATTERY = 11; // 0xb
+    field public static final int METADATA_UNTHETHERED_RIGHT_CHARGING = 14; // 0xe
+    field public static final int METADATA_UNTHETHERED_RIGHT_ICON = 8; // 0x8
   }
 
   public final class BluetoothHeadset implements android.bluetooth.BluetoothProfile {
@@ -1322,6 +1362,7 @@
 
   public class OverlayManager {
     method public java.util.List<android.content.om.OverlayInfo> getOverlayInfosForTarget(@Nullable String, int);
+    method public boolean setEnabled(@Nullable String, boolean, int);
     method public boolean setEnabledExclusiveInCategory(@Nullable String, int);
   }
 
@@ -1644,22 +1685,17 @@
 package android.content.rollback {
 
   public final class PackageRollbackInfo implements android.os.Parcelable {
-    ctor public PackageRollbackInfo(String, android.content.rollback.PackageRollbackInfo.PackageVersion, android.content.rollback.PackageRollbackInfo.PackageVersion);
     method public int describeContents();
+    method public String getPackageName();
+    method public android.content.pm.VersionedPackage getVersionRolledBackFrom();
+    method public android.content.pm.VersionedPackage getVersionRolledBackTo();
     method public void writeToParcel(android.os.Parcel, int);
     field public static final android.os.Parcelable.Creator<android.content.rollback.PackageRollbackInfo> CREATOR;
-    field public final android.content.rollback.PackageRollbackInfo.PackageVersion higherVersion;
-    field public final android.content.rollback.PackageRollbackInfo.PackageVersion lowerVersion;
-    field public final String packageName;
-  }
-
-  public static class PackageRollbackInfo.PackageVersion {
-    ctor public PackageRollbackInfo.PackageVersion(long);
-    field public final long versionCode;
   }
 
   public final class RollbackInfo implements android.os.Parcelable {
     method public int describeContents();
+    method public int getRollbackId();
     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;
@@ -1776,8 +1812,20 @@
   }
 
   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
+    field public static final int CAPABILITY_PROTECTED_CONTENT = 1; // 0x1
   }
 
   public final class DisplayManager {
@@ -1805,9 +1853,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
@@ -1897,6 +1951,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);
   }
@@ -2023,6 +2080,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);
@@ -3204,12 +3271,14 @@
     method public int getQuality();
     method public float getSmallestDisplacement();
     method public android.os.WorkSource getWorkSource();
+    method public boolean isLocationSettingsIgnored();
     method public boolean isLowPowerMode();
     method public android.location.LocationRequest setExpireAt(long);
     method public android.location.LocationRequest setExpireIn(long);
     method public android.location.LocationRequest setFastestInterval(long);
     method public void setHideFromAppOps(boolean);
     method public android.location.LocationRequest setInterval(long);
+    method @RequiresPermission(android.Manifest.permission.WRITE_SECURE_SETTINGS) public android.location.LocationRequest setLocationSettingsIgnored(boolean);
     method public android.location.LocationRequest setLowPowerMode(boolean);
     method public android.location.LocationRequest setNumUpdates(int);
     method public android.location.LocationRequest setProvider(String);
@@ -3345,6 +3414,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);
@@ -3359,6 +3437,15 @@
     method @NonNull public android.media.TimedMetaData.Builder setTimedMetaData(long, @NonNull byte[]);
   }
 
+  public abstract class VolumeProvider {
+    method public void setCallback(android.media.VolumeProvider.Callback);
+  }
+
+  public abstract static class VolumeProvider.Callback {
+    ctor public VolumeProvider.Callback();
+    method public abstract void onVolumeChanged(android.media.VolumeProvider);
+  }
+
 }
 
 package android.media.audiopolicy {
@@ -3530,6 +3617,80 @@
     method public void unregisterCallback(@NonNull android.media.session.ControllerCallbackLink);
   }
 
+  public abstract static class MediaSession.Callback {
+    method public void onSetMediaButtonEventDelegate(@NonNull android.media.session.MediaSessionEngine.MediaButtonEventDelegate);
+  }
+
+  public static final class MediaSession.Token implements android.os.Parcelable {
+    method public android.media.session.ControllerLink getControllerLink();
+  }
+
+  public final class MediaSessionEngine implements java.lang.AutoCloseable {
+    ctor public MediaSessionEngine(@NonNull android.content.Context, @NonNull android.media.session.SessionLink, @NonNull android.media.session.SessionCallbackLink, @NonNull android.media.session.MediaSessionEngine.CallbackStub, int);
+    method public void close();
+    method public String getCallingPackage();
+    method @NonNull public android.media.session.MediaController getController();
+    method @NonNull public android.media.session.MediaSessionManager.RemoteUserInfo getCurrentControllerInfo();
+    method @NonNull public android.media.session.MediaSession.Token getSessionToken();
+    method public boolean isActive();
+    method public static boolean isActiveState(int);
+    method public void sendSessionEvent(@NonNull String, @Nullable android.os.Bundle);
+    method public void setActive(boolean);
+    method public void setCallback(@Nullable android.media.session.MediaSession.Callback);
+    method public void setCallback(@Nullable android.media.session.MediaSession.Callback, @NonNull android.os.Handler);
+    method public void setExtras(@Nullable android.os.Bundle);
+    method public void setFlags(int);
+    method public void setMediaButtonReceiver(@Nullable android.app.PendingIntent);
+    method public void setMetadata(@Nullable android.media.MediaMetadata);
+    method public void setPlaybackState(@Nullable android.media.session.PlaybackState);
+    method public void setPlaybackToLocal(android.media.AudioAttributes);
+    method public void setPlaybackToRemote(@NonNull android.media.VolumeProvider);
+    method public void setQueue(@Nullable java.util.List<android.media.session.MediaSession.QueueItem>);
+    method public void setQueueTitle(@Nullable CharSequence);
+    method public void setRatingType(int);
+    method public void setSessionActivity(@Nullable android.app.PendingIntent);
+  }
+
+  public static final class MediaSessionEngine.CallbackStub {
+    ctor public MediaSessionEngine.CallbackStub();
+    method public void onAdjustVolume(String, int, int, android.media.session.ControllerCallbackLink, int);
+    method public void onCommand(String, int, int, android.media.session.ControllerCallbackLink, String, android.os.Bundle, android.os.ResultReceiver);
+    method public void onCustomAction(String, int, int, android.media.session.ControllerCallbackLink, String, android.os.Bundle);
+    method public void onFastForward(String, int, int, android.media.session.ControllerCallbackLink);
+    method public void onMediaButton(String, int, int, android.content.Intent, int, android.os.ResultReceiver);
+    method public void onMediaButtonFromController(String, int, int, android.media.session.ControllerCallbackLink, android.content.Intent);
+    method public void onNext(String, int, int, android.media.session.ControllerCallbackLink);
+    method public void onPause(String, int, int, android.media.session.ControllerCallbackLink);
+    method public void onPlay(String, int, int, android.media.session.ControllerCallbackLink);
+    method public void onPlayFromMediaId(String, int, int, android.media.session.ControllerCallbackLink, String, android.os.Bundle);
+    method public void onPlayFromSearch(String, int, int, android.media.session.ControllerCallbackLink, String, android.os.Bundle);
+    method public void onPlayFromUri(String, int, int, android.media.session.ControllerCallbackLink, android.net.Uri, android.os.Bundle);
+    method public void onPrepare(String, int, int, android.media.session.ControllerCallbackLink);
+    method public void onPrepareFromMediaId(String, int, int, android.media.session.ControllerCallbackLink, String, android.os.Bundle);
+    method public void onPrepareFromSearch(String, int, int, android.media.session.ControllerCallbackLink, String, android.os.Bundle);
+    method public void onPrepareFromUri(String, int, int, android.media.session.ControllerCallbackLink, android.net.Uri, android.os.Bundle);
+    method public void onPrevious(String, int, int, android.media.session.ControllerCallbackLink);
+    method public void onRate(String, int, int, android.media.session.ControllerCallbackLink, android.media.Rating);
+    method public void onRewind(String, int, int, android.media.session.ControllerCallbackLink);
+    method public void onSeekTo(String, int, int, android.media.session.ControllerCallbackLink, long);
+    method public void onSetVolumeTo(String, int, int, android.media.session.ControllerCallbackLink, int);
+    method public void onSkipToTrack(String, int, int, android.media.session.ControllerCallbackLink, long);
+    method public void onStop(String, int, int, android.media.session.ControllerCallbackLink);
+  }
+
+  public static interface MediaSessionEngine.MediaButtonEventDelegate {
+    method public boolean onMediaButtonIntent(android.content.Intent);
+  }
+
+  public static final class MediaSessionEngine.QueueItem {
+    ctor public MediaSessionEngine.QueueItem(android.media.MediaDescription, long);
+    ctor public MediaSessionEngine.QueueItem(android.os.Parcel);
+    method public android.media.MediaDescription getDescription();
+    method public long getQueueId();
+    method public void writeToParcel(android.os.Parcel, int);
+    field public static final int UNKNOWN_ID = -1; // 0xffffffff
+  }
+
   public final class MediaSessionManager {
     method @RequiresPermission(android.Manifest.permission.SET_MEDIA_KEY_LISTENER) public void setOnMediaKeyListener(android.media.session.MediaSessionManager.OnMediaKeyListener, @Nullable android.os.Handler);
     method @RequiresPermission(android.Manifest.permission.SET_VOLUME_KEY_LONG_PRESS_LISTENER) public void setOnVolumeKeyLongPressListener(android.media.session.MediaSessionManager.OnVolumeKeyLongPressListener, @Nullable android.os.Handler);
@@ -3895,12 +4056,16 @@
 package android.net {
 
   public class CaptivePortal implements android.os.Parcelable {
+    ctor public CaptivePortal(android.os.IBinder);
+    method public void useNetwork();
     field public static final int APP_RETURN_DISMISSED = 0; // 0x0
     field public static final int APP_RETURN_UNWANTED = 1; // 0x1
     field public static final int APP_RETURN_WANTED_AS_IS = 2; // 0x2
   }
 
   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();
     method @RequiresPermission(anyOf={"android.permission.NETWORK_SETTINGS", android.Manifest.permission.NETWORK_SETUP_WIZARD, "android.permission.NETWORK_STACK"}) public void setAirplaneMode(boolean);
@@ -3920,6 +4085,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;
@@ -3952,6 +4121,7 @@
   }
 
   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();
@@ -3962,9 +4132,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();
@@ -3982,6 +4155,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);
@@ -4036,6 +4211,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
@@ -4169,6 +4345,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);
@@ -4217,6 +4394,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
@@ -4488,22 +4679,22 @@
   }
 
   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);
-    method @RequiresPermission(anyOf={"android.permission.NETWORK_SETTINGS", android.Manifest.permission.NETWORK_SETUP_WIZARD, "android.permission.NETWORK_STACK"}) public void disableEphemeralNetwork(String);
     method @RequiresPermission(anyOf={"android.permission.NETWORK_SETTINGS", android.Manifest.permission.NETWORK_SETUP_WIZARD, "android.permission.NETWORK_STACK"}) public void forget(int, android.net.wifi.WifiManager.ActionListener);
     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);
@@ -4512,7 +4703,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
@@ -4548,17 +4739,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 {
@@ -4690,6 +4872,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 {
@@ -4725,7 +4932,7 @@
     method public abstract void onProvisioningStatus(int);
     field public static final int OSU_FAILURE_ADD_PASSPOINT_CONFIGURATION = 22; // 0x16
     field public static final int OSU_FAILURE_AP_CONNECTION = 1; // 0x1
-    field public static final int OSU_FAILURE_INVALID_SERVER_URL = 8; // 0x8
+    field public static final int OSU_FAILURE_INVALID_URL_FORMAT_FOR_OSU = 8; // 0x8
     field public static final int OSU_FAILURE_NO_AAA_SERVER_TRUST_ROOT_NODE = 17; // 0x11
     field public static final int OSU_FAILURE_NO_AAA_TRUST_ROOT_CERTIFICATE = 21; // 0x15
     field public static final int OSU_FAILURE_NO_OSU_ACTIVITY_FOUND = 14; // 0xe
@@ -4835,6 +5042,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";
   }
@@ -5280,8 +5488,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";
@@ -5442,12 +5651,48 @@
     field public static final String NAMESPACE_GAME_DRIVER = "game_driver";
     field public static final String NAMESPACE_INPUT_NATIVE_BOOT = "input_native_boot";
     field public static final String NAMESPACE_NETD_NATIVE = "netd_native";
+    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";
+    field public static final String PROPERTY_RAMPING_RINGER_DURATION = "ramping_duration";
+  }
+
   public final class DocumentsContract {
     method public static boolean isManageMode(android.net.Uri);
     method public static android.net.Uri setManageMode(android.net.Uri);
@@ -5629,6 +5874,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
@@ -5904,10 +6150,7 @@
     method public int getTaskId();
   }
 
-  public final class FillResponse 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.service.autofill.augmented.FillResponse> CREATOR;
+  public final class FillResponse {
   }
 
   public static final class FillResponse.Builder {
@@ -5921,7 +6164,6 @@
     ctor public FillWindow();
     method public void destroy();
     method public boolean update(@NonNull android.service.autofill.augmented.PresentationParams.Area, @NonNull android.view.View, long);
-    field public static final long FLAG_METADATA_ADDRESS = 1L; // 0x1L
   }
 
   public abstract class PresentationParams {
@@ -6083,12 +6325,15 @@
     method public abstract int onSwitchToSubscription(int, @Nullable String, boolean);
     method public abstract int onUpdateSubscriptionNickname(int, String, String);
     field public static final String ACTION_BIND_CARRIER_PROVISIONING_SERVICE = "android.service.euicc.action.BIND_CARRIER_PROVISIONING_SERVICE";
+    field public static final String ACTION_DELETE_SUBSCRIPTION_PRIVILEGED = "android.service.euicc.action.DELETE_SUBSCRIPTION_PRIVILEGED";
     field public static final String ACTION_MANAGE_EMBEDDED_SUBSCRIPTIONS = "android.service.euicc.action.MANAGE_EMBEDDED_SUBSCRIPTIONS";
     field public static final String ACTION_PROVISION_EMBEDDED_SUBSCRIPTION = "android.service.euicc.action.PROVISION_EMBEDDED_SUBSCRIPTION";
+    field public static final String ACTION_RENAME_SUBSCRIPTION_PRIVILEGED = "android.service.euicc.action.RENAME_SUBSCRIPTION_PRIVILEGED";
     field @Deprecated public static final String ACTION_RESOLVE_CONFIRMATION_CODE = "android.service.euicc.action.RESOLVE_CONFIRMATION_CODE";
     field public static final String ACTION_RESOLVE_DEACTIVATE_SIM = "android.service.euicc.action.RESOLVE_DEACTIVATE_SIM";
     field public static final String ACTION_RESOLVE_NO_PRIVILEGES = "android.service.euicc.action.RESOLVE_NO_PRIVILEGES";
     field public static final String ACTION_RESOLVE_RESOLVABLE_ERRORS = "android.service.euicc.action.RESOLVE_RESOLVABLE_ERRORS";
+    field public static final String ACTION_TOGGLE_SUBSCRIPTION_PRIVILEGED = "android.service.euicc.action.TOGGLE_SUBSCRIPTION_PRIVILEGED";
     field public static final String CATEGORY_EUICC_UI = "android.service.euicc.category.EUICC_UI";
     field public static final String EUICC_SERVICE_INTERFACE = "android.service.euicc.EuiccService";
     field public static final String EXTRA_RESOLUTION_ALLOW_POLICY_RULES = "android.service.euicc.extra.RESOLUTION_ALLOW_POLICY_RULES";
@@ -6144,77 +6389,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);
@@ -6666,6 +6840,7 @@
     method @RequiresPermission(android.Manifest.permission.MODIFY_PHONE_STATE) public boolean isInEmergencyCall();
     method @RequiresPermission(anyOf={android.Manifest.permission.READ_PRIVILEGED_PHONE_STATE, android.Manifest.permission.READ_PHONE_STATE}) public boolean isRinging();
     method @Deprecated @RequiresPermission(allOf={android.Manifest.permission.MODIFY_PHONE_STATE, android.Manifest.permission.WRITE_SECURE_SETTINGS}) public boolean setDefaultDialer(String);
+    method @RequiresPermission(android.Manifest.permission.MODIFY_PHONE_STATE) public void setUserSelectedOutgoingPhoneAccount(android.telecom.PhoneAccountHandle);
     field public static final String EXTRA_CALL_BACK_INTENT = "android.telecom.extra.CALL_BACK_INTENT";
     field public static final String EXTRA_CLEAR_MISSED_CALLS_INTENT = "android.telecom.extra.CLEAR_MISSED_CALLS_INTENT";
     field public static final String EXTRA_CONNECTION_SERVICE = "android.telecom.extra.CONNECTION_SERVICE";
@@ -6750,42 +6925,211 @@
   }
 
   public final class DataFailCause {
+    field public static final int ACCESS_ATTEMPT_ALREADY_IN_PROGRESS = 2219; // 0x8ab
+    field public static final int ACCESS_BLOCK = 2087; // 0x827
+    field public static final int ACCESS_BLOCK_ALL = 2088; // 0x828
+    field public static final int ACCESS_CLASS_DSAC_REJECTION = 2108; // 0x83c
+    field public static final int ACCESS_CONTROL_LIST_CHECK_FAILURE = 2128; // 0x850
+    field public static final int ACTIVATION_REJECTED_BCM_VIOLATION = 48; // 0x30
     field public static final int ACTIVATION_REJECT_GGSN = 30; // 0x1e
     field public static final int ACTIVATION_REJECT_UNSPECIFIED = 31; // 0x1f
     field public static final int ACTIVE_PDP_CONTEXT_MAX_NUMBER_REACHED = 65; // 0x41
+    field public static final int APN_DISABLED = 2045; // 0x7fd
+    field public static final int APN_DISALLOWED_ON_ROAMING = 2059; // 0x80b
+    field public static final int APN_MISMATCH = 2054; // 0x806
+    field public static final int APN_PARAMETERS_CHANGED = 2060; // 0x80c
+    field public static final int APN_PENDING_HANDOVER = 2041; // 0x7f9
     field public static final int APN_TYPE_CONFLICT = 112; // 0x70
     field public static final int AUTH_FAILURE_ON_EMERGENCY_CALL = 122; // 0x7a
+    field public static final int BEARER_HANDLING_NOT_SUPPORTED = 60; // 0x3c
+    field public static final int CALL_DISALLOWED_IN_ROAMING = 2068; // 0x814
+    field public static final int CALL_PREEMPT_BY_EMERGENCY_APN = 127; // 0x7f
+    field public static final int CANNOT_ENCODE_OTA_MESSAGE = 2159; // 0x86f
+    field public static final int CDMA_ALERT_STOP = 2077; // 0x81d
+    field public static final int CDMA_INCOMING_CALL = 2076; // 0x81c
+    field public static final int CDMA_INTERCEPT = 2073; // 0x819
+    field public static final int CDMA_LOCK = 2072; // 0x818
+    field public static final int CDMA_RELEASE_DUE_TO_SO_REJECTION = 2075; // 0x81b
+    field public static final int CDMA_REORDER = 2074; // 0x81a
+    field public static final int CDMA_RETRY_ORDER = 2086; // 0x826
+    field public static final int CHANNEL_ACQUISITION_FAILURE = 2078; // 0x81e
+    field public static final int CLOSE_IN_PROGRESS = 2030; // 0x7ee
+    field public static final int COLLISION_WITH_NETWORK_INITIATED_REQUEST = 56; // 0x38
     field public static final int COMPANION_IFACE_IN_USE = 118; // 0x76
+    field public static final int CONCURRENT_SERVICES_INCOMPATIBLE = 2083; // 0x823
+    field public static final int CONCURRENT_SERVICES_NOT_ALLOWED = 2091; // 0x82b
+    field public static final int CONCURRENT_SERVICE_NOT_SUPPORTED_BY_BASE_STATION = 2080; // 0x820
     field public static final int CONDITIONAL_IE_ERROR = 100; // 0x64
+    field public static final int CONGESTION = 2106; // 0x83a
+    field public static final int CONNECTION_RELEASED = 2113; // 0x841
+    field public static final int CS_DOMAIN_NOT_AVAILABLE = 2181; // 0x885
+    field public static final int CS_FALLBACK_CALL_ESTABLISHMENT_NOT_ALLOWED = 2188; // 0x88c
+    field public static final int DATA_PLAN_EXPIRED = 2198; // 0x896
+    field public static final int DATA_ROAMING_SETTINGS_DISABLED = 2064; // 0x810
+    field public static final int DATA_SETTINGS_DISABLED = 2063; // 0x80f
+    field public static final int DBM_OR_SMS_IN_PROGRESS = 2211; // 0x8a3
+    field public static final int DDS_SWITCHED = 2065; // 0x811
+    field public static final int DDS_SWITCH_IN_PROGRESS = 2067; // 0x813
+    field public static final int DRB_RELEASED_BY_RRC = 2112; // 0x840
+    field public static final int DS_EXPLICIT_DEACTIVATION = 2125; // 0x84d
+    field public static final int DUAL_SWITCH = 2227; // 0x8b3
+    field public static final int DUN_CALL_DISALLOWED = 2056; // 0x808
+    field public static final int DUPLICATE_BEARER_ID = 2118; // 0x846
+    field public static final int EHRPD_TO_HRPD_FALLBACK = 2049; // 0x801
+    field public static final int EMBMS_NOT_ENABLED = 2193; // 0x891
+    field public static final int EMBMS_REGULAR_DEACTIVATION = 2195; // 0x893
     field public static final int EMERGENCY_IFACE_ONLY = 116; // 0x74
+    field public static final int EMERGENCY_MODE = 2221; // 0x8ad
     field public static final int EMM_ACCESS_BARRED = 115; // 0x73
     field public static final int EMM_ACCESS_BARRED_INFINITE_RETRY = 121; // 0x79
+    field public static final int EMM_ATTACH_FAILED = 2115; // 0x843
+    field public static final int EMM_ATTACH_STARTED = 2116; // 0x844
+    field public static final int EMM_DETACHED = 2114; // 0x842
+    field public static final int EMM_T3417_EXPIRED = 2130; // 0x852
+    field public static final int EMM_T3417_EXT_EXPIRED = 2131; // 0x853
+    field public static final int EPS_SERVICES_AND_NON_EPS_SERVICES_NOT_ALLOWED = 2178; // 0x882
+    field public static final int EPS_SERVICES_NOT_ALLOWED_IN_PLMN = 2179; // 0x883
     field public static final int ERROR_UNSPECIFIED = 65535; // 0xffff
+    field public static final int ESM_BAD_OTA_MESSAGE = 2122; // 0x84a
+    field public static final int ESM_BEARER_DEACTIVATED_TO_SYNC_WITH_NETWORK = 2120; // 0x848
+    field public static final int ESM_COLLISION_SCENARIOS = 2119; // 0x847
+    field public static final int ESM_CONTEXT_TRANSFERRED_DUE_TO_IRAT = 2124; // 0x84c
+    field public static final int ESM_DOWNLOAD_SERVER_REJECTED_THE_CALL = 2123; // 0x84b
+    field public static final int ESM_FAILURE = 2182; // 0x886
     field public static final int ESM_INFO_NOT_RECEIVED = 53; // 0x35
+    field public static final int ESM_LOCAL_CAUSE_NONE = 2126; // 0x84e
+    field public static final int ESM_NW_ACTIVATED_DED_BEARER_WITH_ID_OF_DEF_BEARER = 2121; // 0x849
+    field public static final int ESM_PROCEDURE_TIME_OUT = 2155; // 0x86b
+    field public static final int ESM_UNKNOWN_EPS_BEARER_CONTEXT = 2111; // 0x83f
+    field public static final int EVDO_CONNECTION_DENY_BY_BILLING_OR_AUTHENTICATION_FAILURE = 2201; // 0x899
+    field public static final int EVDO_CONNECTION_DENY_BY_GENERAL_OR_NETWORK_BUSY = 2200; // 0x898
+    field public static final int EVDO_HDR_CHANGED = 2202; // 0x89a
+    field public static final int EVDO_HDR_CONNECTION_SETUP_TIMEOUT = 2206; // 0x89e
+    field public static final int EVDO_HDR_EXITED = 2203; // 0x89b
+    field public static final int EVDO_HDR_NO_SESSION = 2204; // 0x89c
+    field public static final int EVDO_USING_GPS_FIX_INSTEAD_OF_HDR_CALL = 2205; // 0x89d
+    field public static final int FADE = 2217; // 0x8a9
+    field public static final int FAILED_TO_ACQUIRE_COLOCATED_HDR = 2207; // 0x89f
     field public static final int FEATURE_NOT_SUPP = 40; // 0x28
     field public static final int FILTER_SEMANTIC_ERROR = 44; // 0x2c
     field public static final int FILTER_SYTAX_ERROR = 45; // 0x2d
+    field public static final int FORBIDDEN_APN_NAME = 2066; // 0x812
     field public static final int GPRS_REGISTRATION_FAIL = -2; // 0xfffffffe
+    field public static final int GPRS_SERVICES_AND_NON_GPRS_SERVICES_NOT_ALLOWED = 2097; // 0x831
+    field public static final int GPRS_SERVICES_NOT_ALLOWED = 2098; // 0x832
+    field public static final int GPRS_SERVICES_NOT_ALLOWED_IN_THIS_PLMN = 2103; // 0x837
+    field public static final int HANDOFF_PREFERENCE_CHANGED = 2251; // 0x8cb
+    field public static final int HDR_ACCESS_FAILURE = 2213; // 0x8a5
+    field public static final int HDR_FADE = 2212; // 0x8a4
+    field public static final int HDR_NO_LOCK_GRANTED = 2210; // 0x8a2
     field public static final int IFACE_AND_POL_FAMILY_MISMATCH = 120; // 0x78
     field public static final int IFACE_MISMATCH = 117; // 0x75
+    field public static final int ILLEGAL_ME = 2096; // 0x830
+    field public static final int ILLEGAL_MS = 2095; // 0x82f
+    field public static final int IMEI_NOT_ACCEPTED = 2177; // 0x881
+    field public static final int IMPLICITLY_DETACHED = 2100; // 0x834
+    field public static final int IMSI_UNKNOWN_IN_HOME_SUBSCRIBER_SERVER = 2176; // 0x880
+    field public static final int INCOMING_CALL_REJECTED = 2092; // 0x82c
     field public static final int INSUFFICIENT_RESOURCES = 26; // 0x1a
+    field public static final int INTERFACE_IN_USE = 2058; // 0x80a
     field public static final int INTERNAL_CALL_PREEMPT_BY_HIGH_PRIO_APN = 114; // 0x72
+    field public static final int INTERNAL_EPC_NONEPC_TRANSITION = 2057; // 0x809
+    field public static final int INVALID_CONNECTION_ID = 2156; // 0x86c
+    field public static final int INVALID_DNS_ADDR = 123; // 0x7b
+    field public static final int INVALID_EMM_STATE = 2190; // 0x88e
     field public static final int INVALID_MANDATORY_INFO = 96; // 0x60
+    field public static final int INVALID_MODE = 2223; // 0x8af
     field public static final int INVALID_PCSCF_ADDR = 113; // 0x71
+    field public static final int INVALID_PCSCF_OR_DNS_ADDRESS = 124; // 0x7c
+    field public static final int INVALID_PRIMARY_NSAPI = 2158; // 0x86e
+    field public static final int INVALID_SIM_STATE = 2224; // 0x8b0
     field public static final int INVALID_TRANSACTION_ID = 81; // 0x51
+    field public static final int IPV6_ADDRESS_TRANSFER_FAILED = 2047; // 0x7ff
+    field public static final int IPV6_PREFIX_UNAVAILABLE = 2250; // 0x8ca
     field public static final int IP_ADDRESS_MISMATCH = 119; // 0x77
+    field public static final int IP_VERSION_MISMATCH = 2055; // 0x807
+    field public static final int IRAT_HANDOVER_FAILED = 2194; // 0x892
+    field public static final int IS707B_MAX_ACCESS_PROBES = 2089; // 0x829
+    field public static final int LIMITED_TO_IPV4 = 2234; // 0x8ba
+    field public static final int LIMITED_TO_IPV6 = 2235; // 0x8bb
     field public static final int LLC_SNDCP = 25; // 0x19
+    field public static final int LOCAL_END = 2215; // 0x8a7
+    field public static final int LOCATION_AREA_NOT_ALLOWED = 2102; // 0x836
     field public static final int LOST_CONNECTION = 65540; // 0x10004
+    field public static final int LOWER_LAYER_REGISTRATION_FAILURE = 2197; // 0x895
+    field public static final int LOW_POWER_MODE_OR_POWERING_DOWN = 2044; // 0x7fc
+    field public static final int LTE_NAS_SERVICE_REQUEST_FAILED = 2117; // 0x845
+    field public static final int LTE_THROTTLING_NOT_REQUIRED = 2127; // 0x84f
+    field public static final int MAC_FAILURE = 2183; // 0x887
+    field public static final int MAXIMIUM_NSAPIS_EXCEEDED = 2157; // 0x86d
+    field public static final int MAXINUM_SIZE_OF_L2_MESSAGE_EXCEEDED = 2166; // 0x876
+    field public static final int MAX_ACCESS_PROBE = 2079; // 0x81f
+    field public static final int MAX_IPV4_CONNECTIONS = 2052; // 0x804
+    field public static final int MAX_IPV6_CONNECTIONS = 2053; // 0x805
+    field public static final int MAX_PPP_INACTIVITY_TIMER_EXPIRED = 2046; // 0x7fe
     field public static final int MESSAGE_INCORRECT_SEMANTIC = 95; // 0x5f
     field public static final int MESSAGE_TYPE_UNSUPPORTED = 97; // 0x61
+    field public static final int MIP_CONFIG_FAILURE = 2050; // 0x802
+    field public static final int MIP_FA_ADMIN_PROHIBITED = 2001; // 0x7d1
+    field public static final int MIP_FA_DELIVERY_STYLE_NOT_SUPPORTED = 2012; // 0x7dc
+    field public static final int MIP_FA_ENCAPSULATION_UNAVAILABLE = 2008; // 0x7d8
+    field public static final int MIP_FA_HOME_AGENT_AUTHENTICATION_FAILURE = 2004; // 0x7d4
+    field public static final int MIP_FA_INSUFFICIENT_RESOURCES = 2002; // 0x7d2
+    field public static final int MIP_FA_MALFORMED_REPLY = 2007; // 0x7d7
+    field public static final int MIP_FA_MALFORMED_REQUEST = 2006; // 0x7d6
+    field public static final int MIP_FA_MISSING_CHALLENGE = 2017; // 0x7e1
+    field public static final int MIP_FA_MISSING_HOME_ADDRESS = 2015; // 0x7df
+    field public static final int MIP_FA_MISSING_HOME_AGENT = 2014; // 0x7de
+    field public static final int MIP_FA_MISSING_NAI = 2013; // 0x7dd
+    field public static final int MIP_FA_MOBILE_NODE_AUTHENTICATION_FAILURE = 2003; // 0x7d3
+    field public static final int MIP_FA_REASON_UNSPECIFIED = 2000; // 0x7d0
+    field public static final int MIP_FA_REQUESTED_LIFETIME_TOO_LONG = 2005; // 0x7d5
+    field public static final int MIP_FA_REVERSE_TUNNEL_IS_MANDATORY = 2011; // 0x7db
+    field public static final int MIP_FA_REVERSE_TUNNEL_UNAVAILABLE = 2010; // 0x7da
+    field public static final int MIP_FA_STALE_CHALLENGE = 2018; // 0x7e2
+    field public static final int MIP_FA_UNKNOWN_CHALLENGE = 2016; // 0x7e0
+    field public static final int MIP_FA_VJ_HEADER_COMPRESSION_UNAVAILABLE = 2009; // 0x7d9
+    field public static final int MIP_HA_ADMIN_PROHIBITED = 2020; // 0x7e4
+    field public static final int MIP_HA_ENCAPSULATION_UNAVAILABLE = 2029; // 0x7ed
+    field public static final int MIP_HA_FOREIGN_AGENT_AUTHENTICATION_FAILURE = 2023; // 0x7e7
+    field public static final int MIP_HA_INSUFFICIENT_RESOURCES = 2021; // 0x7e5
+    field public static final int MIP_HA_MALFORMED_REQUEST = 2025; // 0x7e9
+    field public static final int MIP_HA_MOBILE_NODE_AUTHENTICATION_FAILURE = 2022; // 0x7e6
+    field public static final int MIP_HA_REASON_UNSPECIFIED = 2019; // 0x7e3
+    field public static final int MIP_HA_REGISTRATION_ID_MISMATCH = 2024; // 0x7e8
+    field public static final int MIP_HA_REVERSE_TUNNEL_IS_MANDATORY = 2028; // 0x7ec
+    field public static final int MIP_HA_REVERSE_TUNNEL_UNAVAILABLE = 2027; // 0x7eb
+    field public static final int MIP_HA_UNKNOWN_HOME_AGENT_ADDRESS = 2026; // 0x7ea
     field public static final int MISSING_UNKNOWN_APN = 27; // 0x1b
+    field public static final int MODEM_APP_PREEMPTED = 2032; // 0x7f0
+    field public static final int MODEM_RESTART = 2037; // 0x7f5
+    field public static final int MSC_TEMPORARILY_NOT_REACHABLE = 2180; // 0x884
     field public static final int MSG_AND_PROTOCOL_STATE_UNCOMPATIBLE = 101; // 0x65
     field public static final int MSG_TYPE_NONCOMPATIBLE_STATE = 98; // 0x62
+    field public static final int MS_IDENTITY_CANNOT_BE_DERIVED_BY_THE_NETWORK = 2099; // 0x833
+    field public static final int MULTIPLE_PDP_CALL_NOT_ALLOWED = 2192; // 0x890
     field public static final int MULTI_CONN_TO_SAME_PDN_NOT_ALLOWED = 55; // 0x37
+    field public static final int NAS_LAYER_FAILURE = 2191; // 0x88f
+    field public static final int NAS_REQUEST_REJECTED_BY_NETWORK = 2167; // 0x877
     field public static final int NAS_SIGNALLING = 14; // 0xe
     field public static final int NETWORK_FAILURE = 38; // 0x26
+    field public static final int NETWORK_INITIATED_DETACH_NO_AUTO_REATTACH = 2154; // 0x86a
+    field public static final int NETWORK_INITIATED_DETACH_WITH_AUTO_REATTACH = 2153; // 0x869
+    field public static final int NETWORK_INITIATED_TERMINATION = 2031; // 0x7ef
     field public static final int NONE = 0; // 0x0
+    field public static final int NON_IP_NOT_SUPPORTED = 2069; // 0x815
+    field public static final int NORMAL_RELEASE = 2218; // 0x8aa
+    field public static final int NO_CDMA_SERVICE = 2084; // 0x824
+    field public static final int NO_COLLOCATED_HDR = 2225; // 0x8b1
+    field public static final int NO_EPS_BEARER_CONTEXT_ACTIVATED = 2189; // 0x88d
+    field public static final int NO_GPRS_CONTEXT = 2094; // 0x82e
+    field public static final int NO_HYBRID_HDR_SERVICE = 2209; // 0x8a1
+    field public static final int NO_PDP_CONTEXT_ACTIVATED = 2107; // 0x83b
+    field public static final int NO_RESPONSE_FROM_BASE_STATION = 2081; // 0x821
+    field public static final int NO_SERVICE = 2216; // 0x8a8
+    field public static final int NO_SERVICE_ON_GATEWAY = 2093; // 0x82d
     field public static final int NSAPI_IN_USE = 35; // 0x23
+    field public static final int NULL_APN_DISALLOWED = 2061; // 0x80d
     field public static final int OEM_DCFAILCAUSE_1 = 4097; // 0x1001
     field public static final int OEM_DCFAILCAUSE_10 = 4106; // 0x100a
     field public static final int OEM_DCFAILCAUSE_11 = 4107; // 0x100b
@@ -6801,33 +7145,126 @@
     field public static final int OEM_DCFAILCAUSE_7 = 4103; // 0x1007
     field public static final int OEM_DCFAILCAUSE_8 = 4104; // 0x1008
     field public static final int OEM_DCFAILCAUSE_9 = 4105; // 0x1009
+    field public static final int ONLY_IPV4V6_ALLOWED = 57; // 0x39
     field public static final int ONLY_IPV4_ALLOWED = 50; // 0x32
     field public static final int ONLY_IPV6_ALLOWED = 51; // 0x33
+    field public static final int ONLY_NON_IP_ALLOWED = 58; // 0x3a
     field public static final int ONLY_SINGLE_BEARER_ALLOWED = 52; // 0x34
     field public static final int OPERATOR_BARRED = 8; // 0x8
+    field public static final int OTASP_COMMIT_IN_PROGRESS = 2208; // 0x8a0
     field public static final int PDN_CONN_DOES_NOT_EXIST = 54; // 0x36
+    field public static final int PDN_INACTIVITY_TIMER_EXPIRED = 2051; // 0x803
+    field public static final int PDN_IPV4_CALL_DISALLOWED = 2033; // 0x7f1
+    field public static final int PDN_IPV4_CALL_THROTTLED = 2034; // 0x7f2
+    field public static final int PDN_IPV6_CALL_DISALLOWED = 2035; // 0x7f3
+    field public static final int PDN_IPV6_CALL_THROTTLED = 2036; // 0x7f4
+    field public static final int PDN_NON_IP_CALL_DISALLOWED = 2071; // 0x817
+    field public static final int PDN_NON_IP_CALL_THROTTLED = 2070; // 0x816
+    field public static final int PDP_ACTIVATE_MAX_RETRY_FAILED = 2109; // 0x83d
+    field public static final int PDP_DUPLICATE = 2104; // 0x838
+    field public static final int PDP_ESTABLISH_TIMEOUT_EXPIRED = 2161; // 0x871
+    field public static final int PDP_INACTIVE_TIMEOUT_EXPIRED = 2163; // 0x873
+    field public static final int PDP_LOWERLAYER_ERROR = 2164; // 0x874
+    field public static final int PDP_MODIFY_COLLISION = 2165; // 0x875
+    field public static final int PDP_MODIFY_TIMEOUT_EXPIRED = 2162; // 0x872
+    field public static final int PDP_PPP_NOT_SUPPORTED = 2038; // 0x7f6
     field public static final int PDP_WITHOUT_ACTIVE_TFT = 46; // 0x2e
+    field public static final int PHONE_IN_USE = 2222; // 0x8ae
+    field public static final int PHYSICAL_LINK_CLOSE_IN_PROGRESS = 2040; // 0x7f8
+    field public static final int PLMN_NOT_ALLOWED = 2101; // 0x835
+    field public static final int PPP_AUTH_FAILURE = 2229; // 0x8b5
+    field public static final int PPP_CHAP_FAILURE = 2232; // 0x8b8
+    field public static final int PPP_CLOSE_IN_PROGRESS = 2233; // 0x8b9
+    field public static final int PPP_OPTION_MISMATCH = 2230; // 0x8b6
+    field public static final int PPP_PAP_FAILURE = 2231; // 0x8b7
+    field public static final int PPP_TIMEOUT = 2228; // 0x8b4
     field public static final int PREF_RADIO_TECH_CHANGED = -4; // 0xfffffffc
+    field public static final int PROFILE_BEARER_INCOMPATIBLE = 2042; // 0x7fa
     field public static final int PROTOCOL_ERRORS = 111; // 0x6f
     field public static final int QOS_NOT_ACCEPTED = 37; // 0x25
+    field public static final int RADIO_ACCESS_BEARER_FAILURE = 2110; // 0x83e
+    field public static final int RADIO_ACCESS_BEARER_SETUP_FAILURE = 2160; // 0x870
     field public static final int RADIO_NOT_AVAILABLE = 65537; // 0x10001
     field public static final int RADIO_POWER_OFF = -5; // 0xfffffffb
+    field public static final int REDIRECTION_OR_HANDOFF_IN_PROGRESS = 2220; // 0x8ac
     field public static final int REGISTRATION_FAIL = -1; // 0xffffffff
     field public static final int REGULAR_DEACTIVATION = 36; // 0x24
+    field public static final int REJECTED_BY_BASE_STATION = 2082; // 0x822
+    field public static final int RRC_CONNECTION_ABORTED_AFTER_HANDOVER = 2173; // 0x87d
+    field public static final int RRC_CONNECTION_ABORTED_AFTER_IRAT_CELL_CHANGE = 2174; // 0x87e
+    field public static final int RRC_CONNECTION_ABORTED_DUE_TO_IRAT_CHANGE = 2171; // 0x87b
+    field public static final int RRC_CONNECTION_ABORTED_DURING_IRAT_CELL_CHANGE = 2175; // 0x87f
+    field public static final int RRC_CONNECTION_ABORT_REQUEST = 2151; // 0x867
+    field public static final int RRC_CONNECTION_ACCESS_BARRED = 2139; // 0x85b
+    field public static final int RRC_CONNECTION_ACCESS_STRATUM_FAILURE = 2137; // 0x859
+    field public static final int RRC_CONNECTION_ANOTHER_PROCEDURE_IN_PROGRESS = 2138; // 0x85a
+    field public static final int RRC_CONNECTION_CELL_NOT_CAMPED = 2144; // 0x860
+    field public static final int RRC_CONNECTION_CELL_RESELECTION = 2140; // 0x85c
+    field public static final int RRC_CONNECTION_CONFIG_FAILURE = 2141; // 0x85d
+    field public static final int RRC_CONNECTION_INVALID_REQUEST = 2168; // 0x878
+    field public static final int RRC_CONNECTION_LINK_FAILURE = 2143; // 0x85f
+    field public static final int RRC_CONNECTION_NORMAL_RELEASE = 2147; // 0x863
+    field public static final int RRC_CONNECTION_OUT_OF_SERVICE_DURING_CELL_REGISTER = 2150; // 0x866
+    field public static final int RRC_CONNECTION_RADIO_LINK_FAILURE = 2148; // 0x864
+    field public static final int RRC_CONNECTION_REESTABLISHMENT_FAILURE = 2149; // 0x865
+    field public static final int RRC_CONNECTION_REJECT_BY_NETWORK = 2146; // 0x862
+    field public static final int RRC_CONNECTION_RELEASED_SECURITY_NOT_ACTIVE = 2172; // 0x87c
+    field public static final int RRC_CONNECTION_RF_UNAVAILABLE = 2170; // 0x87a
+    field public static final int RRC_CONNECTION_SYSTEM_INFORMATION_BLOCK_READ_ERROR = 2152; // 0x868
+    field public static final int RRC_CONNECTION_SYSTEM_INTERVAL_FAILURE = 2145; // 0x861
+    field public static final int RRC_CONNECTION_TIMER_EXPIRED = 2142; // 0x85e
+    field public static final int RRC_CONNECTION_TRACKING_AREA_ID_CHANGED = 2169; // 0x879
+    field public static final int RRC_UPLINK_CONNECTION_RELEASE = 2134; // 0x856
+    field public static final int RRC_UPLINK_DATA_TRANSMISSION_FAILURE = 2132; // 0x854
+    field public static final int RRC_UPLINK_DELIVERY_FAILED_DUE_TO_HANDOVER = 2133; // 0x855
+    field public static final int RRC_UPLINK_ERROR_REQUEST_FROM_NAS = 2136; // 0x858
+    field public static final int RRC_UPLINK_RADIO_LINK_FAILURE = 2135; // 0x857
+    field public static final int RUIM_NOT_PRESENT = 2085; // 0x825
+    field public static final int SECURITY_MODE_REJECTED = 2186; // 0x88a
+    field public static final int SERVICE_NOT_ALLOWED_ON_PLMN = 2129; // 0x851
     field public static final int SERVICE_OPTION_NOT_SUBSCRIBED = 33; // 0x21
     field public static final int SERVICE_OPTION_NOT_SUPPORTED = 32; // 0x20
     field public static final int SERVICE_OPTION_OUT_OF_ORDER = 34; // 0x22
     field public static final int SIGNAL_LOST = -3; // 0xfffffffd
+    field public static final int SIM_CARD_CHANGED = 2043; // 0x7fb
+    field public static final int SYNCHRONIZATION_FAILURE = 2184; // 0x888
+    field public static final int TEST_LOOPBACK_REGULAR_DEACTIVATION = 2196; // 0x894
     field public static final int TETHERED_CALL_ACTIVE = -6; // 0xfffffffa
     field public static final int TFT_SEMANTIC_ERROR = 41; // 0x29
     field public static final int TFT_SYTAX_ERROR = 42; // 0x2a
+    field public static final int THERMAL_EMERGENCY = 2090; // 0x82a
+    field public static final int THERMAL_MITIGATION = 2062; // 0x80e
+    field public static final int TRAT_SWAP_FAILED = 2048; // 0x800
+    field public static final int UE_INITIATED_DETACH_OR_DISCONNECT = 128; // 0x80
+    field public static final int UE_IS_ENTERING_POWERSAVE_MODE = 2226; // 0x8b2
+    field public static final int UE_RAT_CHANGE = 2105; // 0x839
+    field public static final int UE_SECURITY_CAPABILITIES_MISMATCH = 2185; // 0x889
+    field public static final int UMTS_HANDOVER_TO_IWLAN = 2199; // 0x897
     field public static final int UMTS_REACTIVATION_REQ = 39; // 0x27
+    field public static final int UNACCEPTABLE_NON_EPS_AUTHENTICATION = 2187; // 0x88b
     field public static final int UNKNOWN = 65536; // 0x10000
     field public static final int UNKNOWN_INFO_ELEMENT = 99; // 0x63
     field public static final int UNKNOWN_PDP_ADDRESS_TYPE = 28; // 0x1c
     field public static final int UNKNOWN_PDP_CONTEXT = 43; // 0x2b
+    field public static final int UNPREFERRED_RAT = 2039; // 0x7f7
+    field public static final int UNSUPPORTED_1X_PREV = 2214; // 0x8a6
     field public static final int UNSUPPORTED_APN_IN_CURRENT_PLMN = 66; // 0x42
+    field public static final int UNSUPPORTED_QCI_VALUE = 59; // 0x3b
     field public static final int USER_AUTHENTICATION = 29; // 0x1d
+    field public static final int VSNCP_ADMINISTRATIVELY_PROHIBITED = 2245; // 0x8c5
+    field public static final int VSNCP_APN_UNATHORIZED = 2238; // 0x8be
+    field public static final int VSNCP_GEN_ERROR = 2237; // 0x8bd
+    field public static final int VSNCP_INSUFFICIENT_PARAMETERS = 2243; // 0x8c3
+    field public static final int VSNCP_NO_PDN_GATEWAY_ADDRESS = 2240; // 0x8c0
+    field public static final int VSNCP_PDN_EXISTS_FOR_THIS_APN = 2248; // 0x8c8
+    field public static final int VSNCP_PDN_GATEWAY_REJECT = 2242; // 0x8c2
+    field public static final int VSNCP_PDN_GATEWAY_UNREACHABLE = 2241; // 0x8c1
+    field public static final int VSNCP_PDN_ID_IN_USE = 2246; // 0x8c6
+    field public static final int VSNCP_PDN_LIMIT_EXCEEDED = 2239; // 0x8bf
+    field public static final int VSNCP_RECONNECT_NOT_ALLOWED = 2249; // 0x8c9
+    field public static final int VSNCP_RESOURCE_UNAVAILABLE = 2244; // 0x8c4
+    field public static final int VSNCP_SUBSCRIBER_LIMITATION = 2247; // 0x8c7
+    field public static final int VSNCP_TIMEOUT = 2236; // 0x8bc
   }
 
   public class DisconnectCause {
@@ -7192,10 +7629,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
@@ -7267,6 +7707,7 @@
     method @RequiresPermission(anyOf={android.Manifest.permission.READ_PRIVILEGED_PHONE_STATE, android.Manifest.permission.READ_PHONE_STATE}) public int getRadioPowerState();
     method public int getSimApplicationState();
     method public int getSimCardState();
+    method @Nullable @RequiresPermission(android.Manifest.permission.READ_PRIVILEGED_PHONE_STATE) public String getSimLocale();
     method @RequiresPermission(android.Manifest.permission.READ_PRIVILEGED_PHONE_STATE) public int getSupportedRadioAccessFamily();
     method @RequiresPermission(android.Manifest.permission.MODIFY_PHONE_STATE) public java.util.List<android.telephony.TelephonyHistogram> getTelephonyHistograms();
     method @RequiresPermission(android.Manifest.permission.READ_PRIVILEGED_PHONE_STATE) public android.telephony.UiccCardInfo[] getUiccCardsInfo();
@@ -7569,9 +8010,12 @@
     method @RequiresPermission(android.Manifest.permission.WRITE_EMBEDDED_SUBSCRIPTIONS) public void getDefaultDownloadableSubscriptionList(android.app.PendingIntent);
     method @RequiresPermission(android.Manifest.permission.WRITE_EMBEDDED_SUBSCRIPTIONS) public void getDownloadableSubscriptionMetadata(android.telephony.euicc.DownloadableSubscription, android.app.PendingIntent);
     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
@@ -7582,7 +8026,10 @@
     field public static final int EUICC_OTA_SUCCEEDED = 3; // 0x3
     field public static final String EXTRA_ACTIVATION_TYPE = "android.telephony.euicc.extra.ACTIVATION_TYPE";
     field public static final String EXTRA_EMBEDDED_SUBSCRIPTION_DOWNLOADABLE_SUBSCRIPTIONS = "android.telephony.euicc.extra.EMBEDDED_SUBSCRIPTION_DOWNLOADABLE_SUBSCRIPTIONS";
+    field public static final String EXTRA_ENABLE_SUBSCRIPTION = "android.telephony.euicc.extra.ENABLE_SUBSCRIPTION";
     field public static final String EXTRA_FORCE_PROVISION = "android.telephony.euicc.extra.FORCE_PROVISION";
+    field public static final String EXTRA_SUBSCRIPTION_ID = "android.telephony.euicc.extra.SUBSCRIPTION_ID";
+    field public static final String EXTRA_SUBSCRIPTION_NICKNAME = "android.telephony.euicc.extra.SUBSCRIPTION_NICKNAME";
   }
 
   @IntDef(prefix={"EUICC_OTA_"}, value={android.telephony.euicc.EuiccManager.EUICC_OTA_IN_PROGRESS, android.telephony.euicc.EuiccManager.EUICC_OTA_FAILED, android.telephony.euicc.EuiccManager.EUICC_OTA_SUCCEEDED, android.telephony.euicc.EuiccManager.EUICC_OTA_NOT_NEEDED, android.telephony.euicc.EuiccManager.EUICC_OTA_STATUS_UNAVAILABLE}) @java.lang.annotation.Retention(java.lang.annotation.RetentionPolicy.SOURCE) public static @interface EuiccManager.OtaStatus {
@@ -7798,6 +8245,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();
@@ -7815,7 +8272,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();
@@ -7824,8 +8281,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);
@@ -8260,13 +8717,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 {
@@ -8637,6 +9102,22 @@
 
 package android.util {
 
+  public class DocumentsStatsLog {
+    method public static void logActivityLaunch(int, boolean, int, int);
+    method public static void logFileOperation(int, int);
+    method public static void logFileOperationCanceled(int);
+    method public static void logFileOperationCopyMoveMode(int, int);
+    method public static void logFileOperationFailure(int, int);
+    method public static void logFilePick(int, long, int, boolean, int, int, int);
+    method public static void logInvalidScopedAccessRequest(int);
+    method public static void logPickerLaunchedFrom(String);
+    method public static void logRootVisited(int, int);
+    method public static void logSearchMode(int);
+    method public static void logSearchType(int);
+    method public static void logStartupMs(int);
+    method public static void logUserAction(int);
+  }
+
   public class EventLog {
     method public static void readEventsOnWrapping(int[], long, java.util.Collection<android.util.EventLog.Event>) throws java.io.IOException;
   }
@@ -8677,9 +9158,22 @@
 
 }
 
+package android.view.autofill {
+
+  public final class AutofillManager {
+    method @NonNull public java.util.Set<android.content.ComponentName> getAugmentedAutofillDisabledActivities();
+    method @NonNull public java.util.Set<java.lang.String> getAugmentedAutofillDisabledPackages();
+    method public void setActivityAugmentedAutofillEnabled(@NonNull android.content.ComponentName, boolean);
+    method public void setAugmentedAutofillWhitelist(@Nullable java.util.List<java.lang.String>, @Nullable java.util.List<android.content.ComponentName>);
+    method public void setPackageAugmentedAutofillEnabled(@NonNull String, boolean);
+  }
+
+}
+
 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 26fe204..0f2ba12 100644
--- a/api/test-current.txt
+++ b/api/test-current.txt
@@ -11,9 +11,14 @@
     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 +337,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 {
@@ -352,6 +358,10 @@
     method public boolean isReservedSupported(@NonNull java.util.UUID);
   }
 
+  public final class UsageStatsManager {
+    method public void forceUsageSourceSettingRead();
+  }
+
 }
 
 package android.bluetooth {
@@ -772,6 +782,11 @@
     field public static final java.util.UUID EFFECT_TYPE_NULL;
   }
 
+  public static class AudioEffect.Descriptor {
+    ctor public AudioEffect.Descriptor(android.os.Parcel);
+    method public void writeToParcel(android.os.Parcel);
+  }
+
   public static interface AudioEffect.OnParameterChangeListener {
     method public void onParameterChange(android.media.audiofx.AudioEffect, int, byte[], byte[]);
   }
@@ -781,6 +796,8 @@
 package android.net {
 
   public class CaptivePortal implements android.os.Parcelable {
+    ctor public CaptivePortal(android.os.IBinder);
+    method public void useNetwork();
     field public static final int APP_RETURN_DISMISSED = 0; // 0x0
     field public static final int APP_RETURN_UNWANTED = 1; // 0x1
     field public static final int APP_RETURN_WANTED_AS_IS = 2; // 0x2
@@ -791,11 +808,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();
@@ -803,7 +825,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();
@@ -815,6 +840,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);
@@ -832,6 +859,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
@@ -918,6 +946,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);
@@ -966,6 +995,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
@@ -1262,15 +1305,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;
   }
 
@@ -1677,10 +1716,7 @@
     method public int getTaskId();
   }
 
-  public final class FillResponse 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.service.autofill.augmented.FillResponse> CREATOR;
+  public final class FillResponse {
   }
 
   public static final class FillResponse.Builder {
@@ -1694,7 +1730,6 @@
     ctor public FillWindow();
     method public void destroy();
     method public boolean update(@NonNull android.service.autofill.augmented.PresentationParams.Area, @NonNull android.view.View, long);
-    field public static final long FLAG_METADATA_ADDRESS = 1L; // 0x1L
   }
 
   public abstract class PresentationParams {
@@ -1718,84 +1753,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);
@@ -2266,6 +2231,10 @@
     method public static int getLongPressTooltipHideTimeout();
   }
 
+  public class ViewDebug {
+    method @Nullable public static AutoCloseable startRenderingCommandsCapture(android.view.View, java.util.concurrent.Executor, java.util.function.Function<android.graphics.Picture,java.lang.Boolean>);
+  }
+
   public interface WindowManager extends android.view.ViewManager {
     method public default void setShouldShowIme(int, boolean);
     method public default void setShouldShowSystemDecors(int, boolean);
@@ -2326,6 +2295,12 @@
   }
 
   public final class AutofillManager {
+    method @NonNull public java.util.Set<android.content.ComponentName> getAugmentedAutofillDisabledActivities();
+    method @NonNull public java.util.Set<java.lang.String> getAugmentedAutofillDisabledPackages();
+    method public void setActivityAugmentedAutofillEnabled(@NonNull android.content.ComponentName, boolean);
+    method public void setAugmentedAutofillWhitelist(@Nullable java.util.List<java.lang.String>, @Nullable java.util.List<android.content.ComponentName>);
+    method public void setPackageAugmentedAutofillEnabled(@NonNull String, boolean);
+    field public static final int FLAG_SMART_SUGGESTION_SYSTEM = 1; // 0x1
     field public static final int MAX_TEMP_AUGMENTED_SERVICE_DURATION_MS = 120000; // 0x1d4c0
   }
 
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/src/StatsService.cpp b/cmds/statsd/src/StatsService.cpp
index 820da55..9c320d3 100644
--- a/cmds/statsd/src/StatsService.cpp
+++ b/cmds/statsd/src/StatsService.cpp
@@ -1097,6 +1097,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..fe0504f 100644
--- a/cmds/statsd/src/StatsService.h
+++ b/cmds/statsd/src/StatsService.h
@@ -205,6 +205,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 79ef2ca..1a0a983 100644
--- a/cmds/statsd/src/atoms.proto
+++ b/cmds/statsd/src/atoms.proto
@@ -27,12 +27,14 @@
 import "frameworks/base/core/proto/android/bluetooth/enums.proto";
 import "frameworks/base/core/proto/android/bluetooth/hci/enums.proto";
 import "frameworks/base/core/proto/android/bluetooth/hfp/enums.proto";
+import "frameworks/base/core/proto/android/debug/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";
@@ -202,10 +204,15 @@
         NfcHceTransactionOccurred nfc_hce_transaction_occurred = 139;
         SeStateChanged se_state_changed = 140;
         SeOmapiReported se_omapi_reported = 141;
+        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;
     }
 
     // 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;
@@ -252,6 +259,9 @@
         ProcessMemoryHighWaterMark process_memory_high_water_mark = 10042;
         BatteryLevel battery_level = 10043;
         BuildInformation build_information = 10044;
+        BatteryCycleCount battery_cycle_count = 10045;
+        DebugElapsedClock debug_elapsed_clock = 10046;
+        DebugFailingElapsedClock debug_failing_elapsed_clock = 10047;
     }
 
     // DO NOT USE field numbers above 100,000 in AOSP.
@@ -2484,24 +2494,32 @@
 
 /*
  * 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. */
@@ -2522,6 +2540,19 @@
     optional int32 time_to_inactive_secs = 5;
 };
 
+/**
+ * Logs total effective full charge and discharge cycles on a battery.
+ * Here are some examples of one effective cycle:
+ *   1) the battery charges from 0% to 100% and drains back to 0%,
+ *   2) charging from 50% to 100% and draining back to 50% twice.
+ * Pulled from:
+ *   frameworks/base/cmds/statsd/src/external/ResourceHealthManagerPuller.cpp
+ */
+message BatteryCycleCount {
+    /* Number of total charge and discharge cycles on the system battery. */
+    optional int32 cycle_count = 1;
+}
+
 /*
  * Logs when a connection becomes available and lost.
  * Logged in StatsCompanionService.java
@@ -3000,8 +3031,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;
@@ -4450,5 +4481,117 @@
   // SIMx or eSEx.
   optional string terminal = 2;
 
-  optional string packageName = 3;
+  optional string package_name = 3;
+}
+
+/**
+  * Logs the dispatch latency of a broadcast during processing of BOOT_COMPLETED.
+  * The dispatch latency is the dispatchClockTime - enqueueClockTime.
+  * Logged from:
+  *   frameworks/base/services/core/java/com/android/server/am/BroadcastQueue.java
+  */
+message BroadcastDispatchLatencyReported {
+    optional int64 dispatch_latency_millis = 1;
+}
+
+/**
+   * Logs AttentionManagerService attention check result.
+   *
+   * Logged from:
+   *   frameworks/base/services/core/java/com/android/server/attention/AttentionManagerService.java
+   */
+message AttentionManagerServiceResultReported {
+    // See core/java/android/service/attention/AttentionService.java
+    enum AttentionCheckResult {
+        UNKNOWN = 20;
+        ATTENTION_SUCCESS_ABSENT = 0;
+        ATTENTION_SUCCESS_PRESENT = 1;
+        ATTENTION_FAILURE_PREEMPTED = 2;
+        ATTENTION_FAILURE_TIMED_OUT = 3;
+        ATTENTION_FAILURE_UNKNOWN = 4;
+    }
+    optional AttentionCheckResult attention_check_result = 1 [default = UNKNOWN];
+}
+
+/**
+ * Logs when an adb connection changes state.
+ *
+ * Logged from:
+ *     frameworks/base/services/core/java/com/android/server/adb/AdbDebuggingManager.java
+ */
+message AdbConnectionChanged {
+    // The last time this system connected via adb, or 0 if the 'always allow' option was not
+    // previously selected for this system.
+    optional int64 last_connection_time_millis = 1;
+
+    // The time in ms within which a subsequent connection from an 'always allow' system is allowed
+    // to reconnect via adb without user interaction.
+    optional int64 auth_window_millis = 2;
+
+    // The state of the adb connection from frameworks/base/core/proto/android/debug/enums.proto.
+    optional android.debug.AdbConnectionStateEnum state = 3;
+
+    // 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;
 }
diff --git a/cmds/statsd/src/external/ResourceHealthManagerPuller.cpp b/cmds/statsd/src/external/ResourceHealthManagerPuller.cpp
index b878652..75b63f4 100644
--- a/cmds/statsd/src/external/ResourceHealthManagerPuller.cpp
+++ b/cmds/statsd/src/external/ResourceHealthManagerPuller.cpp
@@ -24,16 +24,16 @@
 
 #include "ResourceHealthManagerPuller.h"
 #include "logd/LogEvent.h"
-#include "statslog.h"
 #include "stats_log_util.h"
+#include "statslog.h"
 
 using android::hardware::hidl_vec;
+using android::hardware::Return;
+using android::hardware::Void;
 using android::hardware::health::V2_0::get_health_service;
 using android::hardware::health::V2_0::HealthInfo;
 using android::hardware::health::V2_0::IHealth;
 using android::hardware::health::V2_0::Result;
-using android::hardware::Return;
-using android::hardware::Void;
 
 using std::make_shared;
 using std::shared_ptr;
@@ -75,35 +75,41 @@
         }
         if (mTagId == android::util::REMAINING_BATTERY_CAPACITY) {
             auto ptr = make_shared<LogEvent>(android::util::REMAINING_BATTERY_CAPACITY,
-                wallClockTimestampNs, elapsedTimestampNs);
+                                             wallClockTimestampNs, elapsedTimestampNs);
             ptr->write(v.legacy.batteryChargeCounter);
             ptr->init();
             data->push_back(ptr);
         } else if (mTagId == android::util::FULL_BATTERY_CAPACITY) {
             auto ptr = make_shared<LogEvent>(android::util::FULL_BATTERY_CAPACITY,
-                wallClockTimestampNs, elapsedTimestampNs);
+                                             wallClockTimestampNs, elapsedTimestampNs);
             ptr->write(v.legacy.batteryFullCharge);
             ptr->init();
             data->push_back(ptr);
         } else if (mTagId == android::util::BATTERY_VOLTAGE) {
-            auto ptr = make_shared<LogEvent>(android::util::BATTERY_VOLTAGE,
-                wallClockTimestampNs, elapsedTimestampNs);
+            auto ptr = make_shared<LogEvent>(android::util::BATTERY_VOLTAGE, wallClockTimestampNs,
+                                             elapsedTimestampNs);
             ptr->write(v.legacy.batteryVoltage);
             ptr->init();
             data->push_back(ptr);
         } else if (mTagId == android::util::BATTERY_LEVEL) {
-                     auto ptr = make_shared<LogEvent>(android::util::BATTERY_LEVEL,
-                         wallClockTimestampNs, elapsedTimestampNs);
-                     ptr->write(v.legacy.batteryLevel);
-                     ptr->init();
-                     data->push_back(ptr);
+            auto ptr = make_shared<LogEvent>(android::util::BATTERY_LEVEL, wallClockTimestampNs,
+                                             elapsedTimestampNs);
+            ptr->write(v.legacy.batteryLevel);
+            ptr->init();
+            data->push_back(ptr);
+        } else if (mTagId == android::util::BATTERY_CYCLE_COUNT) {
+            auto ptr = make_shared<LogEvent>(android::util::BATTERY_CYCLE_COUNT,
+                                             wallClockTimestampNs, elapsedTimestampNs);
+            ptr->write(v.legacy.batteryCycleCount);
+            ptr->init();
+            data->push_back(ptr);
         } else {
             ALOGE("Unsupported tag in ResourceHealthManagerPuller: %d", mTagId);
         }
     });
     if (!result_success || !ret.isOk()) {
         ALOGE("getHealthHal() failed: health HAL service not available. Description: %s",
-                ret.description().c_str());
+              ret.description().c_str());
         if (!ret.isOk() && ret.isDeadObject()) {
             gHealthHal = nullptr;
         }
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 4a716cf..4585a09 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"
@@ -131,9 +130,12 @@
         // battery_voltage
         {android::util::BATTERY_VOLTAGE,
          {.puller = new ResourceHealthManagerPuller(android::util::BATTERY_VOLTAGE)}},
-        // battery_voltage
+        // battery_level
         {android::util::BATTERY_LEVEL,
          {.puller = new ResourceHealthManagerPuller(android::util::BATTERY_LEVEL)}},
+        // battery_cycle_count
+        {android::util::BATTERY_CYCLE_COUNT,
+         {.puller = new ResourceHealthManagerPuller(android::util::BATTERY_CYCLE_COUNT)}},
         // process_memory_state
         {android::util::PROCESS_MEMORY_STATE,
          {.additiveFields = {4, 5, 6, 7, 8, 9},
@@ -147,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},
@@ -206,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/cmds/telecom/src/com/android/commands/telecom/Telecom.java b/cmds/telecom/src/com/android/commands/telecom/Telecom.java
index 4174ad7..1b7fbfe 100644
--- a/cmds/telecom/src/com/android/commands/telecom/Telecom.java
+++ b/cmds/telecom/src/com/android/commands/telecom/Telecom.java
@@ -23,8 +23,10 @@
 import android.os.RemoteException;
 import android.os.ServiceManager;
 import android.os.UserHandle;
+import android.telecom.Log;
 import android.telecom.PhoneAccount;
 import android.telecom.PhoneAccountHandle;
+import android.text.TextUtils;
 
 import com.android.internal.os.BaseCommand;
 import com.android.internal.telecom.ITelecomService;
@@ -45,6 +47,8 @@
     private static final String COMMAND_SET_PHONE_ACCOUNT_ENABLED = "set-phone-account-enabled";
     private static final String COMMAND_SET_PHONE_ACCOUNT_DISABLED = "set-phone-account-disabled";
     private static final String COMMAND_REGISTER_PHONE_ACCOUNT = "register-phone-account";
+    private static final String COMMAND_SET_USER_SELECTED_OUTGOING_PHONE_ACCOUNT =
+            "set-user-selected-outgoing-phone-account";
     private static final String COMMAND_REGISTER_SIM_PHONE_ACCOUNT = "register-sim-phone-account";
     private static final String COMMAND_SET_TEST_CALL_REDIRECTION_APP = "set-test-call-redirection-app";
     private static final String COMMAND_SET_TEST_CALL_SCREENING_APP = "set-test-call-screening-app";
@@ -70,6 +74,8 @@
                 + "usage: telecom set-phone-account-enabled <COMPONENT> <ID> <USER_SN>\n"
                 + "usage: telecom set-phone-account-disabled <COMPONENT> <ID> <USER_SN>\n"
                 + "usage: telecom register-phone-account <COMPONENT> <ID> <USER_SN> <LABEL>\n"
+                + "usage: telecom set-user-selected-outgoing-phone-account <COMPONENT> <ID> "
+                + "<USER_SN>\n"
                 + "usage: telecom set-test-call-redirection-app <PACKAGE>\n"
                 + "usage: telecom set-test-call-screening-app <PACKAGE>\n"
                 + "usage: telecom set-test-auto-mode-app <PACKAGE>\n"
@@ -104,16 +110,18 @@
         mTelecomService = ITelecomService.Stub.asInterface(
                 ServiceManager.getService(Context.TELECOM_SERVICE));
         if (mTelecomService == null) {
+            Log.w(this, "onRun: Can't access telecom manager.");
             showError("Error: Could not access the Telecom Manager. Is the system running?");
             return;
         }
         mUserManager = IUserManager.Stub
                 .asInterface(ServiceManager.getService(Context.USER_SERVICE));
         if (mUserManager == null) {
+            Log.w(this, "onRun: Can't access user manager.");
             showError("Error: Could not access the User Manager. Is the system running?");
             return;
         }
-
+        Log.i(this, "onRun: parsing command.");
         String command = nextArgRequired();
         switch (command) {
             case COMMAND_SET_PHONE_ACCOUNT_ENABLED:
@@ -143,6 +151,9 @@
             case COMMAND_REGISTER_SIM_PHONE_ACCOUNT:
                 runRegisterSimPhoneAccount();
                 break;
+            case COMMAND_SET_USER_SELECTED_OUTGOING_PHONE_ACCOUNT:
+                runSetUserSelectedOutgoingPhoneAccount();
+                break;
             case COMMAND_UNREGISTER_PHONE_ACCOUNT:
                 runUnregisterPhoneAccount();
                 break;
@@ -159,6 +170,7 @@
                 runWaitOnHandler();
                 break;
             default:
+                Log.w(this, "onRun: unknown command: %s", command);
                 throw new IllegalArgumentException ("unknown command '" + command + "'");
         }
     }
@@ -227,6 +239,13 @@
         mTelecomService.setTestPhoneAcctSuggestionComponent(componentName);
     }
 
+    private void runSetUserSelectedOutgoingPhoneAccount() throws RemoteException {
+        Log.i(this, "runSetUserSelectedOutgoingPhoneAccount");
+        final PhoneAccountHandle handle = getPhoneAccountHandleFromArgs();
+        mTelecomService.setUserSelectedOutgoingPhoneAccount(handle);
+        System.out.println("Success - " + handle + " set as default outgoing account.");
+    }
+
     private void runUnregisterPhoneAccount() throws RemoteException {
         final PhoneAccountHandle handle = getPhoneAccountHandleFromArgs();
         mTelecomService.unregisterPhoneAccount(handle);
@@ -256,7 +275,10 @@
 
     }
 
-    private PhoneAccountHandle getPhoneAccountHandleFromArgs() throws RemoteException{
+    private PhoneAccountHandle getPhoneAccountHandleFromArgs() throws RemoteException {
+        if (TextUtils.isEmpty(mArgs.peekNextArg())) {
+            return null;
+        }
         final ComponentName component = parseComponentName(nextArgRequired());
         final String accountId = nextArgRequired();
         final String userSnInStr = nextArgRequired();
@@ -265,6 +287,7 @@
             final int userSn = Integer.parseInt(userSnInStr);
             userHandle = UserHandle.of(mUserManager.getUserHandle(userSn));
         } catch (NumberFormatException ex) {
+            Log.w(this, "getPhoneAccountHandleFromArgs - invalid user %s", userSnInStr);
             throw new IllegalArgumentException ("Invalid user serial number " + userSnInStr);
         }
         return new PhoneAccountHandle(component, accountId, userHandle);
@@ -277,4 +300,4 @@
         }
         return cn;
     }
-}
\ No newline at end of file
+}
diff --git a/config/boot-image-profile.txt b/config/boot-image-profile.txt
index 3ec0db4..c2e441b 100644
--- a/config/boot-image-profile.txt
+++ b/config/boot-image-profile.txt
@@ -2768,6 +2768,11 @@
 HPLandroid/hardware/location/GeofenceHardwareService$1;->registerForMonitorStateChangeCallback(ILandroid/hardware/location/IGeofenceHardwareMonitorCallback;)Z
 HPLandroid/hardware/location/GeofenceHardwareService$1;->removeGeofence(II)Z
 HPLandroid/hardware/location/GeofenceHardwareService;->checkPermission(III)V
+HPLandroid/hardware/location/IActivityRecognitionHardwareClient$Stub$Proxy;->onAvailabilityChanged(ZLandroid/hardware/location/IActivityRecognitionHardware;)V
+HPLandroid/hardware/location/IActivityRecognitionHardwareClient$Stub;-><init>()V
+HPLandroid/hardware/location/IActivityRecognitionHardwareClient$Stub;->asInterface(Landroid/os/IBinder;)Landroid/hardware/location/IActivityRecognitionHardwareClient;
+HPLandroid/hardware/location/IActivityRecognitionHardwareClient$Stub;->onTransact(ILandroid/os/Parcel;Landroid/os/Parcel;I)Z
+HPLandroid/hardware/location/IActivityRecognitionHardwareClient;->onAvailabilityChanged(ZLandroid/hardware/location/IActivityRecognitionHardware;)V
 HPLandroid/hardware/location/IContextHubCallback$Stub;->asBinder()Landroid/os/IBinder;
 HPLandroid/hardware/location/IContextHubCallback$Stub;->onTransact(ILandroid/os/Parcel;Landroid/os/Parcel;I)Z
 HPLandroid/hardware/location/IContextHubService$Stub$Proxy;->findNanoAppOnHub(ILandroid/hardware/location/NanoAppFilter;)[I
@@ -21777,6 +21782,7 @@
 HSPLandroid/hardware/input/TouchCalibration$1;-><init>()V
 HSPLandroid/hardware/input/TouchCalibration;-><init>()V
 HSPLandroid/hardware/input/TouchCalibration;->getAffineTransform()[F
+HSPLandroid/hardware/location/ActivityRecognitionHardware;->isSupported()Z
 HSPLandroid/hardware/location/ContextHubInfo$1;-><init>()V
 HSPLandroid/hardware/location/ContextHubInfo;-><init>(Landroid/hardware/contexthub/V1_0/ContextHub;)V
 HSPLandroid/hardware/location/ContextHubMessage$1;-><init>()V
@@ -21796,6 +21802,13 @@
 HSPLandroid/hardware/location/GeofenceHardwareService;-><init>()V
 HSPLandroid/hardware/location/GeofenceHardwareService;->onBind(Landroid/content/Intent;)Landroid/os/IBinder;
 HSPLandroid/hardware/location/GeofenceHardwareService;->onCreate()V
+HSPLandroid/hardware/location/IActivityRecognitionHardware;->disableActivityEvent(Ljava/lang/String;I)Z
+HSPLandroid/hardware/location/IActivityRecognitionHardware;->enableActivityEvent(Ljava/lang/String;IJ)Z
+HSPLandroid/hardware/location/IActivityRecognitionHardware;->flush()Z
+HSPLandroid/hardware/location/IActivityRecognitionHardware;->getSupportedActivities()[Ljava/lang/String;
+HSPLandroid/hardware/location/IActivityRecognitionHardware;->isActivitySupported(Ljava/lang/String;)Z
+HSPLandroid/hardware/location/IActivityRecognitionHardware;->registerSink(Landroid/hardware/location/IActivityRecognitionHardwareSink;)Z
+HSPLandroid/hardware/location/IActivityRecognitionHardware;->unregisterSink(Landroid/hardware/location/IActivityRecognitionHardwareSink;)Z
 HSPLandroid/hardware/location/IContextHubCallback$Stub$Proxy;->asBinder()Landroid/os/IBinder;
 HSPLandroid/hardware/location/IContextHubCallback$Stub$Proxy;->onMessageReceipt(IILandroid/hardware/location/ContextHubMessage;)V
 HSPLandroid/hardware/location/IContextHubCallback;->onMessageReceipt(IILandroid/hardware/location/ContextHubMessage;)V
@@ -55637,6 +55650,7 @@
 Landroid/hardware/input/KeyboardLayout$1;
 Landroid/hardware/input/TouchCalibration$1;
 Landroid/hardware/input/TouchCalibration;
+Landroid/hardware/location/ActivityRecognitionHardware;
 Landroid/hardware/location/ContextHubInfo$1;
 Landroid/hardware/location/ContextHubInfo;
 Landroid/hardware/location/ContextHubManager;
@@ -55652,6 +55666,11 @@
 Landroid/hardware/location/GeofenceHardwareRequestParcelable$1;
 Landroid/hardware/location/GeofenceHardwareService$1;
 Landroid/hardware/location/GeofenceHardwareService;
+Landroid/hardware/location/IActivityRecognitionHardware$Stub;
+Landroid/hardware/location/IActivityRecognitionHardware;
+Landroid/hardware/location/IActivityRecognitionHardwareClient$Stub$Proxy;
+Landroid/hardware/location/IActivityRecognitionHardwareClient$Stub;
+Landroid/hardware/location/IActivityRecognitionHardwareClient;
 Landroid/hardware/location/IContextHubCallback$Stub$Proxy;
 Landroid/hardware/location/IContextHubCallback;
 Landroid/hardware/location/IContextHubClient$Stub;
diff --git a/config/hiddenapi-greylist.txt b/config/hiddenapi-greylist.txt
index 3a4d904..ee2fe8f 100644
--- a/config/hiddenapi-greylist.txt
+++ b/config/hiddenapi-greylist.txt
@@ -462,6 +462,8 @@
 Landroid/hardware/input/IInputManager$Stub;->asInterface(Landroid/os/IBinder;)Landroid/hardware/input/IInputManager;
 Landroid/hardware/input/IInputManager$Stub;->TRANSACTION_injectInputEvent:I
 Landroid/hardware/input/IInputManager;->injectInputEvent(Landroid/view/InputEvent;I)Z
+Landroid/hardware/location/IActivityRecognitionHardwareClient$Stub;-><init>()V
+Landroid/hardware/location/IActivityRecognitionHardwareClient;->onAvailabilityChanged(ZLandroid/hardware/location/IActivityRecognitionHardware;)V
 Landroid/hardware/location/IContextHubService$Stub;->asInterface(Landroid/os/IBinder;)Landroid/hardware/location/IContextHubService;
 Landroid/hardware/usb/IUsbManager$Stub$Proxy;-><init>(Landroid/os/IBinder;)V
 Landroid/hardware/usb/IUsbManager$Stub;->asInterface(Landroid/os/IBinder;)Landroid/hardware/usb/IUsbManager;
@@ -3516,7 +3518,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/config/preloaded-classes b/config/preloaded-classes
index cd798ad..c8a2a9c 100644
--- a/config/preloaded-classes
+++ b/config/preloaded-classes
@@ -1405,7 +1405,10 @@
 android.hardware.input.InputManager$InputDeviceListener
 android.hardware.input.InputManager$InputDeviceListenerDelegate
 android.hardware.input.InputManager$InputDevicesChangedListener
+android.hardware.location.ActivityRecognitionHardware
 android.hardware.location.ContextHubManager
+android.hardware.location.IActivityRecognitionHardware
+android.hardware.location.IActivityRecognitionHardware$Stub
 android.hardware.radio.RadioManager
 android.hardware.soundtrigger.SoundTrigger
 android.hardware.soundtrigger.SoundTrigger$ConfidenceLevel
diff --git a/core/java/android/accessibilityservice/AccessibilityService.java b/core/java/android/accessibilityservice/AccessibilityService.java
index 35098a0..cebe6e12 100644
--- a/core/java/android/accessibilityservice/AccessibilityService.java
+++ b/core/java/android/accessibilityservice/AccessibilityService.java
@@ -37,6 +37,7 @@
 import android.util.Log;
 import android.util.Slog;
 import android.util.SparseArray;
+import android.view.Display;
 import android.view.KeyEvent;
 import android.view.WindowManager;
 import android.view.WindowManagerImpl;
@@ -382,7 +383,8 @@
         void init(int connectionId, IBinder windowToken);
         boolean onGesture(int gestureId);
         boolean onKeyEvent(KeyEvent event);
-        void onMagnificationChanged(@NonNull Region region,
+        /** Magnification changed callbacks for different displays */
+        void onMagnificationChanged(int displayId, @NonNull Region region,
                 float scale, float centerX, float centerY);
         void onSoftKeyboardShowModeChanged(int showMode);
         void onPerformGestureResult(int sequence, boolean completedSuccessfully);
@@ -452,7 +454,9 @@
 
     private WindowManager mWindowManager;
 
-    private MagnificationController mMagnificationController;
+    /** List of magnification controllers, mapping from displayId -> MagnificationController. */
+    private final SparseArray<MagnificationController> mMagnificationControllers =
+            new SparseArray<>(0);
     private SoftKeyboardController mSoftKeyboardController;
     private AccessibilityButtonController mAccessibilityButtonController;
 
@@ -483,8 +487,10 @@
      * client code.
      */
     private void dispatchServiceConnected() {
-        if (mMagnificationController != null) {
-            mMagnificationController.onServiceConnected();
+        synchronized (mLock) {
+            for (int i = 0; i < mMagnificationControllers.size(); i++) {
+                mMagnificationControllers.valueAt(i).onServiceConnectedLocked();
+            }
         }
         if (mSoftKeyboardController != null) {
             mSoftKeyboardController.onServiceConnected();
@@ -652,11 +658,34 @@
      */
     @NonNull
     public final MagnificationController getMagnificationController() {
+        return getMagnificationController(Display.DEFAULT_DISPLAY);
+    }
+
+    /**
+     * Returns the magnification controller of specified logical display, which may be used to
+     * query and modify the state of display magnification.
+     * <p>
+     * <strong>Note:</strong> In order to control magnification, your service
+     * must declare the capability by setting the
+     * {@link android.R.styleable#AccessibilityService_canControlMagnification}
+     * property in its meta-data. For more information, see
+     * {@link #SERVICE_META_DATA}.
+     *
+     * @param displayId The logic display id, use {@link Display#DEFAULT_DISPLAY} for
+     *                  default display.
+     * @return the magnification controller
+     *
+     * @hide
+     */
+    @NonNull
+    public final MagnificationController getMagnificationController(int displayId) {
         synchronized (mLock) {
-            if (mMagnificationController == null) {
-                mMagnificationController = new MagnificationController(this, mLock);
+            MagnificationController controller = mMagnificationControllers.get(displayId);
+            if (controller == null) {
+                controller = new MagnificationController(this, mLock, displayId);
+                mMagnificationControllers.put(displayId, controller);
             }
-            return mMagnificationController;
+            return controller;
         }
     }
 
@@ -765,11 +794,14 @@
         }
     }
 
-    private void onMagnificationChanged(@NonNull Region region, float scale,
+    private void onMagnificationChanged(int displayId, @NonNull Region region, float scale,
             float centerX, float centerY) {
-        if (mMagnificationController != null) {
-            mMagnificationController.dispatchMagnificationChanged(
-                    region, scale, centerX, centerY);
+        MagnificationController controller;
+        synchronized (mLock) {
+            controller = mMagnificationControllers.get(displayId);
+        }
+        if (controller != null) {
+            controller.dispatchMagnificationChanged(region, scale, centerX, centerY);
         }
     }
 
@@ -794,6 +826,7 @@
      */
     public static final class MagnificationController {
         private final AccessibilityService mService;
+        private final int mDisplayId;
 
         /**
          * Map of listeners to their handlers. Lazily created when adding the
@@ -802,19 +835,19 @@
         private ArrayMap<OnMagnificationChangedListener, Handler> mListeners;
         private final Object mLock;
 
-        MagnificationController(@NonNull AccessibilityService service, @NonNull Object lock) {
+        MagnificationController(@NonNull AccessibilityService service, @NonNull Object lock,
+                int displayId) {
             mService = service;
             mLock = lock;
+            mDisplayId = displayId;
         }
 
         /**
          * Called when the service is connected.
          */
-        void onServiceConnected() {
-            synchronized (mLock) {
-                if (mListeners != null && !mListeners.isEmpty()) {
-                    setMagnificationCallbackEnabled(true);
-                }
+        void onServiceConnectedLocked() {
+            if (mListeners != null && !mListeners.isEmpty()) {
+                setMagnificationCallbackEnabled(true);
             }
         }
 
@@ -891,7 +924,7 @@
                             mService.mConnectionId);
             if (connection != null) {
                 try {
-                    connection.setMagnificationCallbackEnabled(enabled);
+                    connection.setMagnificationCallbackEnabled(mDisplayId, enabled);
                 } catch (RemoteException re) {
                     throw new RuntimeException(re);
                 }
@@ -952,7 +985,7 @@
                             mService.mConnectionId);
             if (connection != null) {
                 try {
-                    return connection.getMagnificationScale();
+                    return connection.getMagnificationScale(mDisplayId);
                 } catch (RemoteException re) {
                     Log.w(LOG_TAG, "Failed to obtain scale", re);
                     re.rethrowFromSystemServer();
@@ -981,7 +1014,7 @@
                             mService.mConnectionId);
             if (connection != null) {
                 try {
-                    return connection.getMagnificationCenterX();
+                    return connection.getMagnificationCenterX(mDisplayId);
                 } catch (RemoteException re) {
                     Log.w(LOG_TAG, "Failed to obtain center X", re);
                     re.rethrowFromSystemServer();
@@ -1010,7 +1043,7 @@
                             mService.mConnectionId);
             if (connection != null) {
                 try {
-                    return connection.getMagnificationCenterY();
+                    return connection.getMagnificationCenterY(mDisplayId);
                 } catch (RemoteException re) {
                     Log.w(LOG_TAG, "Failed to obtain center Y", re);
                     re.rethrowFromSystemServer();
@@ -1044,7 +1077,7 @@
                             mService.mConnectionId);
             if (connection != null) {
                 try {
-                    return connection.getMagnificationRegion();
+                    return connection.getMagnificationRegion(mDisplayId);
                 } catch (RemoteException re) {
                     Log.w(LOG_TAG, "Failed to obtain magnified region", re);
                     re.rethrowFromSystemServer();
@@ -1073,7 +1106,7 @@
                             mService.mConnectionId);
             if (connection != null) {
                 try {
-                    return connection.resetMagnification(animate);
+                    return connection.resetMagnification(mDisplayId, animate);
                 } catch (RemoteException re) {
                     Log.w(LOG_TAG, "Failed to reset", re);
                     re.rethrowFromSystemServer();
@@ -1101,7 +1134,7 @@
                             mService.mConnectionId);
             if (connection != null) {
                 try {
-                    return connection.setMagnificationScaleAndCenter(
+                    return connection.setMagnificationScaleAndCenter(mDisplayId,
                             scale, Float.NaN, Float.NaN, animate);
                 } catch (RemoteException re) {
                     Log.w(LOG_TAG, "Failed to set scale", re);
@@ -1133,7 +1166,7 @@
                             mService.mConnectionId);
             if (connection != null) {
                 try {
-                    return connection.setMagnificationScaleAndCenter(
+                    return connection.setMagnificationScaleAndCenter(mDisplayId,
                             Float.NaN, centerX, centerY, animate);
                 } catch (RemoteException re) {
                     Log.w(LOG_TAG, "Failed to set center", re);
@@ -1624,9 +1657,10 @@
             }
 
             @Override
-            public void onMagnificationChanged(@NonNull Region region,
+            public void onMagnificationChanged(int displayId, @NonNull Region region,
                     float scale, float centerX, float centerY) {
-                AccessibilityService.this.onMagnificationChanged(region, scale, centerX, centerY);
+                AccessibilityService.this.onMagnificationChanged(displayId, region, scale,
+                        centerX, centerY);
             }
 
             @Override
@@ -1729,13 +1763,15 @@
             mCaller.sendMessage(message);
         }
 
-        public void onMagnificationChanged(@NonNull Region region,
+        /** Magnification changed callbacks for different displays */
+        public void onMagnificationChanged(int displayId, @NonNull Region region,
                 float scale, float centerX, float centerY) {
             final SomeArgs args = SomeArgs.obtain();
             args.arg1 = region;
             args.arg2 = scale;
             args.arg3 = centerX;
             args.arg4 = centerY;
+            args.argi1 = displayId;
 
             final Message message = mCaller.obtainMessageO(DO_ON_MAGNIFICATION_CHANGED, args);
             mCaller.sendMessage(message);
@@ -1865,7 +1901,10 @@
                         final float scale = (float) args.arg2;
                         final float centerX = (float) args.arg3;
                         final float centerY = (float) args.arg4;
-                        mCallback.onMagnificationChanged(region, scale, centerX, centerY);
+                        final int displayId = args.argi1;
+                        args.recycle();
+                        mCallback.onMagnificationChanged(displayId, region, scale,
+                                centerX, centerY);
                     }
                 } return;
 
diff --git a/core/java/android/accessibilityservice/IAccessibilityServiceClient.aidl b/core/java/android/accessibilityservice/IAccessibilityServiceClient.aidl
index 4e96b8f..1dae4fc 100644
--- a/core/java/android/accessibilityservice/IAccessibilityServiceClient.aidl
+++ b/core/java/android/accessibilityservice/IAccessibilityServiceClient.aidl
@@ -41,7 +41,7 @@
 
     void onKeyEvent(in KeyEvent event, int sequence);
 
-    void onMagnificationChanged(in Region region, float scale, float centerX, float centerY);
+    void onMagnificationChanged(int displayId, in Region region, float scale, float centerX, float centerY);
 
     void onSoftKeyboardShowModeChanged(int showMode);
 
diff --git a/core/java/android/accessibilityservice/IAccessibilityServiceConnection.aidl b/core/java/android/accessibilityservice/IAccessibilityServiceConnection.aidl
index 276131f..8c38fe4 100644
--- a/core/java/android/accessibilityservice/IAccessibilityServiceConnection.aidl
+++ b/core/java/android/accessibilityservice/IAccessibilityServiceConnection.aidl
@@ -70,20 +70,20 @@
 
     oneway void setOnKeyEventResult(boolean handled, int sequence);
 
-    float getMagnificationScale();
+    float getMagnificationScale(int displayId);
 
-    float getMagnificationCenterX();
+    float getMagnificationCenterX(int displayId);
 
-    float getMagnificationCenterY();
+    float getMagnificationCenterY(int displayId);
 
-    Region getMagnificationRegion();
+    Region getMagnificationRegion(int displayId);
 
-    boolean resetMagnification(boolean animate);
+    boolean resetMagnification(int displayId, boolean animate);
 
-    boolean setMagnificationScaleAndCenter(float scale, float centerX, float centerY,
+    boolean setMagnificationScaleAndCenter(int displayId, float scale, float centerX, float centerY,
         boolean animate);
 
-    void setMagnificationCallbackEnabled(boolean enabled);
+    void setMagnificationCallbackEnabled(int displayId, boolean enabled);
 
     boolean setSoftKeyboardShowMode(int showMode);
 
diff --git a/core/java/android/app/Activity.java b/core/java/android/app/Activity.java
index 836627e..92f47e7 100644
--- a/core/java/android/app/Activity.java
+++ b/core/java/android/app/Activity.java
@@ -74,6 +74,7 @@
 import android.os.ServiceManager.ServiceNotFoundException;
 import android.os.StrictMode;
 import android.os.SystemProperties;
+import android.os.Trace;
 import android.os.UserHandle;
 import android.text.Selection;
 import android.text.SpannableStringBuilder;
@@ -1049,33 +1050,56 @@
     @Retention(RetentionPolicy.SOURCE)
     @interface ContentCaptureNotificationType{}
 
-
-    private void notifyContentCaptureManagerIfNeeded(@ContentCaptureNotificationType int type) {
-        final ContentCaptureManager cm = getContentCaptureManager();
-        if (cm == null) return;
-
+    private String getContentCaptureTypeAsString(@ContentCaptureNotificationType int type) {
         switch (type) {
             case CONTENT_CAPTURE_START:
-                //TODO(b/111276913): decide whether the InteractionSessionId should be
-                // saved / restored in the activity bundle - probably not
-                int flags = 0;
-                if ((getWindow().getAttributes().flags
-                        & WindowManager.LayoutParams.FLAG_SECURE) != 0) {
-                    flags |= ContentCaptureContext.FLAG_DISABLED_BY_FLAG_SECURE;
-                }
-                cm.onActivityStarted(mToken, getComponentName(), flags);
-                break;
+                return "START";
             case CONTENT_CAPTURE_PAUSE:
-                cm.flush(ContentCaptureSession.FLUSH_REASON_ACTIVITY_PAUSED);
-                break;
+                return "PAUSE";
             case CONTENT_CAPTURE_RESUME:
-                cm.flush(ContentCaptureSession.FLUSH_REASON_ACTIVITY_RESUMED);
-                break;
+                return "RESUME";
             case CONTENT_CAPTURE_STOP:
-                cm.onActivityStopped();
-                break;
+                return "STOP";
             default:
-                Log.wtf(TAG, "Invalid @ContentCaptureNotificationType: " + type);
+                return "UNKNOW-" + type;
+        }
+    }
+
+    private void notifyContentCaptureManagerIfNeeded(@ContentCaptureNotificationType int type) {
+        if (Trace.isTagEnabled(Trace.TRACE_TAG_ACTIVITY_MANAGER)) {
+            Trace.traceBegin(Trace.TRACE_TAG_ACTIVITY_MANAGER,
+                    "notifyContentCapture(" + getContentCaptureTypeAsString(type) + ") for "
+                            + mComponent.toShortString());
+        }
+        try {
+            final ContentCaptureManager cm = getContentCaptureManager();
+            if (cm == null) return;
+
+            switch (type) {
+                case CONTENT_CAPTURE_START:
+                    //TODO(b/111276913): decide whether the InteractionSessionId should be
+                    // saved / restored in the activity bundle - probably not
+                    int flags = 0;
+                    if ((getWindow().getAttributes().flags
+                            & WindowManager.LayoutParams.FLAG_SECURE) != 0) {
+                        flags |= ContentCaptureContext.FLAG_DISABLED_BY_FLAG_SECURE;
+                    }
+                    cm.onActivityStarted(mToken, getComponentName(), flags);
+                    break;
+                case CONTENT_CAPTURE_PAUSE:
+                    cm.flush(ContentCaptureSession.FLUSH_REASON_ACTIVITY_PAUSED);
+                    break;
+                case CONTENT_CAPTURE_RESUME:
+                    cm.flush(ContentCaptureSession.FLUSH_REASON_ACTIVITY_RESUMED);
+                    break;
+                case CONTENT_CAPTURE_STOP:
+                    cm.onActivityStopped();
+                    break;
+                default:
+                    Log.wtf(TAG, "Invalid @ContentCaptureNotificationType: " + type);
+            }
+        } finally {
+            Trace.traceEnd(Trace.TRACE_TAG_ACTIVITY_MANAGER);
         }
     }
 
@@ -8194,10 +8218,10 @@
             final AutofillId autofillId = autofillIds[i];
             final View view = autofillClientFindViewByAutofillIdTraversal(autofillId);
             if (view != null) {
-                if (!autofillId.isVirtual()) {
+                if (!autofillId.isVirtualInt()) {
                     visible[i] = view.isVisibleToUser();
                 } else {
-                    visible[i] = view.isVisibleToUserForAutofill(autofillId.getVirtualChildId());
+                    visible[i] = view.isVisibleToUserForAutofill(autofillId.getVirtualChildIntId());
                 }
             }
         }
diff --git a/core/java/android/app/ActivityManager.java b/core/java/android/app/ActivityManager.java
index e0b8d78..1045b7a 100644
--- a/core/java/android/app/ActivityManager.java
+++ b/core/java/android/app/ActivityManager.java
@@ -3529,12 +3529,32 @@
 
     /**
      * Returns "true" if device is running in a test harness.
+     *
+     * @deprecated this method is false for all user builds. Users looking to check if their device
+     * is running in a device farm should see {@link #isRunningInUserTestHarness()}.
      */
+    @Deprecated
     public static boolean isRunningInTestHarness() {
         return SystemProperties.getBoolean("ro.test_harness", false);
     }
 
     /**
+     * Returns "true" if the device is running in Test Harness Mode.
+     *
+     * <p>Test Harness Mode is a feature that allows devices to run without human interaction in a
+     * device farm/testing harness (such as Firebase Test Lab). You should check this method if you
+     * want your app to behave differently when running in a test harness to skip setup screens that
+     * would impede UI testing. e.g. a keyboard application that has a full screen setup page for
+     * the first time it is launched.
+     *
+     * <p>Note that you should <em>not</em> use this to determine whether or not your app is running
+     * an instrumentation test, as it is not set for a standard device running a test.
+     */
+    public static boolean isRunningInUserTestHarness() {
+        return SystemProperties.getBoolean("persist.sys.test_harness", false);
+    }
+
+    /**
      * Unsupported compiled sdk warning should always be shown for the intput activity
      * even in cases where the system would normally not show the warning. E.g. when running in a
      * test harness.
diff --git a/core/java/android/app/ActivityManagerInternal.java b/core/java/android/app/ActivityManagerInternal.java
index 5cac048..86e658d 100644
--- a/core/java/android/app/ActivityManagerInternal.java
+++ b/core/java/android/app/ActivityManagerInternal.java
@@ -219,9 +219,11 @@
      * @param userId
      * @param event
      * @param appToken ActivityRecord's appToken.
+     * @param taskRoot TaskRecord's root
      */
     public abstract void updateActivityUsageStats(
-            ComponentName activity, int userId, int event, IBinder appToken);
+            ComponentName activity, int userId, int event, IBinder appToken,
+            ComponentName taskRoot);
     public abstract void updateForegroundTimeIfOnBattery(
             String packageName, int uid, long cpuTimeDiff);
     public abstract void sendForegroundProfileChanged(int userId);
diff --git a/core/java/android/app/ActivityThread.java b/core/java/android/app/ActivityThread.java
index 7767f04..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() {
@@ -5939,6 +5932,11 @@
             Binder.enableTracing();
         }
 
+        // Initialize heap profiling.
+        if (isAppProfileable || Build.IS_DEBUGGABLE) {
+            nInitZygoteChildHeapProfiling();
+        }
+
         // Allow renderer debugging features if we're debuggable.
         boolean isAppDebuggable = (data.appInfo.flags & ApplicationInfo.FLAG_DEBUGGABLE) != 0;
         HardwareRenderer.setDebuggingEnabled(isAppDebuggable || Build.IS_DEBUGGABLE);
@@ -5955,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();
@@ -6965,4 +6962,5 @@
     // ------------------ Regular JNI ------------------------
     private native void nPurgePendingResources();
     private native void nDumpGraphicsInfo(FileDescriptor fd);
+    private native void nInitZygoteChildHeapProfiling();
 }
diff --git a/core/java/android/app/ActivityView.java b/core/java/android/app/ActivityView.java
index ab8f234..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) { }
     }
 
     /**
@@ -328,7 +344,7 @@
                 }
             } else {
                 mTmpTransaction.reparent(mRootSurfaceControl,
-                        mSurfaceView.getSurfaceControl().getHandle()).apply();
+                        mSurfaceView.getSurfaceControl()).apply();
             }
 
             if (mVirtualDisplay != null) {
@@ -390,7 +406,7 @@
                 .build();
 
         try {
-            wm.reparentDisplayContent(displayId, mRootSurfaceControl.getHandle());
+            wm.reparentDisplayContent(displayId, mRootSurfaceControl);
             wm.dontOverrideDisplayInfo(displayId);
             if (mSingleTaskInstance) {
                 mActivityTaskManager.setDisplayToSingleTaskInstance(displayId);
@@ -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 5868771f..364d3c9 100644
--- a/core/java/android/app/AppOpsManager.java
+++ b/core/java/android/app/AppOpsManager.java
@@ -27,6 +27,7 @@
 import android.annotation.UnsupportedAppUsage;
 import android.app.usage.UsageStatsManager;
 import android.content.Context;
+import android.content.pm.PackageManager;
 import android.content.pm.ParceledListSlice;
 import android.database.ContentObserver;
 import android.media.AudioAttributes.AttributeUsage;
@@ -38,12 +39,13 @@
 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;
+import android.util.SparseArray;
 
 import com.android.internal.annotations.GuardedBy;
-import android.util.SparseArray;
 import com.android.internal.app.IAppOpsActiveCallback;
 import com.android.internal.app.IAppOpsCallback;
 import com.android.internal.app.IAppOpsNotedCallback;
@@ -60,8 +62,8 @@
 import java.util.Collections;
 import java.util.HashMap;
 import java.util.List;
-import java.util.concurrent.atomic.AtomicInteger;
 import java.util.concurrent.Executor;
+import java.util.concurrent.atomic.AtomicInteger;
 import java.util.function.Consumer;
 
 /**
@@ -1489,7 +1491,7 @@
             AppOpsManager.MODE_ALLOWED, // READ_ICC_SMS
             AppOpsManager.MODE_ALLOWED, // WRITE_ICC_SMS
             AppOpsManager.MODE_DEFAULT, // WRITE_SETTINGS
-            AppOpsManager.MODE_DEFAULT, // SYSTEM_ALERT_WINDOW
+            getSystemAlertWindowDefault(), // SYSTEM_ALERT_WINDOW
             AppOpsManager.MODE_ALLOWED, // ACCESS_NOTIFICATIONS
             AppOpsManager.MODE_ALLOWED, // CAMERA
             AppOpsManager.MODE_ALLOWED, // RECORD_AUDIO
@@ -1711,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
@@ -4468,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) {
@@ -4816,4 +4825,62 @@
             }
         }
     }
+
+    private static int getSystemAlertWindowDefault() {
+        final Context context = ActivityThread.currentApplication();
+        if (context == null) {
+            return AppOpsManager.MODE_DEFAULT;
+        }
+
+        // system alert window is disable on low ram phones starting from Q
+        final PackageManager pm = context.getPackageManager();
+        // TVs are constantly plugged in and has less concern for memory/power
+        if (ActivityManager.isLowRamDeviceStatic()
+                && !pm.hasSystemFeature(PackageManager.FEATURE_LEANBACK, 0)) {
+            return AppOpsManager.MODE_IGNORED;
+        }
+
+        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/DownloadManager.java b/core/java/android/app/DownloadManager.java
index 1622c06..853b45e 100644
--- a/core/java/android/app/DownloadManager.java
+++ b/core/java/android/app/DownloadManager.java
@@ -1324,7 +1324,8 @@
      * @param description the description that would appear for this file in Downloads App.
      * @param isMediaScannerScannable true if the file is to be scanned by MediaScanner. Files
      * scanned by MediaScanner appear in the applications used to view media (for example,
-     * Gallery app).
+     * Gallery app). Starting from {@link android.os.Build.VERSION_CODES#Q}, this argument is
+     * ignored and the file is always scanned by MediaScanner.
      * @param mimeType mimetype of the file.
      * @param path absolute pathname to the file. The file should be world-readable, so that it can
      * be managed by the Downloads App and any other app that is used to read it (for example,
@@ -1353,7 +1354,8 @@
      * @param description the description that would appear for this file in Downloads App.
      * @param isMediaScannerScannable true if the file is to be scanned by MediaScanner. Files
      * scanned by MediaScanner appear in the applications used to view media (for example,
-     * Gallery app).
+     * Gallery app). Starting from {@link android.os.Build.VERSION_CODES#Q}, this argument is
+     * ignored and the file is always scanned by MediaScanner.
      * @param mimeType mimetype of the file.
      * @param path absolute pathname to the file. The file should be world-readable, so that it can
      * be managed by the Downloads App and any other app that is used to read it (for example,
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 1ae0f52..181acce 100644
--- a/core/java/android/app/KeyguardManager.java
+++ b/core/java/android/app/KeyguardManager.java
@@ -19,6 +19,7 @@
 import android.Manifest;
 import android.annotation.NonNull;
 import android.annotation.Nullable;
+import android.annotation.RequiresFeature;
 import android.annotation.RequiresPermission;
 import android.annotation.SystemApi;
 import android.annotation.SystemService;
@@ -100,6 +101,12 @@
     public static final String EXTRA_DESCRIPTION = "android.app.extra.DESCRIPTION";
 
     /**
+     * A boolean value to forward to {@link android.hardware.biometrics.BiometricPrompt}.
+     * @hide
+     */
+    public static final String EXTRA_USE_IMPLICIT = "android.app.extra.USE_IMPLICIT";
+
+    /**
      * A CharSequence description to show to the user on the alternate button when used with
      * {@link #ACTION_CONFIRM_FRP_CREDENTIAL}.
      * @hide
@@ -122,13 +129,39 @@
      * {@link android.app.Activity#startActivityForResult(Intent, int)} and check for
      * {@link android.app.Activity#RESULT_OK} if the user successfully completes the challenge.
      *
+     * @param title Title to be shown on the dialog.
+     * @param description Description to be shown on the dialog.
      * @return the intent for launching the activity or null if no password is required.
      **/
+    @RequiresFeature(PackageManager.FEATURE_SECURE_LOCK_SCREEN)
     public Intent createConfirmDeviceCredentialIntent(CharSequence title, CharSequence description) {
-        if (!isDeviceSecure()) return null;
+        return createConfirmDeviceCredentialIntent(title, description, false /* useImplicit */);
+    }
+
+    /**
+     * Get an intent to prompt the user to confirm credentials (pin, pattern or password)
+     * for the current user of the device. The caller is expected to launch this activity using
+     * {@link android.app.Activity#startActivityForResult(Intent, int)} and check for
+     * {@link android.app.Activity#RESULT_OK} if the user successfully completes the challenge.
+     *
+     * @param title Title to be shown on the dialog.
+     * @param description Description to be shown on the dialog.
+     * @param useImplicit If useImplicit is set to true, ConfirmDeviceCredentials will invoke
+     *      {@link android.hardware.biometrics.BiometricPrompt} with
+     *      {@link android.hardware.biometrics.BiometricPrompt.Builder#setRequireConfirmation(
+     *      boolean)} set to false.
+     * @return the intent for launching the activity or null if no password is required.
+     * @hide
+     */
+    public Intent createConfirmDeviceCredentialIntent(CharSequence title, CharSequence description,
+            boolean useImplicit) {
+        if (!isDeviceSecure()) {
+            return null;
+        }
         Intent intent = new Intent(ACTION_CONFIRM_DEVICE_CREDENTIAL);
         intent.putExtra(EXTRA_TITLE, title);
         intent.putExtra(EXTRA_DESCRIPTION, description);
+        intent.putExtra(EXTRA_USE_IMPLICIT, useImplicit);
 
         // explicitly set the package for security
         intent.setPackage(getSettingsPackageForIntent(intent));
@@ -176,6 +209,7 @@
      * @throws IllegalStateException if the device has already been provisioned
      * @hide
      */
+    @RequiresFeature(PackageManager.FEATURE_SECURE_LOCK_SCREEN)
     @SystemApi
     public Intent createConfirmFactoryResetCredentialIntent(
             CharSequence title, CharSequence description, CharSequence alternateButtonLabel) {
@@ -231,6 +265,7 @@
      * secure notifications cannot be shown if {@code false}
      * @hide
      */
+    @RequiresFeature(PackageManager.FEATURE_SECURE_LOCK_SCREEN)
     @RequiresPermission(Manifest.permission.CONTROL_KEYGUARD_SECURE_NOTIFICATIONS)
     @SystemApi
     public void setPrivateNotificationsAllowed(boolean allow) {
@@ -249,6 +284,7 @@
      * By default, private notifications are allowed.
      * @hide
      */
+    @RequiresFeature(PackageManager.FEATURE_SECURE_LOCK_SCREEN)
     @RequiresPermission(Manifest.permission.CONTROL_KEYGUARD_SECURE_NOTIFICATIONS)
     @SystemApi
     public boolean getPrivateNotificationsAllowed() {
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/UiAutomation.java b/core/java/android/app/UiAutomation.java
index 3f9627e..a021e3c 100644
--- a/core/java/android/app/UiAutomation.java
+++ b/core/java/android/app/UiAutomation.java
@@ -1240,7 +1240,7 @@
                 }
 
                 @Override
-                public void onMagnificationChanged(@NonNull Region region,
+                public void onMagnificationChanged(int displayId, @NonNull Region region,
                         float scale, float centerX, float centerY) {
                     /* do nothing */
                 }
diff --git a/core/java/android/app/VrManager.java b/core/java/android/app/VrManager.java
index 5b87de4..5f1a94c 100644
--- a/core/java/android/app/VrManager.java
+++ b/core/java/android/app/VrManager.java
@@ -215,19 +215,12 @@
     }
 
     /**
-     * Start VR Input method for the packageName in {@link ComponentName}.
-     * This method notifies InputMethodManagerService to use VR IME instead of
-     * regular phone IME.
-     * @param componentName ComponentName of a VR InputMethod that should be set as selected
-     * input by InputMethodManagerService.
+     * This method is not implemented.
+     *
+     * @param componentName not used
      */
     @RequiresPermission(android.Manifest.permission.RESTRICTED_VR_ACCESS)
     public void setVrInputMethod(ComponentName componentName) {
-        try {
-            mService.setVrInputMethod(componentName);
-        } catch (RemoteException e) {
-            e.rethrowFromSystemServer();
-        }
     }
 
     /**
diff --git a/core/java/android/app/admin/DevicePolicyManager.java b/core/java/android/app/admin/DevicePolicyManager.java
index 86db99bf..2514eee 100644
--- a/core/java/android/app/admin/DevicePolicyManager.java
+++ b/core/java/android/app/admin/DevicePolicyManager.java
@@ -1362,16 +1362,23 @@
     public static final String EXTRA_RESTRICTION = "android.app.extra.RESTRICTION";
 
     /**
-     * Activity action: have the user enter a new password. This activity should
-     * be launched after using {@link #setPasswordQuality(ComponentName, int)},
-     * or {@link #setPasswordMinimumLength(ComponentName, int)} to have the user
-     * enter a new password that meets the current requirements. You can use
-     * {@link #isActivePasswordSufficient()} to determine whether you need to
-     * have the user select a new password in order to meet the current
-     * constraints. Upon being resumed from this activity, you can check the new
+     * Activity action: have the user enter a new password.
+     *
+     * <p>For admin apps, this activity should be launched after using {@link
+     * #setPasswordQuality(ComponentName, int)}, or {@link
+     * #setPasswordMinimumLength(ComponentName, int)} to have the user enter a new password that
+     * meets the current requirements. You can use {@link #isActivePasswordSufficient()} to
+     * determine whether you need to have the user select a new password in order to meet the
+     * current constraints. Upon being resumed from this activity, you can check the new
      * password characteristics to see if they are sufficient.
      *
-     * If the intent is launched from within a managed profile with a profile
+     * <p>Non-admin apps can use {@link #getPasswordComplexity()} to check the current screen lock
+     * complexity, and use this activity with extra {@link #EXTRA_PASSWORD_COMPLEXITY} to suggest
+     * to users how complex the app wants the new screen lock to be. Note that both {@link
+     * #getPasswordComplexity()} and the extra {@link #EXTRA_PASSWORD_COMPLEXITY} require the
+     * calling app to have the permission {@link permission#GET_AND_REQUEST_SCREEN_LOCK_COMPLEXITY}.
+     *
+     * <p>If the intent is launched from within a managed profile with a profile
      * owner built against {@link android.os.Build.VERSION_CODES#M} or before,
      * this will trigger entering a new password for the parent of the profile.
      * For all other cases it will trigger entering a new password for the user
@@ -1384,6 +1391,24 @@
             = "android.app.action.SET_NEW_PASSWORD";
 
     /**
+     * An integer indicating the complexity level of the new password an app would like the user to
+     * set when launching the action {@link #ACTION_SET_NEW_PASSWORD}.
+     *
+     * <p>Must be one of
+     * <ul>
+     *     <li>{@link #PASSWORD_COMPLEXITY_HIGH}
+     *     <li>{@link #PASSWORD_COMPLEXITY_MEDIUM}
+     *     <li>{@link #PASSWORD_COMPLEXITY_LOW}
+     *     <li>{@link #PASSWORD_COMPLEXITY_NONE}
+     * </ul>
+     *
+     * <p>If an invalid value is used, it will be treated as {@link #PASSWORD_COMPLEXITY_NONE}.
+     */
+    @RequiresPermission(android.Manifest.permission.GET_AND_REQUEST_SCREEN_LOCK_COMPLEXITY)
+    public static final String EXTRA_PASSWORD_COMPLEXITY =
+            "android.app.extra.PASSWORD_COMPLEXITY";
+
+    /**
      * Constant for {@link #getPasswordComplexity()}: no password.
      *
      * <p>Note that these complexity constants are ordered so that higher values are more complex.
@@ -2513,6 +2538,9 @@
      * requested quality constant (between the policy set here, the user's preference, and any other
      * considerations) is the one that is in effect.
      * <p>
+     * On devices not supporting {@link PackageManager#FEATURE_SECURE_LOCK_SCREEN} feature, the
+     * password is always treated as empty.
+     * <p>
      * The calling device admin must have requested
      * {@link DeviceAdminInfo#USES_POLICY_LIMIT_PASSWORD} to be able to call this method; if it has
      * not, a security exception will be thrown.
@@ -2548,6 +2576,9 @@
      * returned by {@link #getParentProfileInstance(ComponentName)} in order to retrieve
      * restrictions on the parent profile.
      *
+     * <p>Note: on devices not supporting {@link PackageManager#FEATURE_SECURE_LOCK_SCREEN} feature,
+     * the password is always treated as empty.
+     *
      * @param admin The name of the admin component to check, or {@code null} to aggregate
      * all admins.
      */
@@ -2580,6 +2611,9 @@
      * {@link #PASSWORD_QUALITY_ALPHANUMERIC}, or {@link #PASSWORD_QUALITY_COMPLEX} with
      * {@link #setPasswordQuality}.
      * <p>
+     * On devices not supporting {@link PackageManager#FEATURE_SECURE_LOCK_SCREEN} feature, the
+     * password is always treated as empty.
+     * <p>
      * The calling device admin must have requested
      * {@link DeviceAdminInfo#USES_POLICY_LIMIT_PASSWORD} to be able to call this method; if it has
      * not, a security exception will be thrown.
@@ -2609,11 +2643,13 @@
      * restrictions on this user and its participating profiles. Restrictions on profiles that have
      * a separate challenge are not taken into account.
      *
+     * <p>On devices not supporting {@link PackageManager#FEATURE_SECURE_LOCK_SCREEN} feature, the
+     * password is always treated as empty.
+     *
      * <p>This method can be called on the {@link DevicePolicyManager} instance
      * returned by {@link #getParentProfileInstance(ComponentName)} in order to retrieve
      * restrictions on the parent profile.
      *
-     * user and its profiles or a particular one.
      * @param admin The name of the admin component to check, or {@code null} to aggregate
      * all admins.
      */
@@ -2644,6 +2680,9 @@
      * setting this value. This constraint is only imposed if the administrator has also requested
      * {@link #PASSWORD_QUALITY_COMPLEX} with {@link #setPasswordQuality}. The default value is 0.
      * <p>
+     * On devices not supporting {@link PackageManager#FEATURE_SECURE_LOCK_SCREEN} feature, the
+     * password is always treated as empty.
+     * <p>
      * The calling device admin must have requested
      * {@link DeviceAdminInfo#USES_POLICY_LIMIT_PASSWORD} to be able to call this method; if it has
      * not, a security exception will be thrown.
@@ -2678,6 +2717,9 @@
      * and only applies when the password quality is
      * {@link #PASSWORD_QUALITY_COMPLEX}.
      *
+     * <p>On devices not supporting {@link PackageManager#FEATURE_SECURE_LOCK_SCREEN} feature, the
+     * password is always treated as empty.
+     *
      * <p>This method can be called on the {@link DevicePolicyManager} instance
      * returned by {@link #getParentProfileInstance(ComponentName)} in order to retrieve
      * restrictions on the parent profile.
@@ -2714,6 +2756,9 @@
      * setting this value. This constraint is only imposed if the administrator has also requested
      * {@link #PASSWORD_QUALITY_COMPLEX} with {@link #setPasswordQuality}. The default value is 0.
      * <p>
+     * On devices not supporting {@link PackageManager#FEATURE_SECURE_LOCK_SCREEN} feature, the
+     * password is always treated as empty.
+     * <p>
      * The calling device admin must have requested
      * {@link DeviceAdminInfo#USES_POLICY_LIMIT_PASSWORD} to be able to call this method; if it has
      * not, a security exception will be thrown.
@@ -2748,6 +2793,9 @@
      * and only applies when the password quality is
      * {@link #PASSWORD_QUALITY_COMPLEX}.
      *
+     * <p>On devices not supporting {@link PackageManager#FEATURE_SECURE_LOCK_SCREEN} feature, the
+     * password is always treated as empty.
+     *
      * <p>This method can be called on the {@link DevicePolicyManager} instance
      * returned by {@link #getParentProfileInstance(ComponentName)} in order to retrieve
      * restrictions on the parent profile.
@@ -2784,6 +2832,9 @@
      * only imposed if the administrator has also requested {@link #PASSWORD_QUALITY_COMPLEX} with
      * {@link #setPasswordQuality}. The default value is 1.
      * <p>
+     * On devices not supporting {@link PackageManager#FEATURE_SECURE_LOCK_SCREEN} feature, the
+     * password is always treated as empty.
+     * <p>
      * The calling device admin must have requested
      * {@link DeviceAdminInfo#USES_POLICY_LIMIT_PASSWORD} to be able to call this method; if it has
      * not, a security exception will be thrown.
@@ -2818,6 +2869,9 @@
      * and only applies when the password quality is
      * {@link #PASSWORD_QUALITY_COMPLEX}.
      *
+     * <p>On devices not supporting {@link PackageManager#FEATURE_SECURE_LOCK_SCREEN} feature, the
+     * password is always treated as empty.
+     *
      * <p>This method can be called on the {@link DevicePolicyManager} instance
      * returned by {@link #getParentProfileInstance(ComponentName)} in order to retrieve
      * restrictions on the parent profile.
@@ -2853,6 +2907,9 @@
      * setting this value. This constraint is only imposed if the administrator has also requested
      * {@link #PASSWORD_QUALITY_COMPLEX} with {@link #setPasswordQuality}. The default value is 1.
      * <p>
+     * On devices not supporting {@link PackageManager#FEATURE_SECURE_LOCK_SCREEN} feature, the
+     * password is always treated as empty.
+     * <p>
      * The calling device admin must have requested
      * {@link DeviceAdminInfo#USES_POLICY_LIMIT_PASSWORD} to be able to call this method; if it has
      * not, a security exception will be thrown.
@@ -2887,6 +2944,9 @@
      * and only applies when the password quality is
      * {@link #PASSWORD_QUALITY_COMPLEX}.
      *
+     * <p>On devices not supporting {@link PackageManager#FEATURE_SECURE_LOCK_SCREEN} feature, the
+     * password is always treated as empty.
+     *
      * <p>This method can be called on the {@link DevicePolicyManager} instance
      * returned by {@link #getParentProfileInstance(ComponentName)} in order to retrieve
      * restrictions on the parent profile.
@@ -2922,6 +2982,9 @@
      * only imposed if the administrator has also requested {@link #PASSWORD_QUALITY_COMPLEX} with
      * {@link #setPasswordQuality}. The default value is 1.
      * <p>
+     * On devices not supporting {@link PackageManager#FEATURE_SECURE_LOCK_SCREEN} feature, the
+     * password is always treated as empty.
+     * <p>
      * The calling device admin must have requested
      * {@link DeviceAdminInfo#USES_POLICY_LIMIT_PASSWORD} to be able to call this method; if it has
      * not, a security exception will be thrown.
@@ -2955,6 +3018,9 @@
      * and only applies when the password quality is
      * {@link #PASSWORD_QUALITY_COMPLEX}.
      *
+     * <p>On devices not supporting {@link PackageManager#FEATURE_SECURE_LOCK_SCREEN} feature, the
+     * password is always treated as empty.
+     *
      * <p>This method can be called on the {@link DevicePolicyManager} instance
      * returned by {@link #getParentProfileInstance(ComponentName)} in order to retrieve
      * restrictions on the parent profile.
@@ -2990,6 +3056,9 @@
      * setting this value. This constraint is only imposed if the administrator has also requested
      * {@link #PASSWORD_QUALITY_COMPLEX} with {@link #setPasswordQuality}. The default value is 0.
      * <p>
+     * On devices not supporting {@link PackageManager#FEATURE_SECURE_LOCK_SCREEN} feature, the
+     * password is always treated as empty.
+     * <p>
      * The calling device admin must have requested
      * {@link DeviceAdminInfo#USES_POLICY_LIMIT_PASSWORD} to be able to call this method; if it has
      * not, a security exception will be thrown.
@@ -3024,6 +3093,9 @@
      * and only applies when the password quality is
      * {@link #PASSWORD_QUALITY_COMPLEX}.
      *
+     * <p>On devices not supporting {@link PackageManager#FEATURE_SECURE_LOCK_SCREEN} feature, the
+     * password is always treated as empty.
+     *
      * <p>This method can be called on the {@link DevicePolicyManager} instance
      * returned by {@link #getParentProfileInstance(ComponentName)} in order to retrieve
      * restrictions on the parent profile.
@@ -3060,6 +3132,9 @@
      * , {@link #PASSWORD_QUALITY_NUMERIC_COMPLEX} {@link #PASSWORD_QUALITY_ALPHABETIC}, or
      * {@link #PASSWORD_QUALITY_ALPHANUMERIC} with {@link #setPasswordQuality}.
      * <p>
+     * On devices not supporting {@link PackageManager#FEATURE_SECURE_LOCK_SCREEN} feature, the
+     * password is always treated as empty.
+     * <p>
      * The calling device admin must have requested
      * {@link DeviceAdminInfo#USES_POLICY_LIMIT_PASSWORD} to be able to call this method; if it has
      * not, a security exception will be thrown.
@@ -3112,6 +3187,7 @@
      * @throws SecurityException if {@code admin} is not an active administrator or {@code admin}
      *             does not use {@link DeviceAdminInfo#USES_POLICY_EXPIRE_PASSWORD}
      */
+    @RequiresFeature(PackageManager.FEATURE_SECURE_LOCK_SCREEN)
     public void setPasswordExpirationTimeout(@NonNull ComponentName admin, long timeout) {
         if (mService != null) {
             try {
@@ -3136,6 +3212,7 @@
      * @param admin The name of the admin component to check, or {@code null} to aggregate all admins.
      * @return The timeout for the given admin or the minimum of all timeouts
      */
+    @RequiresFeature(PackageManager.FEATURE_SECURE_LOCK_SCREEN)
     public long getPasswordExpirationTimeout(@Nullable ComponentName admin) {
         if (mService != null) {
             try {
@@ -3160,6 +3237,7 @@
      * @param admin The name of the admin component to check, or {@code null} to aggregate all admins.
      * @return The password expiration time, in milliseconds since epoch.
      */
+    @RequiresFeature(PackageManager.FEATURE_SECURE_LOCK_SCREEN)
     public long getPasswordExpiration(@Nullable ComponentName admin) {
         if (mService != null) {
             try {
@@ -3184,12 +3262,14 @@
      * all admins.
      * @return The length of the password history
      */
+    @RequiresFeature(PackageManager.FEATURE_SECURE_LOCK_SCREEN)
     public int getPasswordHistoryLength(@Nullable ComponentName admin) {
         return getPasswordHistoryLength(admin, myUserId());
     }
 
     /** @hide per-user version */
     @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.P, trackingBug = 115609023)
+    @RequiresFeature(PackageManager.FEATURE_SECURE_LOCK_SCREEN)
     public int getPasswordHistoryLength(@Nullable ComponentName admin, int userHandle) {
         if (mService != null) {
             try {
@@ -3204,10 +3284,16 @@
     /**
      * Return the maximum password length that the device supports for a
      * particular password quality.
+     * <p>On devices not supporting {@link PackageManager#FEATURE_SECURE_LOCK_SCREEN} feature, the
+     * password is always empty.
      * @param quality The quality being interrogated.
      * @return Returns the maximum length that the user can enter.
      */
     public int getPasswordMaximumLength(int quality) {
+        PackageManager pm = mContext.getPackageManager();
+        if (!pm.hasSystemFeature(PackageManager.FEATURE_SECURE_LOCK_SCREEN)) {
+            return 0;
+        }
         // Kind-of arbitrary.
         return 16;
     }
@@ -3218,6 +3304,10 @@
      * user and its participating profiles. Restrictions on profiles that have a separate challenge
      * are not taken into account. The user must be unlocked in order to perform the check.
      * <p>
+     * On devices not supporting {@link PackageManager#FEATURE_SECURE_LOCK_SCREEN} feature, the
+     * password is always treated as empty - i.e. this method will always return false on such
+     * devices, provided any password requirements were set.
+     * <p>
      * The calling device admin must have requested
      * {@link DeviceAdminInfo#USES_POLICY_LIMIT_PASSWORD} to be able to call this method; if it has
      * not, a security exception will be thrown.
@@ -3250,6 +3340,9 @@
      * explicitly querying the parent profile screen lock complexity via {@link
      * #getParentProfileInstance}.
      *
+     * <p>On devices not supporting {@link PackageManager#FEATURE_SECURE_LOCK_SCREEN} feature, the
+     * password is always treated as empty.
+     *
      * @throws IllegalStateException if the user is not unlocked.
      * @throws SecurityException if the calling application does not have the permission
      *                           {@link permission#GET_AND_REQUEST_SCREEN_LOCK_COMPLEXITY}
@@ -3329,6 +3422,7 @@
      * @throws SecurityException if the calling application does not own an active administrator
      *             that uses {@link DeviceAdminInfo#USES_POLICY_WATCH_LOGIN}
      */
+    @RequiresFeature(PackageManager.FEATURE_SECURE_LOCK_SCREEN)
     public int getCurrentFailedPasswordAttempts() {
         return getCurrentFailedPasswordAttempts(myUserId());
     }
@@ -3396,6 +3490,7 @@
      *             both {@link DeviceAdminInfo#USES_POLICY_WATCH_LOGIN} and
      *             {@link DeviceAdminInfo#USES_POLICY_WIPE_DATA}.
      */
+    @RequiresFeature(PackageManager.FEATURE_SECURE_LOCK_SCREEN)
     public void setMaximumFailedPasswordsForWipe(@NonNull ComponentName admin, int num) {
         if (mService != null) {
             try {
@@ -3419,12 +3514,14 @@
      * @param admin The name of the admin component to check, or {@code null} to aggregate
      * all admins.
      */
+    @RequiresFeature(PackageManager.FEATURE_SECURE_LOCK_SCREEN)
     public int getMaximumFailedPasswordsForWipe(@Nullable ComponentName admin) {
         return getMaximumFailedPasswordsForWipe(admin, myUserId());
     }
 
     /** @hide per-user version */
     @UnsupportedAppUsage
+    @RequiresFeature(PackageManager.FEATURE_SECURE_LOCK_SCREEN)
     public int getMaximumFailedPasswordsForWipe(@Nullable ComponentName admin, int userHandle) {
         if (mService != null) {
             try {
@@ -3444,6 +3541,7 @@
      * user passed in.
      * @hide Used only by Keyguard
      */
+    @RequiresFeature(PackageManager.FEATURE_SECURE_LOCK_SCREEN)
     public int getProfileWithMinimumFailedPasswordsForWipe(int userHandle) {
         if (mService != null) {
             try {
@@ -3514,6 +3612,7 @@
      *             that uses {@link DeviceAdminInfo#USES_POLICY_RESET_PASSWORD}
      * @throws IllegalStateException if the calling user is locked or has a managed profile.
      */
+    @RequiresFeature(PackageManager.FEATURE_SECURE_LOCK_SCREEN)
     public boolean resetPassword(String password, int flags) {
         throwIfParentInstance("resetPassword");
         if (mService != null) {
@@ -3557,6 +3656,7 @@
      * @throws SecurityException if admin is not a device or profile owner.
      * @throws IllegalArgumentException if the supplied token is invalid.
      */
+    @RequiresFeature(PackageManager.FEATURE_SECURE_LOCK_SCREEN)
     public boolean setResetPasswordToken(ComponentName admin, byte[] token) {
         throwIfParentInstance("setResetPasswordToken");
         if (mService != null) {
@@ -3576,6 +3676,7 @@
      * @return true if the operation is successful, false otherwise.
      * @throws SecurityException if admin is not a device or profile owner.
      */
+    @RequiresFeature(PackageManager.FEATURE_SECURE_LOCK_SCREEN)
     public boolean clearResetPasswordToken(ComponentName admin) {
         throwIfParentInstance("clearResetPasswordToken");
         if (mService != null) {
@@ -3596,6 +3697,7 @@
      * @throws SecurityException if admin is not a device or profile owner.
      * @throws IllegalStateException if no token has been set.
      */
+    @RequiresFeature(PackageManager.FEATURE_SECURE_LOCK_SCREEN)
     public boolean isResetPasswordTokenActive(ComponentName admin) {
         throwIfParentInstance("isResetPasswordTokenActive");
         if (mService != null) {
@@ -3637,6 +3739,7 @@
      * @throws SecurityException if admin is not a device or profile owner.
      * @throws IllegalStateException if the provided token is not valid.
      */
+    @RequiresFeature(PackageManager.FEATURE_SECURE_LOCK_SCREEN)
     public boolean resetPasswordWithToken(@NonNull ComponentName admin, String password,
             byte[] token, int flags) {
         throwIfParentInstance("resetPassword");
@@ -3742,6 +3845,7 @@
      *
      * @throws SecurityException if {@code admin} is not a device or profile owner.
      */
+    @RequiresFeature(PackageManager.FEATURE_SECURE_LOCK_SCREEN)
     public void setRequiredStrongAuthTimeout(@NonNull ComponentName admin,
             long timeoutMs) {
         if (mService != null) {
@@ -3766,12 +3870,14 @@
      *         across all participating admins.
      * @return The timeout in milliseconds or 0 if not configured for the provided admin.
      */
+    @RequiresFeature(PackageManager.FEATURE_SECURE_LOCK_SCREEN)
     public long getRequiredStrongAuthTimeout(@Nullable ComponentName admin) {
         return getRequiredStrongAuthTimeout(admin, myUserId());
     }
 
     /** @hide per-user version */
     @UnsupportedAppUsage
+    @RequiresFeature(PackageManager.FEATURE_SECURE_LOCK_SCREEN)
     public long getRequiredStrongAuthTimeout(@Nullable ComponentName admin, @UserIdInt int userId) {
         if (mService != null) {
             try {
@@ -3874,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
@@ -3885,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");
@@ -3907,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());
     }
 
     /**
@@ -3927,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);
@@ -5350,6 +5466,7 @@
      * @hide
      */
     @UnsupportedAppUsage
+    @RequiresFeature(PackageManager.FEATURE_SECURE_LOCK_SCREEN)
     public void setActivePasswordState(PasswordMetrics metrics, int userHandle) {
         if (mService != null) {
             try {
@@ -5363,6 +5480,7 @@
     /**
      * @hide
      */
+    @RequiresFeature(PackageManager.FEATURE_SECURE_LOCK_SCREEN)
     public void reportPasswordChanged(@UserIdInt int userId) {
         if (mService != null) {
             try {
@@ -5377,6 +5495,7 @@
      * @hide
      */
     @UnsupportedAppUsage
+    @RequiresFeature(PackageManager.FEATURE_SECURE_LOCK_SCREEN)
     public void reportFailedPasswordAttempt(int userHandle) {
         if (mService != null) {
             try {
@@ -5391,6 +5510,7 @@
      * @hide
      */
     @UnsupportedAppUsage
+    @RequiresFeature(PackageManager.FEATURE_SECURE_LOCK_SCREEN)
     public void reportSuccessfulPasswordAttempt(int userHandle) {
         if (mService != null) {
             try {
@@ -5404,6 +5524,7 @@
     /**
      * @hide
      */
+    @RequiresFeature(PackageManager.FEATURE_SECURE_LOCK_SCREEN)
     public void reportFailedBiometricAttempt(int userHandle) {
         if (mService != null) {
             try {
@@ -5417,6 +5538,7 @@
     /**
      * @hide
      */
+    @RequiresFeature(PackageManager.FEATURE_SECURE_LOCK_SCREEN)
     public void reportSuccessfulBiometricAttempt(int userHandle) {
         if (mService != null) {
             try {
@@ -6383,6 +6505,7 @@
      * @throws SecurityException if {@code admin} is not an active administrator or does not use
      *             {@link DeviceAdminInfo#USES_POLICY_DISABLE_KEYGUARD_FEATURES}
      */
+    @RequiresFeature(PackageManager.FEATURE_SECURE_LOCK_SCREEN)
     public void setTrustAgentConfiguration(@NonNull ComponentName admin,
             @NonNull ComponentName target, PersistableBundle configuration) {
         if (mService != null) {
@@ -6412,6 +6535,7 @@
      * @param agent Which component to get enabled features for.
      * @return configuration for the given trust agent.
      */
+    @RequiresFeature(PackageManager.FEATURE_SECURE_LOCK_SCREEN)
     public @Nullable List<PersistableBundle> getTrustAgentConfiguration(
             @Nullable ComponentName admin, @NonNull ComponentName agent) {
         return getTrustAgentConfiguration(admin, agent, myUserId());
@@ -6419,6 +6543,7 @@
 
     /** @hide per-user version */
     @UnsupportedAppUsage
+    @RequiresFeature(PackageManager.FEATURE_SECURE_LOCK_SCREEN)
     public @Nullable List<PersistableBundle> getTrustAgentConfiguration(
             @Nullable ComponentName admin, @NonNull ComponentName agent, int userHandle) {
         if (mService != null) {
@@ -9403,18 +9528,12 @@
     }
 
     /**
-     * Allows the device owner or profile owner to enable or disable the backup service.
+     * Allows the device owner to enable or disable the backup service.
      *
-     * <p> Each user has its own backup service which manages the backup and restore mechanisms in
-     * that user. Disabling the backup service will prevent data from being backed up or restored.
+     * <p> Backup service manages all backup and restore mechanisms on the device. Setting this to
+     * false will prevent data from being backed up or restored.
      *
-     * <p> Device owner calls this API to control backup services across all users on the device.
-     * Profile owner can use this API to enable or disable the profile's backup service. However,
-     * for a managed profile its backup functionality is only enabled if both the device owner
-     * and the profile owner have enabled the backup service.
-     *
-     * <p> By default, backup service is disabled on a device with device owner, and within a
-     * managed profile.
+     * <p> Backup service is off by default when device owner is present.
      *
      * @param admin Which {@link DeviceAdminReceiver} this request is associated with.
      * @param enabled {@code true} to enable the backup service, {@code false} to disable it.
@@ -9430,12 +9549,7 @@
     }
 
     /**
-     * Return whether the backup service is enabled by the device owner or profile owner for the
-     * current user, as previously set by {@link #setBackupServiceEnabled(ComponentName, boolean)}.
-     *
-     * <p> Whether the backup functionality is actually enabled or not depends on settings from both
-     * the current user and the device owner, please see
-     * {@link #setBackupServiceEnabled(ComponentName, boolean)} for details.
+     * Return whether the backup service is enabled by the device owner.
      *
      * <p> Backup service manages all backup and restore mechanisms on the device.
      *
@@ -10324,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();
             }
@@ -10348,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();
             }
@@ -10402,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 {
@@ -10431,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/admin/PasswordMetrics.java b/core/java/android/app/admin/PasswordMetrics.java
index 8b41755..e5df2c7 100644
--- a/core/java/android/app/admin/PasswordMetrics.java
+++ b/core/java/android/app/admin/PasswordMetrics.java
@@ -20,6 +20,11 @@
 import static android.app.admin.DevicePolicyManager.PASSWORD_COMPLEXITY_LOW;
 import static android.app.admin.DevicePolicyManager.PASSWORD_COMPLEXITY_MEDIUM;
 import static android.app.admin.DevicePolicyManager.PASSWORD_COMPLEXITY_NONE;
+import static android.app.admin.DevicePolicyManager.PASSWORD_QUALITY_ALPHABETIC;
+import static android.app.admin.DevicePolicyManager.PASSWORD_QUALITY_ALPHANUMERIC;
+import static android.app.admin.DevicePolicyManager.PASSWORD_QUALITY_COMPLEX;
+import static android.app.admin.DevicePolicyManager.PASSWORD_QUALITY_NUMERIC;
+import static android.app.admin.DevicePolicyManager.PASSWORD_QUALITY_UNSPECIFIED;
 
 import android.annotation.IntDef;
 import android.annotation.NonNull;
@@ -27,6 +32,8 @@
 import android.os.Parcel;
 import android.os.Parcelable;
 
+import com.android.internal.annotations.VisibleForTesting;
+
 import java.lang.annotation.Retention;
 import java.lang.annotation.RetentionPolicy;
 
@@ -85,6 +92,101 @@
         nonLetter = in.readInt();
     }
 
+    /** Returns the min quality allowed by {@code complexityLevel}. */
+    public static int complexityLevelToMinQuality(@PasswordComplexity int complexityLevel) {
+        // this would be the quality of the first metrics since mMetrics is sorted in ascending
+        // order of quality
+        return PasswordComplexityBucket
+                .complexityLevelToBucket(complexityLevel).mMetrics[0].quality;
+    }
+
+    /**
+     * Returns a merged minimum {@link PasswordMetrics} requirements that a new password must meet
+     * to fulfil {@code requestedQuality}, {@code requiresNumeric} and {@code
+     * requiresLettersOrSymbols}, which are derived from {@link DevicePolicyManager} requirements,
+     * and {@code complexityLevel}.
+     *
+     * <p>Note that we are taking {@code userEnteredPasswordQuality} into account because there are
+     * more than one set of metrics to meet the minimum complexity requirement and inspecting what
+     * the user has entered can help determine whether the alphabetic or alphanumeric set of metrics
+     * should be used. For example, suppose minimum complexity requires either ALPHABETIC(8+), or
+     * ALPHANUMERIC(6+). If the user has entered "a", the length requirement displayed on the UI
+     * would be 8. Then the user appends "1" to make it "a1". We now know the user is entering
+     * an alphanumeric password so we would update the min complexity required min length to 6.
+     */
+    public static PasswordMetrics getMinimumMetrics(@PasswordComplexity int complexityLevel,
+            int userEnteredPasswordQuality, int requestedQuality, boolean requiresNumeric,
+            boolean requiresLettersOrSymbols) {
+        int targetQuality = Math.max(
+                userEnteredPasswordQuality,
+                getActualRequiredQuality(
+                        requestedQuality, requiresNumeric, requiresLettersOrSymbols));
+        return getTargetQualityMetrics(complexityLevel, targetQuality);
+    }
+
+    /**
+     * Returns the {@link PasswordMetrics} at {@code complexityLevel} which the metrics quality
+     * is the same as {@code targetQuality}.
+     *
+     * <p>If {@code complexityLevel} does not allow {@code targetQuality}, returns the metrics
+     * with the min quality at {@code complexityLevel}.
+     */
+    // TODO(bernardchau): update tests to test getMinimumMetrics and change this to be private
+    @VisibleForTesting
+    public static PasswordMetrics getTargetQualityMetrics(
+            @PasswordComplexity int complexityLevel, int targetQuality) {
+        PasswordComplexityBucket targetBucket =
+                PasswordComplexityBucket.complexityLevelToBucket(complexityLevel);
+        for (PasswordMetrics metrics : targetBucket.mMetrics) {
+            if (targetQuality == metrics.quality) {
+                return metrics;
+            }
+        }
+        // none of the metrics at complexityLevel has targetQuality, return metrics with min quality
+        // see test case testGetMinimumMetrics_actualRequiredQualityStricter for an example, where
+        // min complexity allows at least NUMERIC_COMPLEX, user has not entered anything yet, and
+        // requested quality is NUMERIC
+        return targetBucket.mMetrics[0];
+    }
+
+    /**
+     * Finds out the actual quality requirement based on whether quality is {@link
+     * DevicePolicyManager#PASSWORD_QUALITY_COMPLEX} and whether digits, letters or symbols are
+     * required.
+     */
+    @VisibleForTesting
+    // TODO(bernardchau): update tests to test getMinimumMetrics and change this to be private
+    public static int getActualRequiredQuality(
+            int requestedQuality, boolean requiresNumeric, boolean requiresLettersOrSymbols) {
+        if (requestedQuality != PASSWORD_QUALITY_COMPLEX) {
+            return requestedQuality;
+        }
+
+        // find out actual password quality from complex requirements
+        if (requiresNumeric && requiresLettersOrSymbols) {
+            return PASSWORD_QUALITY_ALPHANUMERIC;
+        }
+        if (requiresLettersOrSymbols) {
+            return PASSWORD_QUALITY_ALPHABETIC;
+        }
+        if (requiresNumeric) {
+            // cannot specify numeric complex using complex quality so this must be numeric
+            return PASSWORD_QUALITY_NUMERIC;
+        }
+
+        // reaching here means dpm sets quality to complex without specifying any requirements
+        return PASSWORD_QUALITY_UNSPECIFIED;
+    }
+
+    /**
+     * Returns {@code complexityLevel} or {@link DevicePolicyManager#PASSWORD_COMPLEXITY_NONE}
+     * if {@code complexityLevel} is not valid.
+     */
+    @PasswordComplexity
+    public static int sanitizeComplexityLevel(@PasswordComplexity int complexityLevel) {
+        return PasswordComplexityBucket.complexityLevelToBucket(complexityLevel).mComplexityLevel;
+    }
+
     public boolean isDefault() {
         return quality == DevicePolicyManager.PASSWORD_QUALITY_UNSPECIFIED
                 && length == 0 && letters == 0 && upperCase == 0 && lowerCase == 0
@@ -280,7 +382,7 @@
     @PasswordComplexity
     public int determineComplexity() {
         for (PasswordComplexityBucket bucket : PasswordComplexityBucket.BUCKETS) {
-            if (satisfiesBucket(bucket.getMetrics())) {
+            if (satisfiesBucket(bucket.mMetrics)) {
                 return bucket.mComplexityLevel;
             }
         }
@@ -290,7 +392,7 @@
     /**
      * Requirements in terms of {@link PasswordMetrics} for each {@link PasswordComplexity}.
      */
-    public static class PasswordComplexityBucket {
+    private static class PasswordComplexityBucket {
         /**
          * Definition of {@link DevicePolicyManager#PASSWORD_COMPLEXITY_HIGH} in terms of
          * {@link PasswordMetrics}.
@@ -299,12 +401,13 @@
                 new PasswordComplexityBucket(
                         PASSWORD_COMPLEXITY_HIGH,
                         new PasswordMetrics(
-                                DevicePolicyManager.PASSWORD_QUALITY_ALPHANUMERIC, /* length= */ 6),
+                                DevicePolicyManager.PASSWORD_QUALITY_NUMERIC_COMPLEX, /* length= */
+                                8),
                         new PasswordMetrics(
                                 DevicePolicyManager.PASSWORD_QUALITY_ALPHABETIC, /* length= */ 6),
                         new PasswordMetrics(
-                                DevicePolicyManager.PASSWORD_QUALITY_NUMERIC_COMPLEX, /* length= */
-                                8));
+                                DevicePolicyManager.PASSWORD_QUALITY_ALPHANUMERIC, /* length= */
+                                6));
 
         /**
          * Definition of {@link DevicePolicyManager#PASSWORD_COMPLEXITY_MEDIUM} in terms of
@@ -314,11 +417,12 @@
                 new PasswordComplexityBucket(
                         PASSWORD_COMPLEXITY_MEDIUM,
                         new PasswordMetrics(
-                                DevicePolicyManager.PASSWORD_QUALITY_ALPHANUMERIC, /* length= */ 4),
+                                DevicePolicyManager.PASSWORD_QUALITY_NUMERIC_COMPLEX, /* length= */
+                                4),
                         new PasswordMetrics(
                                 DevicePolicyManager.PASSWORD_QUALITY_ALPHABETIC, /* length= */ 4),
                         new PasswordMetrics(
-                                DevicePolicyManager.PASSWORD_QUALITY_NUMERIC_COMPLEX, /* length= */
+                                DevicePolicyManager.PASSWORD_QUALITY_ALPHANUMERIC, /* length= */
                                 4));
 
         /**
@@ -328,11 +432,11 @@
         private static final PasswordComplexityBucket LOW =
                 new PasswordComplexityBucket(
                         PASSWORD_COMPLEXITY_LOW,
-                        new PasswordMetrics(DevicePolicyManager.PASSWORD_QUALITY_ALPHANUMERIC),
-                        new PasswordMetrics(DevicePolicyManager.PASSWORD_QUALITY_ALPHABETIC),
-                        new PasswordMetrics(DevicePolicyManager.PASSWORD_QUALITY_NUMERIC_COMPLEX),
+                        new PasswordMetrics(DevicePolicyManager.PASSWORD_QUALITY_SOMETHING),
                         new PasswordMetrics(DevicePolicyManager.PASSWORD_QUALITY_NUMERIC),
-                        new PasswordMetrics(DevicePolicyManager.PASSWORD_QUALITY_SOMETHING));
+                        new PasswordMetrics(DevicePolicyManager.PASSWORD_QUALITY_NUMERIC_COMPLEX),
+                        new PasswordMetrics(DevicePolicyManager.PASSWORD_QUALITY_ALPHABETIC),
+                        new PasswordMetrics(DevicePolicyManager.PASSWORD_QUALITY_ALPHANUMERIC));
 
         /**
          * A special bucket to represent {@link DevicePolicyManager#PASSWORD_COMPLEXITY_NONE}.
@@ -348,19 +452,27 @@
         private final int mComplexityLevel;
         private final PasswordMetrics[] mMetrics;
 
+        /**
+         * @param metricsArray must be sorted in ascending order of {@link #quality}.
+         */
         private PasswordComplexityBucket(@PasswordComplexity int complexityLevel,
-                PasswordMetrics... metrics) {
-            this.mComplexityLevel = complexityLevel;
-            this.mMetrics = metrics;
-        }
+                PasswordMetrics... metricsArray) {
+            int previousQuality = PASSWORD_QUALITY_UNSPECIFIED;
+            for (PasswordMetrics metrics : metricsArray) {
+                if (metrics.quality < previousQuality) {
+                    throw new IllegalArgumentException("metricsArray must be sorted in ascending"
+                            + " order of quality");
+                }
+                previousQuality = metrics.quality;
+            }
 
-        /** Returns the {@link PasswordMetrics} that meet the min requirements of this bucket. */
-        public PasswordMetrics[] getMetrics() {
-            return mMetrics;
+            this.mMetrics = metricsArray;
+            this.mComplexityLevel = complexityLevel;
+
         }
 
         /** Returns the bucket that {@code complexityLevel} represents. */
-        public static PasswordComplexityBucket complexityLevelToBucket(
+        private static PasswordComplexityBucket complexityLevelToBucket(
                 @PasswordComplexity int complexityLevel) {
             for (PasswordComplexityBucket bucket : BUCKETS) {
                 if (bucket.mComplexityLevel == complexityLevel) {
diff --git a/core/java/android/app/assist/AssistStructure.java b/core/java/android/app/assist/AssistStructure.java
index 7d03f00..6006ad2 100644
--- a/core/java/android/app/assist/AssistStructure.java
+++ b/core/java/android/app/assist/AssistStructure.java
@@ -894,7 +894,7 @@
             }
             if (mAutofillId != null) {
                 autofillFlags |= AUTOFILL_FLAGS_HAS_AUTOFILL_VIEW_ID;
-                if (mAutofillId.isVirtual()) {
+                if (mAutofillId.isVirtualInt()) {
                     autofillFlags |= AUTOFILL_FLAGS_HAS_AUTOFILL_VIRTUAL_VIEW_ID;
                 }
             }
@@ -961,8 +961,9 @@
                 if ((autofillFlags & AUTOFILL_FLAGS_HAS_AUTOFILL_VIEW_ID) != 0) {
                     out.writeInt(mAutofillId.getViewId());
                     if ((autofillFlags & AUTOFILL_FLAGS_HAS_AUTOFILL_VIRTUAL_VIEW_ID) != 0) {
-                        out.writeInt(mAutofillId.getVirtualChildId());
+                        out.writeInt(mAutofillId.getVirtualChildIntId());
                     }
+                    // TODO(b/113593220): write session id as well
                 }
                 if ((autofillFlags & AUTOFILL_FLAGS_HAS_AUTOFILL_TYPE) != 0) {
                     out.writeInt(mAutofillType);
diff --git a/core/java/android/app/backup/BackupAgent.java b/core/java/android/app/backup/BackupAgent.java
index c983d4f..24580b4 100644
--- a/core/java/android/app/backup/BackupAgent.java
+++ b/core/java/android/app/backup/BackupAgent.java
@@ -16,6 +16,7 @@
 
 package android.app.backup;
 
+import android.annotation.Nullable;
 import android.app.IBackupAgent;
 import android.app.QueuedWork;
 import android.app.backup.FullBackup.BackupScheme.PathWithRequiredFlags;
@@ -181,6 +182,8 @@
 
     Handler mHandler = null;
 
+    @Nullable private UserHandle mUser;
+
     Handler getHandler() {
         if (mHandler == null) {
             mHandler = new Handler(Looper.getMainLooper());
@@ -232,6 +235,8 @@
      */
     public void onCreate(UserHandle user) {
         onCreate();
+
+        mUser = user;
     }
 
     /**
@@ -528,6 +533,10 @@
     public void onQuotaExceeded(long backupDataBytes, long quotaBytes) {
     }
 
+    private int getBackupUserId() {
+        return mUser == null ? super.getUserId() : mUser.getIdentifier();
+    }
+
     /**
      * Check whether the xml yielded any <include/> tag for the provided <code>domainToken</code>.
      * If so, perform a {@link #fullBackupFileTree} which backs up the file or recurses if the path
@@ -1033,7 +1042,7 @@
 
                 Binder.restoreCallingIdentity(ident);
                 try {
-                    callbackBinder.opComplete(token, 0);
+                    callbackBinder.opCompleteForUser(getBackupUserId(), token, 0);
                 } catch (RemoteException e) {
                     // we'll time out anyway, so we're safe
                 }
@@ -1082,7 +1091,7 @@
 
                 Binder.restoreCallingIdentity(ident);
                 try {
-                    callbackBinder.opComplete(token, 0);
+                    callbackBinder.opCompleteForUser(getBackupUserId(), token, 0);
                 } catch (RemoteException e) {
                     // we'll time out anyway, so we're safe
                 }
@@ -1112,7 +1121,8 @@
             } finally {
                 Binder.restoreCallingIdentity(ident);
                 try {
-                    callbackBinder.opComplete(token, measureOutput.getSize());
+                    callbackBinder.opCompleteForUser(getBackupUserId(), token,
+                            measureOutput.getSize());
                 } catch (RemoteException e) {
                     // timeout, so we're safe
                 }
@@ -1137,7 +1147,7 @@
 
                 Binder.restoreCallingIdentity(ident);
                 try {
-                    callbackBinder.opComplete(token, 0);
+                    callbackBinder.opCompleteForUser(getBackupUserId(), token, 0);
                 } catch (RemoteException e) {
                     // we'll time out anyway, so we're safe
                 }
@@ -1162,7 +1172,7 @@
 
                 Binder.restoreCallingIdentity(ident);
                 try {
-                    callbackBinder.opComplete(token, 0);
+                    callbackBinder.opCompleteForUser(getBackupUserId(), token, 0);
                 } catch (RemoteException e) {
                     // we'll time out anyway, so we're safe
                 }
diff --git a/core/java/android/app/backup/IBackupManager.aidl b/core/java/android/app/backup/IBackupManager.aidl
index f8c5a81..eda8981 100644
--- a/core/java/android/app/backup/IBackupManager.aidl
+++ b/core/java/android/app/backup/IBackupManager.aidl
@@ -549,6 +549,19 @@
 
     /**
      * Notify the backup manager that a BackupAgent has completed the operation
+     * corresponding to the given token and user id.
+     *
+     * @param userId User id for which the operation has been completed.
+     * @param token The transaction token passed to the BackupAgent method being
+     *        invoked.
+     * @param result In the case of a full backup measure operation, the estimated
+     *        total file size that would result from the operation. Unused in all other
+     *        cases.
+     */
+    void opCompleteForUser(int userId, int token, long result);
+
+    /**
+     * Notify the backup manager that a BackupAgent has completed the operation
      * corresponding to the given token.
      *
      * @param token The transaction token passed to the BackupAgent method being
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/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 bbae7d3..b1500c1 100644
--- a/core/java/android/app/usage/IUsageStatsManager.aidl
+++ b/core/java/android/app/usage/IUsageStatsManager.aidl
@@ -55,8 +55,13 @@
             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);
     void reportUsageStop(in IBinder activity, String token, String callingPackage);
+    int getUsageSource();
+    void forceUsageSourceSettingRead();
 }
diff --git a/core/java/android/app/usage/UsageEvents.java b/core/java/android/app/usage/UsageEvents.java
index 2c5fe04..451f44b 100644
--- a/core/java/android/app/usage/UsageEvents.java
+++ b/core/java/android/app/usage/UsageEvents.java
@@ -16,6 +16,7 @@
 package android.app.usage;
 
 import android.annotation.IntDef;
+import android.annotation.Nullable;
 import android.annotation.SystemApi;
 import android.annotation.UnsupportedAppUsage;
 import android.content.res.Configuration;
@@ -286,7 +287,6 @@
         @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.P, trackingBug = 115609023)
         public String mClass;
 
-
         /**
          * {@hide}
          */
@@ -295,6 +295,16 @@
         /**
          * {@hide}
          */
+        public String mTaskRootPackage;
+
+        /**
+         * {@hide}
+         */
+        public String mTaskRootClass;
+
+        /**
+         * {@hide}
+         */
         @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.P, trackingBug = 115609023)
         public long mTimeStamp;
 
@@ -373,6 +383,8 @@
             mPackage = orig.mPackage;
             mClass = orig.mClass;
             mInstanceId = orig.mInstanceId;
+            mTaskRootPackage = orig.mTaskRootPackage;
+            mTaskRootClass = orig.mTaskRootClass;
             mTimeStamp = orig.mTimeStamp;
             mEventType = orig.mEventType;
             mConfiguration = orig.mConfiguration;
@@ -411,6 +423,28 @@
         }
 
         /**
+         * The package name of the task root when this event was reported.
+         * Or {@code null} for queries from apps without {@link
+         * android.Manifest.permission#PACKAGE_USAGE_STATS}
+         * @hide
+         */
+        @SystemApi
+        public @Nullable String getTaskRootPackageName() {
+            return mTaskRootPackage;
+        }
+
+        /**
+         * The class name of the task root when this event was reported.
+         * Or {@code null} for queries from apps without {@link
+         * android.Manifest.permission#PACKAGE_USAGE_STATS}
+         * @hide
+         */
+        @SystemApi
+        public @Nullable String getTaskRootClassName() {
+            return mTaskRootClass;
+        }
+
+        /**
          * The time at which this event occurred, measured in milliseconds since the epoch.
          * <p/>
          * See {@link System#currentTimeMillis()}.
@@ -522,6 +556,9 @@
     @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.P, trackingBug = 115609023)
     private int mIndex = 0;
 
+    // Only used when parceling events. If false, task roots will be omitted from the parcel
+    private final boolean mIncludeTaskRoots;
+
     /*
      * In order to save space, since ComponentNames will be duplicated everywhere,
      * we use a map and index into it.
@@ -552,6 +589,7 @@
             mParcel.setDataSize(mParcel.dataPosition());
             mParcel.setDataPosition(positionInParcel);
         }
+        mIncludeTaskRoots = true;
     }
 
     /**
@@ -560,16 +598,27 @@
      */
     UsageEvents() {
         mEventCount = 0;
+        mIncludeTaskRoots = true;
+    }
+
+    /**
+     * Construct the iterator in preparation for writing it to a parcel.
+     * Defaults to excluding task roots from the parcel.
+     * {@hide}
+     */
+    public UsageEvents(List<Event> events, String[] stringPool) {
+        this(events, stringPool, false);
     }
 
     /**
      * Construct the iterator in preparation for writing it to a parcel.
      * {@hide}
      */
-    public UsageEvents(List<Event> events, String[] stringPool) {
+    public UsageEvents(List<Event> events, String[] stringPool, boolean includeTaskRoots) {
         mStringPool = stringPool;
         mEventCount = events.size();
         mEventsToWrite = events;
+        mIncludeTaskRoots = includeTaskRoots;
     }
 
     /**
@@ -645,9 +694,25 @@
         } else {
             classIndex = -1;
         }
+
+        final int taskRootPackageIndex;
+        if (mIncludeTaskRoots && event.mTaskRootPackage != null) {
+            taskRootPackageIndex = findStringIndex(event.mTaskRootPackage);
+        } else {
+            taskRootPackageIndex = -1;
+        }
+
+        final int taskRootClassIndex;
+        if (mIncludeTaskRoots && event.mTaskRootClass != null) {
+            taskRootClassIndex = findStringIndex(event.mTaskRootClass);
+        } else {
+            taskRootClassIndex = -1;
+        }
         p.writeInt(packageIndex);
         p.writeInt(classIndex);
         p.writeInt(event.mInstanceId);
+        p.writeInt(taskRootPackageIndex);
+        p.writeInt(taskRootClassIndex);
         p.writeInt(event.mEventType);
         p.writeLong(event.mTimeStamp);
 
@@ -691,6 +756,21 @@
             eventOut.mClass = null;
         }
         eventOut.mInstanceId = p.readInt();
+
+        final int taskRootPackageIndex = p.readInt();
+        if (taskRootPackageIndex >= 0) {
+            eventOut.mTaskRootPackage = mStringPool[taskRootPackageIndex];
+        } else {
+            eventOut.mTaskRootPackage = null;
+        }
+
+        final int taskRootClassIndex = p.readInt();
+        if (taskRootClassIndex >= 0) {
+            eventOut.mTaskRootClass = mStringPool[taskRootClassIndex];
+        } else {
+            eventOut.mTaskRootClass = null;
+        }
+
         eventOut.mEventType = p.readInt();
         eventOut.mTimeStamp = p.readLong();
 
diff --git a/core/java/android/app/usage/UsageStatsManager.java b/core/java/android/app/usage/UsageStatsManager.java
index 605deac..51397a2 100644
--- a/core/java/android/app/usage/UsageStatsManager.java
+++ b/core/java/android/app/usage/UsageStatsManager.java
@@ -22,6 +22,7 @@
 import android.annotation.RequiresPermission;
 import android.annotation.SystemApi;
 import android.annotation.SystemService;
+import android.annotation.TestApi;
 import android.annotation.UnsupportedAppUsage;
 import android.app.Activity;
 import android.app.PendingIntent;
@@ -234,6 +235,29 @@
     @SystemApi
     public static final String EXTRA_TIME_USED = "android.app.usage.extra.TIME_USED";
 
+
+    /**
+     * App usage observers will consider the task root package the source of usage.
+     * @hide
+     */
+    @SystemApi
+    public static final int USAGE_SOURCE_TASK_ROOT_ACTIVITY = 1;
+
+    /**
+     * App usage observers will consider the visible activity's package the source of usage.
+     * @hide
+     */
+    @SystemApi
+    public static final int USAGE_SOURCE_CURRENT_ACTIVITY = 2;
+
+    /** @hide */
+    @IntDef(prefix = { "USAGE_SOURCE_" }, value = {
+            USAGE_SOURCE_TASK_ROOT_ACTIVITY,
+            USAGE_SOURCE_CURRENT_ACTIVITY,
+    })
+    @Retention(RetentionPolicy.SOURCE)
+    public @interface UsageSource {}
+
     @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.P, trackingBug = 115609023)
     private static final UsageEvents sEmptyResults = new UsageEvents();
 
@@ -595,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.
@@ -658,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.
@@ -712,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
@@ -719,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.
@@ -742,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.
@@ -776,6 +870,38 @@
         }
     }
 
+    /**
+     * Get what App Usage Observers will consider the source of usage for an activity. Usage Source
+     * is decided at boot and will not change until next boot.
+     * @see #USAGE_SOURCE_TASK_ROOT_ACTIVITY
+     * @see #USAGE_SOURCE_CURRENT_ACTIVITY
+     *
+     * @throws SecurityException if the caller doesn't have the OBSERVE_APP_USAGE permission and
+     *                           is not the profile owner of this user.
+     * @hide
+     */
+    @SystemApi
+    public @UsageSource int getUsageSource() {
+        try {
+            return mService.getUsageSource();
+        } catch (RemoteException e) {
+            throw e.rethrowFromSystemServer();
+        }
+    }
+
+    /**
+     * Force the Usage Source be reread from global settings.
+     * @hide
+     */
+    @TestApi
+    public void forceUsageSourceSettingRead() {
+        try {
+            mService.forceUsageSourceSettingRead();
+        } catch (RemoteException e) {
+            throw e.rethrowFromSystemServer();
+        }
+    }
+
     /** @hide */
     public static String reasonToString(int standbyReason) {
         StringBuilder sb = new StringBuilder();
@@ -845,6 +971,22 @@
         return sb.toString();
     }
 
+    /** @hide */
+    public static String usageSourceToString(int usageSource) {
+        switch (usageSource) {
+            case USAGE_SOURCE_TASK_ROOT_ACTIVITY:
+                return "TASK_ROOT_ACTIVITY";
+            case USAGE_SOURCE_CURRENT_ACTIVITY:
+                return "CURRENT_ACTIVITY";
+            default:
+                StringBuilder sb = new StringBuilder();
+                sb.append("UNKNOWN(");
+                sb.append(usageSource);
+                sb.append(")");
+                return sb.toString();
+        }
+    }
+
     /**
      * {@hide}
      * Temporarily whitelist the specified app for a short duration. This is to allow an app
diff --git a/core/java/android/app/usage/UsageStatsManagerInternal.java b/core/java/android/app/usage/UsageStatsManagerInternal.java
index cc3ab00..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;
@@ -40,9 +41,11 @@
      *                  {@link UsageEvents}
      * @param instanceId For activity, hashCode of ActivityRecord's appToken.
      *                   For non-activity, it is not used.
+     * @param taskRoot For activity, the name of the package at the root of the task
+     *                 For non-activity, it is not used.
      */
     public abstract void reportEvent(ComponentName component, @UserIdInt int userId, int eventType,
-            int instanceId);
+            int instanceId, ComponentName taskRoot);
 
     /**
      * Reports an event to the UsageStatsManager.
@@ -268,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 1945b2f..ab8c196 100644
--- a/core/java/android/bluetooth/BluetoothAdapter.java
+++ b/core/java/android/bluetooth/BluetoothAdapter.java
@@ -36,6 +36,7 @@
 import android.content.Context;
 import android.os.BatteryStats;
 import android.os.Binder;
+import android.os.Handler;
 import android.os.IBinder;
 import android.os.ParcelUuid;
 import android.os.RemoteException;
@@ -648,6 +649,32 @@
 
     private final Object mLock = new Object();
     private final Map<LeScanCallback, ScanCallback> mLeScanClients;
+    private static final Map<BluetoothDevice, List<Pair<MetadataListener, Handler>>>
+                sMetadataListeners = new HashMap<>();
+
+    /**
+     * Bluetooth metadata listener. Overrides the default BluetoothMetadataListener
+     * implementation.
+     */
+    private static final IBluetoothMetadataListener sBluetoothMetadataListener =
+            new IBluetoothMetadataListener.Stub() {
+        @Override
+        public void onMetadataChanged(BluetoothDevice device, int key, String value) {
+            synchronized (sMetadataListeners) {
+                if (sMetadataListeners.containsKey(device)) {
+                    List<Pair<MetadataListener, Handler>> list = sMetadataListeners.get(device);
+                    for (Pair<MetadataListener, Handler> pair : list) {
+                        MetadataListener listener = pair.first;
+                        Handler handler = pair.second;
+                        handler.post(() -> {
+                            listener.onMetadataChanged(device, key, value);
+                        });
+                    }
+                }
+            }
+            return;
+        }
+    };
 
     /**
      * Get a handle to the default local Bluetooth adapter.
@@ -1873,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
@@ -2024,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) {
@@ -2441,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,
@@ -2498,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;
         }
@@ -2607,6 +2657,16 @@
                             }
                         }
                     }
+                    synchronized (sMetadataListeners) {
+                        sMetadataListeners.forEach((device, pair) -> {
+                            try {
+                                mService.registerMetadataListener(sBluetoothMetadataListener,
+                                        device);
+                            } catch (RemoteException e) {
+                                Log.e(TAG, "Failed to register metadata listener", e);
+                            }
+                        });
+                    }
                 }
 
                 public void onBluetoothServiceDown() {
@@ -3090,4 +3150,142 @@
                     + "listenUsingInsecureL2capChannel");
         return listenUsingInsecureL2capChannel();
     }
+
+    /**
+     * Register a {@link #MetadataListener} to receive update about metadata
+     * changes for this {@link BluetoothDevice}.
+     * Registration must be done when Bluetooth is ON and will last until
+     * {@link #unregisterMetadataListener(BluetoothDevice)} is called, even when Bluetooth
+     * restarted in the middle.
+     * All input parameters should not be null or {@link NullPointerException} will be triggered.
+     * The same {@link BluetoothDevice} and {@link #MetadataListener} pair can only be registered
+     * once, double registration would cause {@link IllegalArgumentException}.
+     *
+     * @param device {@link BluetoothDevice} that will be registered
+     * @param listener {@link #MetadataListener} that will receive asynchronous callbacks
+     * @param handler the handler for listener callback
+     * @return true on success, false on error
+     * @throws NullPointerException If one of {@code listener}, {@code device} or {@code handler}
+     * is null.
+     * @throws IllegalArgumentException The same {@link #MetadataListener} and
+     * {@link BluetoothDevice} are registered twice.
+     * @hide
+     */
+    @SystemApi
+    @RequiresPermission(Manifest.permission.BLUETOOTH_PRIVILEGED)
+    public boolean registerMetadataListener(BluetoothDevice device, MetadataListener listener,
+            Handler handler) {
+        if (DBG) Log.d(TAG, "registerMetdataListener()");
+
+        final IBluetooth service = mService;
+        if (service == null) {
+            Log.e(TAG, "Bluetooth is not enabled. Cannot register metadata listener");
+            return false;
+        }
+        if (listener == null) {
+            throw new NullPointerException("listener is null");
+        }
+        if (device == null) {
+            throw new NullPointerException("device is null");
+        }
+        if (handler == null) {
+            throw new NullPointerException("handler is null");
+        }
+
+        synchronized (sMetadataListeners) {
+            List<Pair<MetadataListener, Handler>> listenerList = sMetadataListeners.get(device);
+            if (listenerList == null) {
+                // Create new listener/handler list for registeration
+                listenerList = new ArrayList<>();
+                sMetadataListeners.put(device, listenerList);
+            } else {
+                // Check whether this device was already registed by the lisenter
+                if (listenerList.stream().anyMatch((pair) -> (pair.first.equals(listener)))) {
+                    throw new IllegalArgumentException("listener was already regestered"
+                            + " for the device");
+                }
+            }
+
+            Pair<MetadataListener, Handler> listenerPair = new Pair(listener, handler);
+            listenerList.add(listenerPair);
+
+            boolean ret = false;
+            try {
+                ret = service.registerMetadataListener(sBluetoothMetadataListener, device);
+            } catch (RemoteException e) {
+                Log.e(TAG, "registerMetadataListener fail", e);
+            } finally {
+                if (!ret) {
+                    // Remove listener registered earlier when fail.
+                    listenerList.remove(listenerPair);
+                    if (listenerList.isEmpty()) {
+                        // Remove the device if its listener list is empty
+                        sMetadataListeners.remove(device);
+                    }
+                }
+            }
+            return ret;
+        }
+    }
+
+    /**
+     * Unregister all {@link MetadataListener} from this {@link BluetoothDevice}.
+     * Unregistration can be done when Bluetooth is either ON or OFF.
+     * {@link #registerMetadataListener(MetadataListener, BluetoothDevice, Handler)} must
+     * be called before unregisteration.
+     * Unregistering a device that is not regestered would cause {@link IllegalArgumentException}.
+     *
+     * @param device {@link BluetoothDevice} that will be unregistered. it
+     * should not be null or {@link NullPointerException} will be triggered.
+     * @return true on success, false on error
+     * @throws NullPointerException If {@code device} is null.
+     * @throws IllegalArgumentException If {@code device} has not been registered before.
+     * @hide
+     */
+    @SystemApi
+    @RequiresPermission(Manifest.permission.BLUETOOTH_PRIVILEGED)
+    public boolean unregisterMetadataListener(BluetoothDevice device) {
+        if (DBG) Log.d(TAG, "unregisterMetdataListener()");
+        if (device == null) {
+            throw new NullPointerException("device is null");
+        }
+
+        synchronized (sMetadataListeners) {
+            if (sMetadataListeners.containsKey(device)) {
+                sMetadataListeners.remove(device);
+            } else {
+                throw new IllegalArgumentException("device was not registered");
+            }
+
+            final IBluetooth service = mService;
+            if (service == null) {
+                // Bluetooth is OFF, do nothing to Bluetooth service.
+                return true;
+            }
+            try {
+                return service.unregisterMetadataListener(device);
+            } catch (RemoteException e) {
+                Log.e(TAG, "unregisterMetadataListener fail", e);
+                return false;
+            }
+        }
+    }
+
+    /**
+     * This abstract class is used to implement {@link BluetoothAdapter} metadata listener.
+     * @hide
+     */
+    @SystemApi
+    public abstract static class MetadataListener {
+        /**
+         * Callback triggered if the metadata of {@link BluetoothDevice} registered in
+         * {@link #registerMetadataListener}.
+         *
+         * @param device changed {@link BluetoothDevice}.
+         * @param key changed metadata key, one of BluetoothDevice.METADATA_*.
+         * @param value the new value of metadata.
+         */
+        public void onMetadataChanged(BluetoothDevice device, int key, String value) {
+        }
+    }
 }
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 235dc5c..4d8dc35 100644
--- a/core/java/android/bluetooth/BluetoothDevice.java
+++ b/core/java/android/bluetooth/BluetoothDevice.java
@@ -341,6 +341,137 @@
             "android.bluetooth.device.action.SDP_RECORD";
 
     /**
+     * Maximum length of a metadata entry, this is to avoid exploding Bluetooth
+     * disk usage
+     * @hide
+     */
+    @SystemApi
+    public static final int METADATA_MAX_LENGTH = 2048;
+
+    /**
+     * Manufacturer name of this Bluetooth device
+     * @hide
+     */
+    @SystemApi
+    public static final int METADATA_MANUFACTURER_NAME = 0;
+
+    /**
+     * Model name of this Bluetooth device
+     * @hide
+     */
+    @SystemApi
+    public static final int METADATA_MODEL_NAME = 1;
+
+    /**
+     * Software version of this Bluetooth device
+     * @hide
+     */
+    @SystemApi
+    public static final int METADATA_SOFTWARE_VERSION = 2;
+
+    /**
+     * Hardware version of this Bluetooth device
+     * @hide
+     */
+    @SystemApi
+    public static final int METADATA_HARDWARE_VERSION = 3;
+
+    /**
+     * Package name of the companion app, if any
+     * @hide
+     */
+    @SystemApi
+    public static final int METADATA_COMPANION_APP = 4;
+
+    /**
+     * URI to the main icon shown on the settings UI
+     * @hide
+     */
+    @SystemApi
+    public static final int METADATA_MAIN_ICON = 5;
+
+    /**
+     * Whether this device is an untethered headset with left, right and case
+     * @hide
+     */
+    @SystemApi
+    public static final int METADATA_IS_UNTHETHERED_HEADSET = 6;
+
+    /**
+     * URI to icon of the left headset
+     * @hide
+     */
+    @SystemApi
+    public static final int METADATA_UNTHETHERED_LEFT_ICON = 7;
+
+    /**
+     * URI to icon of the right headset
+     * @hide
+     */
+    @SystemApi
+    public static final int METADATA_UNTHETHERED_RIGHT_ICON = 8;
+
+    /**
+     * URI to icon of the headset charging case
+     * @hide
+     */
+    @SystemApi
+    public static final int METADATA_UNTHETHERED_CASE_ICON = 9;
+
+    /**
+     * Battery level (0-100), {@link BluetoothDevice#BATTERY_LEVEL_UNKNOWN}
+     * is invalid, of the left headset
+     * @hide
+     */
+    @SystemApi
+    public static final int METADATA_UNTHETHERED_LEFT_BATTERY = 10;
+
+    /**
+     * Battery level (0-100), {@link BluetoothDevice#BATTERY_LEVEL_UNKNOWN}
+     * is invalid, of the right headset
+     * @hide
+     */
+    @SystemApi
+    public static final int METADATA_UNTHETHERED_RIGHT_BATTERY = 11;
+
+    /**
+     * Battery level (0-100), {@link BluetoothDevice#BATTERY_LEVEL_UNKNOWN}
+     * is invalid, of the headset charging case
+     * @hide
+     */
+    @SystemApi
+    public static final int METADATA_UNTHETHERED_CASE_BATTERY = 12;
+
+    /**
+     * Whether the left headset is charging
+     * @hide
+     */
+    @SystemApi
+    public static final int METADATA_UNTHETHERED_LEFT_CHARGING = 13;
+
+    /**
+     * Whether the right headset is charging
+     * @hide
+     */
+    @SystemApi
+    public static final int METADATA_UNTHETHERED_RIGHT_CHARGING = 14;
+
+    /**
+     * Whether the headset charging case is charging
+     * @hide
+     */
+    @SystemApi
+    public static final int METADATA_UNTHETHERED_CASE_CHARGING = 15;
+
+    /**
+     * URI to the enhanced settings UI slice, null or empty String means
+     * the UI does not exist
+     * @hide
+     */
+    @SystemApi
+    public static final int METADATA_ENHANCED_SETTINGS_UI_URI = 16;
+
+    /**
      * Broadcast Action: This intent is used to broadcast the {@link UUID}
      * wrapped as a {@link android.os.ParcelUuid} of the remote device after it
      * has been fetched. This intent is sent only when the UUIDs of the remote
@@ -401,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
@@ -1461,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}.
      *
@@ -2026,4 +2243,61 @@
         Log.e(TAG, "createL2capCocSocket: PLEASE USE THE OFFICIAL API, createInsecureL2capChannel");
         return createInsecureL2capChannel(psm);
     }
+
+    /**
+     * Set a keyed metadata of this {@link BluetoothDevice} to a
+     * {@link String} value.
+     * Only bonded devices's metadata will be persisted across Bluetooth
+     * restart.
+     * Metadata will be removed when the device's bond state is moved to
+     * {@link #BOND_NONE}.
+     *
+     * @param key must be within the list of BluetoothDevice.METADATA_*
+     * @param value the string data to set for key. Must be less than
+     * {@link BluetoothAdapter#METADATA_MAX_LENGTH} characters in length
+     * @return true on success, false on error
+     * @hide
+    */
+    @SystemApi
+    @RequiresPermission(Manifest.permission.BLUETOOTH_PRIVILEGED)
+    public boolean setMetadata(int key, String value) {
+        final IBluetooth service = sService;
+        if (service == null) {
+            Log.e(TAG, "Bluetooth is not enabled. Cannot set metadata");
+            return false;
+        }
+        if (value.length() > METADATA_MAX_LENGTH) {
+            throw new IllegalArgumentException("value length is " + value.length()
+                    + ", should not over " + METADATA_MAX_LENGTH);
+        }
+        try {
+            return service.setMetadata(this, key, value);
+        } catch (RemoteException e) {
+            Log.e(TAG, "setMetadata fail", e);
+            return false;
+        }
+    }
+
+    /**
+     * Get a keyed metadata for this {@link BluetoothDevice} as {@link String}
+     *
+     * @param key must be within the list of BluetoothDevice.METADATA_*
+     * @return Metadata of the key as string, null on error or not found
+     * @hide
+     */
+    @SystemApi
+    @RequiresPermission(Manifest.permission.BLUETOOTH_PRIVILEGED)
+    public String getMetadata(int key) {
+        final IBluetooth service = sService;
+        if (service == null) {
+            Log.e(TAG, "Bluetooth is not enabled. Cannot get metadata");
+            return null;
+        }
+        try {
+            return service.getMetadata(this, key);
+        } catch (RemoteException e) {
+            Log.e(TAG, "getMetadata fail", e);
+            return null;
+        }
+    }
 }
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 cefc700..280f1ac 100644
--- a/core/java/android/content/Context.java
+++ b/core/java/android/content/Context.java
@@ -771,7 +771,9 @@
      * <p>
      * This is not generally intended for third party application developers.
      */
-    public abstract String getOpPackageName();
+    public String getOpPackageName() {
+        throw new RuntimeException("Not implemented. Must override in a subclass.");
+    }
 
     /** Return the full application info for this context's package. */
     public abstract ApplicationInfo getApplicationInfo();
@@ -2980,9 +2982,11 @@
      *
      * @see #bindService
      */
-    public abstract boolean bindIsolatedService(@RequiresPermission Intent service,
+    public boolean bindIsolatedService(@RequiresPermission Intent service,
             @NonNull ServiceConnection conn, @BindServiceFlags int flags,
-            @NonNull String instanceName);
+            @NonNull String instanceName) {
+        throw new RuntimeException("Not implemented. Must override in a subclass.");
+    }
 
     /**
      * Same as {@link #bindService(Intent, ServiceConnection, int)}, but with an explicit userHandle
@@ -3037,8 +3041,10 @@
      *                   a related groups -- higher importance values will be killed before
      *                   lower ones.
      */
-    public abstract void updateServiceGroup(@NonNull ServiceConnection conn, int group,
-            int importance);
+    public void updateServiceGroup(@NonNull ServiceConnection conn, int group,
+            int importance) {
+        throw new RuntimeException("Not implemented. Must override in a subclass.");
+    }
 
     /**
      * Disconnect from an application service.  You will no longer receive
diff --git a/core/java/android/content/Intent.java b/core/java/android/content/Intent.java
index 8497656..9e7aaf6 100644
--- a/core/java/android/content/Intent.java
+++ b/core/java/android/content/Intent.java
@@ -2008,6 +2008,15 @@
             "android.intent.extra.PERMISSION_GROUP_NAME";
 
     /**
+     * Intent extra: The number of milliseconds.
+     * <p>
+     * Type: long
+     * </p>
+     */
+    public static final String EXTRA_DURATION_MILLIS =
+            "android.intent.extra.DURATION_MILLIS";
+
+    /**
      * Activity action: Launch UI to review app uses of permissions.
      * <p>
      * Input: {@link #EXTRA_PERMISSION_NAME} specifies the permission name
@@ -2020,11 +2029,16 @@
      * {@link #EXTRA_PERMISSION_NAME}.
      * </p>
      * <p>
+     * Input: {@link #EXTRA_DURATION_MILLIS} specifies the minimum number of milliseconds of recent
+     * activity to show (optional).  Must be non-negative.
+     * </p>
+     * <p>
      * Output: Nothing.
      * </p>
      *
      * @see #EXTRA_PERMISSION_NAME
      * @see #EXTRA_PERMISSION_GROUP_NAME
+     * @see #EXTRA_DURATION_MILLIS
      *
      * @hide
      */
@@ -2363,7 +2377,6 @@
     /**
      * Broadcast Action: An existing version of an application package has been
      * rolled back to a previous version.
-     * The data contains the name of the package.
      *
      * <p class="note">This is a protected intent that can only be sent
      * by the system.
@@ -3034,6 +3047,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
@@ -3192,7 +3212,18 @@
      *
      * <p class="note">This is a protected intent that can only be sent
      * by the system.
+     *
+     * <p class="note">If the user has chosen a {@link android.telecom.CallRedirectionService} to
+     * handle redirection of outgoing calls, this intent will NOT be sent as an ordered broadcast.
+     * This means that attempts to re-write the outgoing call by other apps using this intent will
+     * be ignored.
+     * </p>
+     *
+     * @deprecated Apps that redirect outgoing calls should use the
+     * {@link android.telecom.CallRedirectionService} API.  Apps that perform call screening
+     * should use the {@link android.telecom.CallScreeningService} API.
      */
+    @Deprecated
     @SdkConstant(SdkConstantType.BROADCAST_INTENT_ACTION)
     public static final String ACTION_NEW_OUTGOING_CALL =
             "android.intent.action.NEW_OUTGOING_CALL";
@@ -9945,9 +9976,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);
         }
@@ -9992,7 +10035,6 @@
         if (mSelector != null) {
             proto.write(IntentProto.SELECTOR, mSelector.toShortString(secure, comp, extras, clip));
         }
-        proto.end(token);
     }
 
     /**
diff --git a/core/java/android/content/om/OverlayManager.java b/core/java/android/content/om/OverlayManager.java
index 7a2220bf..8e72fa5 100644
--- a/core/java/android/content/om/OverlayManager.java
+++ b/core/java/android/content/om/OverlayManager.java
@@ -54,6 +54,7 @@
         this(context, IOverlayManager.Stub.asInterface(
             ServiceManager.getService(Context.OVERLAY_SERVICE)));
     }
+
     /**
      * Request that an overlay package is enabled and any other overlay packages with the same
      * target package and category are disabled.
@@ -75,6 +76,26 @@
     }
 
     /**
+     * Request that an overlay package is enabled.
+     *
+     * @param packageName the name of the overlay package to enable.
+     * @param enable {@code false} if the overlay should be turned off.
+     * @param userId The user for which to change the overlay.
+     * @return true if the system successfully registered the request, false otherwise.
+     *
+     * @hide
+     */
+    @SystemApi
+    public boolean setEnabled(@Nullable final String packageName, final boolean enable,
+            int userId) {
+        try {
+            return mService.setEnabled(packageName, enable, userId);
+        } catch (RemoteException e) {
+            throw e.rethrowFromSystemServer();
+        }
+    }
+
+    /**
      * Returns information about all overlays for the given target package for
      * the specified user. The returned list is ordered according to the
      * overlay priority with the highest priority at the end of the list.
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/tests/utils/SleepUtils/AlarmService/src/com/android/testing/alarmservice/Alarm.aidl b/core/java/android/content/pm/LauncherApps.aidl
similarity index 66%
copy from tests/utils/SleepUtils/AlarmService/src/com/android/testing/alarmservice/Alarm.aidl
copy to core/java/android/content/pm/LauncherApps.aidl
index 62a8c48..1d98ad1 100644
--- a/tests/utils/SleepUtils/AlarmService/src/com/android/testing/alarmservice/Alarm.aidl
+++ b/core/java/android/content/pm/LauncherApps.aidl
@@ -1,11 +1,11 @@
-/*
- * Copyright (C) 2013 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.
  * You may obtain a copy of the License at
  *
- *      http://www.apache.org/licenses/LICENSE-2.0
+ *     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,
@@ -14,10 +14,6 @@
  * limitations under the License.
  */
 
-package com.android.testing.alarmservice;
+package android.content.pm;
 
-interface Alarm {
-    int prepare();
-    int setAlarmAndWait(long timeoutMills);
-    int done();
-}
\ No newline at end of file
+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 b845673..783ee64 100644
--- a/core/java/android/content/pm/PackageManager.java
+++ b/core/java/android/content/pm/PackageManager.java
@@ -2060,6 +2060,14 @@
 
     /**
      * Feature for {@link #getSystemAvailableFeatures} and
+     * {@link #hasSystemFeature}: The device has a secure implementation of keyguard, meaning the
+     * device supports PIN, pattern and password as defined in Android CDD
+     */
+    @SdkConstant(SdkConstantType.FEATURE)
+    public static final String FEATURE_SECURE_LOCK_SCREEN = "android.software.secure_lock_screen";
+
+    /**
+     * Feature for {@link #getSystemAvailableFeatures} and
      * {@link #hasSystemFeature}: The device includes an accelerometer.
      */
     @SdkConstant(SdkConstantType.FEATURE)
@@ -2215,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.
@@ -5764,7 +5779,7 @@
      */
     @RequiresPermission(value = android.Manifest.permission.CHANGE_COMPONENT_ENABLED_STATE,
             conditional = true)
-    public abstract void setComponentEnabledSetting(ComponentName componentName,
+    public abstract void setComponentEnabledSetting(@NonNull ComponentName componentName,
             @EnabledState int newState, @EnabledFlags int flags);
 
     /**
@@ -5778,7 +5793,7 @@
      * @return Returns the current enabled state for the component.
      */
     public abstract @EnabledState int getComponentEnabledSetting(
-            ComponentName componentName);
+            @NonNull ComponentName componentName);
 
     /**
      * Set the enabled setting for an application
@@ -5793,7 +5808,7 @@
      */
     @RequiresPermission(value = android.Manifest.permission.CHANGE_COMPONENT_ENABLED_STATE,
             conditional = true)
-    public abstract void setApplicationEnabledSetting(String packageName,
+    public abstract void setApplicationEnabledSetting(@NonNull String packageName,
             @EnabledState int newState, @EnabledFlags int flags);
 
     /**
@@ -5807,7 +5822,7 @@
      * @return Returns the current enabled state for the application.
      * @throws IllegalArgumentException if the named package does not exist.
      */
-    public abstract @EnabledState int getApplicationEnabledSetting(String packageName);
+    public abstract @EnabledState int getApplicationEnabledSetting(@NonNull String packageName);
 
     /**
      * Flush the package restrictions for a given user to disk. This forces the package restrictions
diff --git a/core/java/android/content/pm/PackageManagerInternal.java b/core/java/android/content/pm/PackageManagerInternal.java
index c7320b0..c9a4c82 100644
--- a/core/java/android/content/pm/PackageManagerInternal.java
+++ b/core/java/android/content/pm/PackageManagerInternal.java
@@ -794,6 +794,12 @@
             "android.content.pm.extra.ENABLE_ROLLBACK_INSTALL_FLAGS";
 
     /**
+     * Extra field name for the set of installed users for a given rollback package.
+     */
+    public static final String EXTRA_ENABLE_ROLLBACK_INSTALLED_USERS =
+            "android.content.pm.extra.ENABLE_ROLLBACK_INSTALLED_USERS";
+
+    /**
      * Used as the {@code enableRollbackCode} argument for
      * {@link PackageManagerInternal#setEnableRollbackCode} to indicate that
      * enabling rollback succeeded.
@@ -827,4 +833,10 @@
      * Ask the package manager to compile layouts in the given package.
      */
     public abstract boolean compileLayouts(String packageName);
+
+    /*
+     * Inform the package manager that the pending package install identified by
+     * {@code token} can be completed.
+     */
+    public abstract void finishPackageInstall(int token, boolean didLaunch);
 }
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/rollback/IRollbackManager.aidl b/core/java/android/content/rollback/IRollbackManager.aidl
index 7f557cd..420bcb6 100644
--- a/core/java/android/content/rollback/IRollbackManager.aidl
+++ b/core/java/android/content/rollback/IRollbackManager.aidl
@@ -33,6 +33,12 @@
     void executeRollback(in RollbackInfo rollback, String callerPackageName,
             in IntentSender statusReceiver);
 
+    // Exposed for use from the system server only. Callback from the package
+    // manager during the install flow when user data can be restored for a given
+    // package.
+    void restoreUserData(String packageName, int userId, int appId, long ceDataInode,
+            String seInfo, int token);
+
     // Exposed for test purposes only.
     void reloadPersistedData();
 
diff --git a/core/java/android/content/rollback/PackageRollbackInfo.java b/core/java/android/content/rollback/PackageRollbackInfo.java
index 2040024..4644a83 100644
--- a/core/java/android/content/rollback/PackageRollbackInfo.java
+++ b/core/java/android/content/rollback/PackageRollbackInfo.java
@@ -17,11 +17,10 @@
 package android.content.rollback;
 
 import android.annotation.SystemApi;
+import android.content.pm.VersionedPackage;
 import android.os.Parcel;
 import android.os.Parcelable;
 
-import java.util.Objects;
-
 /**
  * Information about a rollback available for a particular package.
  *
@@ -29,59 +28,41 @@
  */
 @SystemApi
 public final class PackageRollbackInfo implements Parcelable {
-    /**
-     * The name of a package being rolled back.
-     */
-    public final String packageName;
+
+    private final VersionedPackage mVersionRolledBackFrom;
+    private final VersionedPackage mVersionRolledBackTo;
 
     /**
-     * The version the package was rolled back from.
+     * Returns the name of the package to roll back from.
      */
-    public final PackageVersion higherVersion;
-
-    /**
-     * The version the package was rolled back to.
-     */
-    public final PackageVersion lowerVersion;
-
-    /**
-     * Represents a version of a package.
-     */
-    public static class PackageVersion {
-        public final long versionCode;
-
-        // TODO(b/120200473): Include apk sha or some other way to distinguish
-        // between two different apks with the same version code.
-        public PackageVersion(long versionCode) {
-            this.versionCode = versionCode;
-        }
-
-        @Override
-        public boolean equals(Object other) {
-            if (other instanceof PackageVersion)  {
-                PackageVersion otherVersion = (PackageVersion) other;
-                return versionCode == otherVersion.versionCode;
-            }
-            return false;
-        }
-
-        @Override
-        public int hashCode() {
-            return Objects.hash(versionCode);
-        }
+    public String getPackageName() {
+        return mVersionRolledBackFrom.getPackageName();
     }
 
-    public PackageRollbackInfo(String packageName,
-            PackageVersion higherVersion, PackageVersion lowerVersion) {
-        this.packageName = packageName;
-        this.higherVersion = higherVersion;
-        this.lowerVersion = lowerVersion;
+    /**
+     * Returns the version of the package rolled back from.
+     */
+    public VersionedPackage getVersionRolledBackFrom() {
+        return mVersionRolledBackFrom;
+    }
+
+    /**
+     * Returns the version of the package rolled back to.
+     */
+    public VersionedPackage getVersionRolledBackTo() {
+        return mVersionRolledBackTo;
+    }
+
+    /** @hide */
+    public PackageRollbackInfo(VersionedPackage packageRolledBackFrom,
+            VersionedPackage packageRolledBackTo) {
+        this.mVersionRolledBackFrom = packageRolledBackFrom;
+        this.mVersionRolledBackTo = packageRolledBackTo;
     }
 
     private PackageRollbackInfo(Parcel in) {
-        this.packageName = in.readString();
-        this.higherVersion = new PackageVersion(in.readLong());
-        this.lowerVersion = new PackageVersion(in.readLong());
+        this.mVersionRolledBackFrom = VersionedPackage.CREATOR.createFromParcel(in);
+        this.mVersionRolledBackTo = VersionedPackage.CREATOR.createFromParcel(in);
     }
 
     @Override
@@ -91,9 +72,8 @@
 
     @Override
     public void writeToParcel(Parcel out, int flags) {
-        out.writeString(packageName);
-        out.writeLong(higherVersion.versionCode);
-        out.writeLong(lowerVersion.versionCode);
+        mVersionRolledBackFrom.writeToParcel(out, flags);
+        mVersionRolledBackTo.writeToParcel(out, flags);
     }
 
     public static final Parcelable.Creator<PackageRollbackInfo> CREATOR =
diff --git a/core/java/android/content/rollback/RollbackInfo.java b/core/java/android/content/rollback/RollbackInfo.java
index 66df4fe..0803a7c 100644
--- a/core/java/android/content/rollback/RollbackInfo.java
+++ b/core/java/android/content/rollback/RollbackInfo.java
@@ -30,6 +30,11 @@
 public final class RollbackInfo implements Parcelable {
 
     /**
+     * A unique identifier for the rollback.
+     */
+    private final int mRollbackId;
+
+    /**
      * The package that needs to be rolled back.
      */
     public final PackageRollbackInfo targetPackage;
@@ -40,12 +45,21 @@
     // staged installs is supported.
 
     /** @hide */
-    public RollbackInfo(PackageRollbackInfo targetPackage) {
+    public RollbackInfo(int rollbackId, PackageRollbackInfo targetPackage) {
+        this.mRollbackId = rollbackId;
         this.targetPackage = targetPackage;
     }
 
     private RollbackInfo(Parcel in) {
-        this.targetPackage = PackageRollbackInfo.CREATOR.createFromParcel(in);
+        mRollbackId = in.readInt();
+        targetPackage = PackageRollbackInfo.CREATOR.createFromParcel(in);
+    }
+
+    /**
+     * Returns a unique identifier for this rollback.
+     */
+    public int getRollbackId() {
+        return mRollbackId;
     }
 
     @Override
@@ -55,6 +69,7 @@
 
     @Override
     public void writeToParcel(Parcel out, int flags) {
+        out.writeInt(mRollbackId);
         targetPackage.writeToParcel(out, flags);
     }
 
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/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 70a9f08..27f0b04 100644
--- a/core/java/android/hardware/display/ColorDisplayManager.java
+++ b/core/java/android/hardware/display/ColorDisplayManager.java
@@ -17,18 +17,28 @@
 package android.hardware.display;
 
 import android.Manifest;
+import android.annotation.IntDef;
 import android.annotation.IntRange;
 import android.annotation.NonNull;
 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.
@@ -39,7 +49,120 @@
 @SystemService(Context.COLOR_DISPLAY_SERVICE)
 public final class ColorDisplayManager {
 
+    /**
+     * @hide
+     */
+    @Retention(RetentionPolicy.SOURCE)
+    @IntDef({CAPABILITY_NONE, CAPABILITY_PROTECTED_CONTENT, CAPABILITY_HARDWARE_ACCELERATION_GLOBAL,
+            CAPABILITY_HARDWARE_ACCELERATION_PER_APP})
+    public @interface CapabilityType {}
+
+    /**
+     * The device does not support color transforms.
+     *
+     * @hide
+     */
+    @SystemApi
+    public static final int CAPABILITY_NONE = 0x0;
+    /**
+     * The device can properly apply transforms over protected content.
+     *
+     * @hide
+     */
+    @SystemApi
+    public static final int CAPABILITY_PROTECTED_CONTENT = 0x1;
+    /**
+     * The device's hardware can efficiently apply transforms to the entire display.
+     *
+     * @hide
+     */
+    @SystemApi
+    public static final int CAPABILITY_HARDWARE_ACCELERATION_GLOBAL = 0x2;
+    /**
+     * The device's hardware can efficiently apply transforms to a specific Surface (window) so
+     * that apps can be transformed independently of one another.
+     *
+     * @hide
+     */
+    @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
@@ -49,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
@@ -96,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
@@ -114,6 +429,36 @@
         return context.getResources().getBoolean(R.bool.config_setColorTransformAccelerated);
     }
 
+    /**
+     * Returns the available software and hardware color transform capabilities of this device.
+     *
+     * @hide
+     */
+    @SystemApi
+    @RequiresPermission(Manifest.permission.CONTROL_DISPLAY_COLOR_TRANSFORMS)
+    public @CapabilityType int getTransformCapabilities() {
+        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;
@@ -139,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();
@@ -162,5 +595,29 @@
                 throw e.rethrowFromSystemServer();
             }
         }
+
+        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();
+            } catch (RemoteException e) {
+                throw e.rethrowFromSystemServer();
+            }
+        }
     }
 }
diff --git a/core/java/android/hardware/display/IColorDisplayManager.aidl b/core/java/android/hardware/display/IColorDisplayManager.aidl
index 644f510..30e76cf 100644
--- a/core/java/android/hardware/display/IColorDisplayManager.aidl
+++ b/core/java/android/hardware/display/IColorDisplayManager.aidl
@@ -16,10 +16,29 @@
 
 package android.hardware.display;
 
+import android.hardware.display.Time;
+
 /** @hide */
 interface IColorDisplayManager {
     boolean isDeviceColorManaged();
 
     boolean setSaturationLevel(int saturationLevel);
     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/tests/utils/SleepUtils/AlarmService/src/com/android/testing/alarmservice/Alarm.aidl b/core/java/android/hardware/display/Time.aidl
similarity index 74%
rename from tests/utils/SleepUtils/AlarmService/src/com/android/testing/alarmservice/Alarm.aidl
rename to core/java/android/hardware/display/Time.aidl
index 62a8c48..95cb563 100644
--- a/tests/utils/SleepUtils/AlarmService/src/com/android/testing/alarmservice/Alarm.aidl
+++ b/core/java/android/hardware/display/Time.aidl
@@ -1,5 +1,5 @@
 /*
- * Copyright (C) 2013 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.
@@ -14,10 +14,6 @@
  * limitations under the License.
  */
 
-package com.android.testing.alarmservice;
+package android.hardware.display;
 
-interface Alarm {
-    int prepare();
-    int setAlarmAndWait(long timeoutMills);
-    int done();
-}
\ No newline at end of file
+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/hdmi/HdmiUtils.java b/core/java/android/hardware/hdmi/HdmiUtils.java
index 3081738..8c94b78 100644
--- a/core/java/android/hardware/hdmi/HdmiUtils.java
+++ b/core/java/android/hardware/hdmi/HdmiUtils.java
@@ -1,5 +1,5 @@
 /*
- * Copyright (C) 2014 The Android Open Source Project
+ * 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.
@@ -16,14 +16,18 @@
 
 package android.hardware.hdmi;
 
+import android.annotation.IntDef;
+
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+
 /**
- * Various utilities to handle HDMI CEC messages.
+ * Various utilities related to HDMI CEC.
  *
  * TODO(b/110094868): unhide for Q
  * @hide
  */
-public class HdmiUtils {
-
+public final class HdmiUtils {
     /**
      * Return value of {@link #getLocalPortFromPhysicalAddress(int, int)}
      */
@@ -78,4 +82,164 @@
         }
         return port;
     }
+
+    /**
+     * TODO(b/110094868): unhide for Q
+     * @hide
+     */
+    @Retention(RetentionPolicy.SOURCE)
+    @IntDef({HDMI_RELATIVE_POSITION_UNKNOWN, HDMI_RELATIVE_POSITION_DIRECTLY_BELOW,
+            HDMI_RELATIVE_POSITION_BELOW, HDMI_RELATIVE_POSITION_SAME,
+            HDMI_RELATIVE_POSITION_DIRECTLY_ABOVE, HDMI_RELATIVE_POSITION_ABOVE,
+            HDMI_RELATIVE_POSITION_SIBLING, HDMI_RELATIVE_POSITION_DIFFERENT_BRANCH})
+    public @interface HdmiAddressRelativePosition {}
+    /**
+     * HDMI relative position is not determined.
+     * TODO(b/110094868): unhide for Q
+     * @hide
+     */
+    public static final int HDMI_RELATIVE_POSITION_UNKNOWN = 0;
+    /**
+     * HDMI relative position: directly blow the device.
+     * TODO(b/110094868): unhide for Q
+     * @hide
+     */
+    public static final int HDMI_RELATIVE_POSITION_DIRECTLY_BELOW = 1;
+    /**
+     * HDMI relative position: indirectly below the device.
+     * TODO(b/110094868): unhide for Q
+     * @hide
+     */
+    public static final int HDMI_RELATIVE_POSITION_BELOW = 2;
+    /**
+     * HDMI relative position: the same device.
+     * TODO(b/110094868): unhide for Q
+     * @hide
+     */
+    public static final int HDMI_RELATIVE_POSITION_SAME = 3;
+    /**
+     * HDMI relative position: directly above the device.
+     * TODO(b/110094868): unhide for Q
+     * @hide
+     */
+    public static final int HDMI_RELATIVE_POSITION_DIRECTLY_ABOVE = 4;
+    /**
+     * HDMI relative position: indirectly above the device.
+     * TODO(b/110094868): unhide for Q
+     * @hide
+     */
+    public static final int HDMI_RELATIVE_POSITION_ABOVE = 5;
+    /**
+     * HDMI relative position: directly below a same device.
+     * TODO(b/110094868): unhide for Q
+     * @hide
+     */
+    public static final int HDMI_RELATIVE_POSITION_SIBLING = 6;
+    /**
+     * HDMI relative position: different branch.
+     * TODO(b/110094868): unhide for Q
+     * @hide
+     */
+    public static final int HDMI_RELATIVE_POSITION_DIFFERENT_BRANCH = 7;
+
+    private static final int NPOS = -1;
+
+    /**
+     * Check if the given physical address is valid.
+     *
+     * @param address physical address
+     * @return {@code true} if the given address is valid
+     */
+    public static boolean isValidPhysicalAddress(int address) {
+        if (address < 0 || address >= 0xFFFF) {
+            return false;
+        }
+        int mask = 0xF000;
+        boolean hasZero = false;
+        for (int i = 0; i < 4; i++) {
+            if ((address & mask) == 0) {
+                hasZero = true;
+            } else if (hasZero) {
+                // only 0s are valid after a 0.
+                // e.g. 0x1012 is not valid.
+                return false;
+            }
+            mask >>= 4;
+        }
+        return true;
+    }
+
+
+    /**
+     * Returns the relative position of two physical addresses.
+     */
+    @HdmiAddressRelativePosition
+    public static int getHdmiAddressRelativePosition(int src, int dest) {
+        if (src == 0xFFFF || dest == 0xFFFF) {
+            // address not assigned
+            return HDMI_RELATIVE_POSITION_UNKNOWN;
+        }
+        try {
+            int firstDiffPos = physicalAddressFirstDifferentDigitPos(src, dest);
+            if (firstDiffPos == NPOS) {
+                return HDMI_RELATIVE_POSITION_SAME;
+            }
+            int mask = (0xF000 >> (firstDiffPos * 4));
+            int nextPos = firstDiffPos + 1;
+            if ((src & mask) == 0) {
+                // src is above dest
+                if (nextPos == 4) {
+                    // last digits are different
+                    return HDMI_RELATIVE_POSITION_DIRECTLY_ABOVE;
+                }
+                if (((0xF000 >> (nextPos * 4)) & dest) == 0) {
+                    // next digit is 0
+                    return HDMI_RELATIVE_POSITION_DIRECTLY_ABOVE;
+                }
+                return HDMI_RELATIVE_POSITION_ABOVE;
+            }
+
+            if ((dest & mask) == 0) {
+                // src is below dest
+                if (nextPos == 4) {
+                    // last digits are different
+                    return HDMI_RELATIVE_POSITION_DIRECTLY_BELOW;
+                }
+                if (((0xF000 >> (nextPos * 4)) & src) == 0) {
+                    // next digit is 0
+                    return HDMI_RELATIVE_POSITION_DIRECTLY_BELOW;
+                }
+                return HDMI_RELATIVE_POSITION_BELOW;
+            }
+            if (nextPos == 4) {
+                // last digits are different
+                return HDMI_RELATIVE_POSITION_SIBLING;
+            }
+            if (((0xF000 >> (nextPos * 4)) & src) == 0 && ((0xF000 >> (nextPos * 4)) & dest) == 0) {
+                return HDMI_RELATIVE_POSITION_SIBLING;
+            }
+            return HDMI_RELATIVE_POSITION_DIFFERENT_BRANCH;
+        } catch (IllegalArgumentException e) {
+            // invalid address
+            return HDMI_RELATIVE_POSITION_UNKNOWN;
+        }
+    }
+
+    private static int physicalAddressFirstDifferentDigitPos(int address1, int address2)
+            throws IllegalArgumentException {
+        if (!isValidPhysicalAddress(address1)) {
+            throw new IllegalArgumentException(address1 + " is not a valid address.");
+        }
+        if (!isValidPhysicalAddress(address2)) {
+            throw new IllegalArgumentException(address2 + " is not a valid address.");
+        }
+        int mask = 0xF000;
+        for (int i = 0; i < 4; i++) {
+            if ((address1 & mask) != (address2 & mask)) {
+                return i;
+            }
+            mask = mask >> 4;
+        }
+        return NPOS;
+    }
 }
diff --git a/tests/utils/SleepUtils/AlarmService/src/com/android/testing/alarmservice/Alarm.aidl b/core/java/android/hardware/location/ActivityChangedEvent.aidl
similarity index 69%
copy from tests/utils/SleepUtils/AlarmService/src/com/android/testing/alarmservice/Alarm.aidl
copy to core/java/android/hardware/location/ActivityChangedEvent.aidl
index 62a8c48..21f2445 100644
--- a/tests/utils/SleepUtils/AlarmService/src/com/android/testing/alarmservice/Alarm.aidl
+++ b/core/java/android/hardware/location/ActivityChangedEvent.aidl
@@ -1,5 +1,5 @@
 /*
- * Copyright (C) 2013 The Android Open Source Project
+ * Copyright (C) 2014 The Android Open Source Project
  *
  * Licensed under the Apache License, Version 2.0 (the "License");
  * you may not use this file except in compliance with the License.
@@ -11,13 +11,9 @@
  * 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.
+ * limitations under the License
  */
 
-package com.android.testing.alarmservice;
+package android.hardware.location;
 
-interface Alarm {
-    int prepare();
-    int setAlarmAndWait(long timeoutMills);
-    int done();
-}
\ No newline at end of file
+parcelable ActivityChangedEvent;
\ No newline at end of file
diff --git a/core/java/android/hardware/location/ActivityChangedEvent.java b/core/java/android/hardware/location/ActivityChangedEvent.java
new file mode 100644
index 0000000..16cfe6e
--- /dev/null
+++ b/core/java/android/hardware/location/ActivityChangedEvent.java
@@ -0,0 +1,92 @@
+/*
+ * Copyright (C) 2014 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License
+ */
+
+package android.hardware.location;
+
+import android.annotation.NonNull;
+import android.os.Parcel;
+import android.os.Parcelable;
+
+import java.security.InvalidParameterException;
+import java.util.Arrays;
+import java.util.List;
+
+/**
+ * A class representing an event for Activity changes.
+ *
+ * @hide
+ */
+public class ActivityChangedEvent implements Parcelable {
+    private final List<ActivityRecognitionEvent> mActivityRecognitionEvents;
+
+    public ActivityChangedEvent(ActivityRecognitionEvent[] activityRecognitionEvents) {
+        if (activityRecognitionEvents == null) {
+            throw new InvalidParameterException(
+                    "Parameter 'activityRecognitionEvents' must not be null.");
+        }
+
+        mActivityRecognitionEvents = Arrays.asList(activityRecognitionEvents);
+    }
+
+    @NonNull
+    public Iterable<ActivityRecognitionEvent> getActivityRecognitionEvents() {
+        return mActivityRecognitionEvents;
+    }
+
+    public static final Creator<ActivityChangedEvent> CREATOR =
+            new Creator<ActivityChangedEvent>() {
+        @Override
+        public ActivityChangedEvent createFromParcel(Parcel source) {
+            int activityRecognitionEventsLength = source.readInt();
+            ActivityRecognitionEvent[] activityRecognitionEvents =
+                    new ActivityRecognitionEvent[activityRecognitionEventsLength];
+            source.readTypedArray(activityRecognitionEvents, ActivityRecognitionEvent.CREATOR);
+
+            return new ActivityChangedEvent(activityRecognitionEvents);
+        }
+
+        @Override
+        public ActivityChangedEvent[] newArray(int size) {
+            return new ActivityChangedEvent[size];
+        }
+    };
+
+    @Override
+    public int describeContents() {
+        return 0;
+    }
+
+    @Override
+    public void writeToParcel(Parcel parcel, int flags) {
+        ActivityRecognitionEvent[] activityRecognitionEventArray =
+                mActivityRecognitionEvents.toArray(new ActivityRecognitionEvent[0]);
+        parcel.writeInt(activityRecognitionEventArray.length);
+        parcel.writeTypedArray(activityRecognitionEventArray, flags);
+    }
+
+    @Override
+    public String toString() {
+        StringBuilder builder = new StringBuilder("[ ActivityChangedEvent:");
+
+        for (ActivityRecognitionEvent event : mActivityRecognitionEvents) {
+            builder.append("\n    ");
+            builder.append(event.toString());
+        }
+        builder.append("\n]");
+
+        return builder.toString();
+    }
+}
diff --git a/core/java/android/hardware/location/ActivityRecognitionEvent.java b/core/java/android/hardware/location/ActivityRecognitionEvent.java
new file mode 100644
index 0000000..190030a
--- /dev/null
+++ b/core/java/android/hardware/location/ActivityRecognitionEvent.java
@@ -0,0 +1,87 @@
+/*
+ * Copyright (C) 2014 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.hardware.location;
+
+import android.os.Parcel;
+import android.os.Parcelable;
+
+/**
+ * A class that represents an Activity Recognition Event.
+ *
+ * @hide
+ */
+public class ActivityRecognitionEvent implements Parcelable {
+    private final String mActivity;
+    private final int mEventType;
+    private final long mTimestampNs;
+
+    public ActivityRecognitionEvent(String activity, int eventType, long timestampNs) {
+        mActivity = activity;
+        mEventType = eventType;
+        mTimestampNs = timestampNs;
+    }
+
+    public String getActivity() {
+        return mActivity;
+    }
+
+    public int getEventType() {
+        return mEventType;
+    }
+
+    public long getTimestampNs() {
+        return mTimestampNs;
+    }
+
+    public static final Creator<ActivityRecognitionEvent> CREATOR =
+            new Creator<ActivityRecognitionEvent>() {
+        @Override
+        public ActivityRecognitionEvent createFromParcel(Parcel source) {
+            String activity = source.readString();
+            int eventType = source.readInt();
+            long timestampNs = source.readLong();
+
+            return new ActivityRecognitionEvent(activity, eventType, timestampNs);
+        }
+
+        @Override
+        public ActivityRecognitionEvent[] newArray(int size) {
+            return new ActivityRecognitionEvent[size];
+        }
+    };
+
+    @Override
+    public int describeContents() {
+        return 0;
+    }
+
+    @Override
+    public void writeToParcel(Parcel parcel, int flags) {
+        parcel.writeString(mActivity);
+        parcel.writeInt(mEventType);
+        parcel.writeLong(mTimestampNs);
+    }
+
+    @Override
+    public String toString() {
+        return String.format(
+                "Activity='%s', EventType=%s, TimestampNs=%s",
+                mActivity,
+                mEventType,
+                mTimestampNs);
+    }
+}
diff --git a/core/java/android/hardware/location/ActivityRecognitionHardware.java b/core/java/android/hardware/location/ActivityRecognitionHardware.java
new file mode 100644
index 0000000..8acd1ff
--- /dev/null
+++ b/core/java/android/hardware/location/ActivityRecognitionHardware.java
@@ -0,0 +1,279 @@
+/*
+ * Copyright (C) 2014 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License
+ */
+
+package android.hardware.location;
+
+import android.Manifest;
+import android.content.Context;
+import android.os.RemoteCallbackList;
+import android.os.RemoteException;
+import android.text.TextUtils;
+import android.util.Log;
+
+/**
+ * A class that implements an {@link IActivityRecognitionHardware} backed up by the Activity
+ * Recognition HAL.
+ *
+ * @hide
+ */
+public class ActivityRecognitionHardware extends IActivityRecognitionHardware.Stub {
+    private static final String TAG = "ActivityRecognitionHW";
+    private static final boolean DEBUG = Log.isLoggable(TAG, Log.DEBUG);
+
+    private static final String HARDWARE_PERMISSION = Manifest.permission.LOCATION_HARDWARE;
+    private static final String ENFORCE_HW_PERMISSION_MESSAGE = "Permission '"
+            + HARDWARE_PERMISSION + "' not granted to access ActivityRecognitionHardware";
+
+    private static final int INVALID_ACTIVITY_TYPE = -1;
+    private static final int NATIVE_SUCCESS_RESULT = 0;
+    private static final int EVENT_TYPE_DISABLED = 0;
+    private static final int EVENT_TYPE_ENABLED = 1;
+
+    /**
+     * Contains the number of supported Event Types.
+     *
+     * NOTE: increment this counter every time a new EVENT_TYPE_ is added to
+     *       com.android.location.provider.ActivityRecognitionProvider
+     */
+    private static final int EVENT_TYPE_COUNT = 3;
+
+    private static ActivityRecognitionHardware sSingletonInstance;
+    private static final Object sSingletonInstanceLock = new Object();
+
+    private final Context mContext;
+    private final int mSupportedActivitiesCount;
+    private final String[] mSupportedActivities;
+    private final int[][] mSupportedActivitiesEnabledEvents;
+    private final SinkList mSinks = new SinkList();
+
+    private static class Event {
+        public int activity;
+        public int type;
+        public long timestamp;
+    }
+
+    private ActivityRecognitionHardware(Context context) {
+        nativeInitialize();
+
+        mContext = context;
+        mSupportedActivities = fetchSupportedActivities();
+        mSupportedActivitiesCount = mSupportedActivities.length;
+        mSupportedActivitiesEnabledEvents = new int[mSupportedActivitiesCount][EVENT_TYPE_COUNT];
+    }
+
+    public static ActivityRecognitionHardware getInstance(Context context) {
+        synchronized (sSingletonInstanceLock) {
+            if (sSingletonInstance == null) {
+                sSingletonInstance = new ActivityRecognitionHardware(context);
+            }
+
+            return sSingletonInstance;
+        }
+    }
+
+    public static boolean isSupported() {
+        return nativeIsSupported();
+    }
+
+    @Override
+    public String[] getSupportedActivities() {
+        checkPermissions();
+        return mSupportedActivities;
+    }
+
+    @Override
+    public boolean isActivitySupported(String activity) {
+        checkPermissions();
+        int activityType = getActivityType(activity);
+        return activityType != INVALID_ACTIVITY_TYPE;
+    }
+
+    @Override
+    public boolean registerSink(IActivityRecognitionHardwareSink sink) {
+        checkPermissions();
+        return mSinks.register(sink);
+    }
+
+    @Override
+    public boolean unregisterSink(IActivityRecognitionHardwareSink sink) {
+        checkPermissions();
+        return mSinks.unregister(sink);
+    }
+
+    @Override
+    public boolean enableActivityEvent(String activity, int eventType, long reportLatencyNs) {
+        checkPermissions();
+
+        int activityType = getActivityType(activity);
+        if (activityType == INVALID_ACTIVITY_TYPE) {
+            return false;
+        }
+
+        int result = nativeEnableActivityEvent(activityType, eventType, reportLatencyNs);
+        if (result == NATIVE_SUCCESS_RESULT) {
+            mSupportedActivitiesEnabledEvents[activityType][eventType] = EVENT_TYPE_ENABLED;
+            return true;
+        }
+        return false;
+    }
+
+    @Override
+    public boolean disableActivityEvent(String activity, int eventType) {
+        checkPermissions();
+
+        int activityType = getActivityType(activity);
+        if (activityType == INVALID_ACTIVITY_TYPE) {
+            return false;
+        }
+
+        int result = nativeDisableActivityEvent(activityType, eventType);
+        if (result == NATIVE_SUCCESS_RESULT) {
+            mSupportedActivitiesEnabledEvents[activityType][eventType] = EVENT_TYPE_DISABLED;
+            return true;
+        }
+        return false;
+    }
+
+    @Override
+    public boolean flush() {
+        checkPermissions();
+        int result = nativeFlush();
+        return result == NATIVE_SUCCESS_RESULT;
+    }
+
+    /**
+     * Called by the Activity-Recognition HAL.
+     */
+    private void onActivityChanged(Event[] events) {
+        if (events == null || events.length == 0) {
+            if (DEBUG) Log.d(TAG, "No events to broadcast for onActivityChanged.");
+            return;
+        }
+
+        int eventsLength = events.length;
+        ActivityRecognitionEvent activityRecognitionEventArray[] =
+                new ActivityRecognitionEvent[eventsLength];
+        for (int i = 0; i < eventsLength; ++i) {
+            Event event = events[i];
+            String activityName = getActivityName(event.activity);
+            activityRecognitionEventArray[i] =
+                    new ActivityRecognitionEvent(activityName, event.type, event.timestamp);
+        }
+        ActivityChangedEvent activityChangedEvent =
+                new ActivityChangedEvent(activityRecognitionEventArray);
+
+        int size = mSinks.beginBroadcast();
+        for (int i = 0; i < size; ++i) {
+            IActivityRecognitionHardwareSink sink = mSinks.getBroadcastItem(i);
+            try {
+                sink.onActivityChanged(activityChangedEvent);
+            } catch (RemoteException e) {
+                Log.e(TAG, "Error delivering activity changed event.", e);
+            }
+        }
+        mSinks.finishBroadcast();
+    }
+
+    private String getActivityName(int activityType) {
+        if (activityType < 0 || activityType >= mSupportedActivities.length) {
+            String message = String.format(
+                    "Invalid ActivityType: %d, SupportedActivities: %d",
+                    activityType,
+                    mSupportedActivities.length);
+            Log.e(TAG, message);
+            return null;
+        }
+
+        return mSupportedActivities[activityType];
+    }
+
+    private int getActivityType(String activity) {
+        if (TextUtils.isEmpty(activity)) {
+            return INVALID_ACTIVITY_TYPE;
+        }
+
+        int supportedActivitiesLength = mSupportedActivities.length;
+        for (int i = 0; i < supportedActivitiesLength; ++i) {
+            if (activity.equals(mSupportedActivities[i])) {
+                return i;
+            }
+        }
+
+        return INVALID_ACTIVITY_TYPE;
+    }
+
+    private void checkPermissions() {
+        mContext.enforceCallingPermission(HARDWARE_PERMISSION, ENFORCE_HW_PERMISSION_MESSAGE);
+    }
+
+    private String[] fetchSupportedActivities() {
+        String[] supportedActivities = nativeGetSupportedActivities();
+        if (supportedActivities != null) {
+            return supportedActivities;
+        }
+
+        return new String[0];
+    }
+
+    private class SinkList extends RemoteCallbackList<IActivityRecognitionHardwareSink> {
+        @Override
+        public void onCallbackDied(IActivityRecognitionHardwareSink callback) {
+            int callbackCount = mSinks.getRegisteredCallbackCount();
+            if (DEBUG) Log.d(TAG, "RegisteredCallbackCount: " + callbackCount);
+            if (callbackCount != 0) {
+                return;
+            }
+            // currently there is only one client for this, so if all its sinks have died, we clean
+            // up after them, this ensures that the AR HAL is not out of sink
+            for (int activity = 0; activity < mSupportedActivitiesCount; ++activity) {
+                for (int event = 0; event < EVENT_TYPE_COUNT; ++event) {
+                    disableActivityEventIfEnabled(activity, event);
+                }
+            }
+        }
+
+        private void disableActivityEventIfEnabled(int activityType, int eventType) {
+            if (mSupportedActivitiesEnabledEvents[activityType][eventType] != EVENT_TYPE_ENABLED) {
+                return;
+            }
+
+            int result = nativeDisableActivityEvent(activityType, eventType);
+            mSupportedActivitiesEnabledEvents[activityType][eventType] = EVENT_TYPE_DISABLED;
+            String message = String.format(
+                    "DisableActivityEvent: activityType=%d, eventType=%d, result=%d",
+                    activityType,
+                    eventType,
+                    result);
+            Log.e(TAG, message);
+        }
+    }
+
+    // native bindings
+    static { nativeClassInit(); }
+
+    private static native void nativeClassInit();
+    private static native boolean nativeIsSupported();
+
+    private native void nativeInitialize();
+    private native void nativeRelease();
+    private native String[] nativeGetSupportedActivities();
+    private native int nativeEnableActivityEvent(
+            int activityType,
+            int eventType,
+            long reportLatenceNs);
+    private native int nativeDisableActivityEvent(int activityType, int eventType);
+    private native int nativeFlush();
+}
diff --git a/core/java/android/hardware/location/IActivityRecognitionHardware.aidl b/core/java/android/hardware/location/IActivityRecognitionHardware.aidl
new file mode 100644
index 0000000..bc6b183
--- /dev/null
+++ b/core/java/android/hardware/location/IActivityRecognitionHardware.aidl
@@ -0,0 +1,62 @@
+/*
+ * Copyright (C) 2014, The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/license/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.location;
+
+import android.hardware.location.IActivityRecognitionHardwareSink;
+
+/**
+ * Activity Recognition Hardware provider interface.
+ * This interface can be used to implement hardware based activity recognition.
+ *
+ * @hide
+ */
+interface IActivityRecognitionHardware {
+    /**
+     * Gets an array of supported activities by hardware.
+     */
+    String[] getSupportedActivities();
+
+    /**
+     * Returns true if the given activity is supported, false otherwise.
+     */
+    boolean isActivitySupported(in String activityType);
+
+    /**
+     * Registers a sink with Hardware Activity-Recognition.
+     */
+    boolean registerSink(in IActivityRecognitionHardwareSink sink);
+
+    /**
+     * Unregisters a sink with Hardware Activity-Recognition.
+     */
+    boolean unregisterSink(in IActivityRecognitionHardwareSink sink);
+
+    /**
+     * Enables tracking of a given activity/event type, if the activity is supported.
+     */
+    boolean enableActivityEvent(in String activityType, int eventType, long reportLatencyNs);
+
+    /**
+     * Disables tracking of a given activity/eventy type.
+     */
+    boolean disableActivityEvent(in String activityType, int eventType);
+
+    /**
+     * Requests hardware for all the activity events detected up to the given point in time.
+     */
+    boolean flush();
+}
\ No newline at end of file
diff --git a/core/java/android/hardware/location/IActivityRecognitionHardwareClient.aidl b/core/java/android/hardware/location/IActivityRecognitionHardwareClient.aidl
new file mode 100644
index 0000000..3fe645c
--- /dev/null
+++ b/core/java/android/hardware/location/IActivityRecognitionHardwareClient.aidl
@@ -0,0 +1,36 @@
+/*
+ * Copyright (C) 2015, 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/license/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.location;
+
+import android.hardware.location.IActivityRecognitionHardware;
+
+/**
+ * Activity Recognition Hardware client interface.
+ * This interface can be used to receive interfaces to implementations of
+ * {@link IActivityRecognitionHardware}.
+ *
+ * @hide
+ */
+oneway interface IActivityRecognitionHardwareClient {
+    /**
+     * Hardware Activity-Recognition availability event.
+     *
+     * @param isSupported whether the platform has hardware support for the feature
+     * @param instance the available instance to provide access to the feature
+     */
+    void onAvailabilityChanged(in boolean isSupported, in IActivityRecognitionHardware instance);
+}
diff --git a/core/java/android/hardware/location/IActivityRecognitionHardwareSink.aidl b/core/java/android/hardware/location/IActivityRecognitionHardwareSink.aidl
new file mode 100644
index 0000000..21c8e87
--- /dev/null
+++ b/core/java/android/hardware/location/IActivityRecognitionHardwareSink.aidl
@@ -0,0 +1,32 @@
+/*
+ * Copyright (C) 2014, The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/license/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.location;
+
+import android.hardware.location.ActivityChangedEvent;
+
+/**
+ * Activity Recognition Hardware provider Sink interface.
+ * This interface can be used to implement sinks to receive activity notifications.
+ *
+ * @hide
+ */
+interface IActivityRecognitionHardwareSink {
+    /**
+     * Activity changed event.
+     */
+    void onActivityChanged(in ActivityChangedEvent event);
+}
\ No newline at end of file
diff --git a/core/java/android/hardware/location/IActivityRecognitionHardwareWatcher.aidl b/core/java/android/hardware/location/IActivityRecognitionHardwareWatcher.aidl
new file mode 100644
index 0000000..12e3117
--- /dev/null
+++ b/core/java/android/hardware/location/IActivityRecognitionHardwareWatcher.aidl
@@ -0,0 +1,34 @@
+/*
+ * Copyright (C) 2014, The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/license/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.location;
+
+import android.hardware.location.IActivityRecognitionHardware;
+
+/**
+ * Activity Recognition Hardware watcher. This interface can be used to receive interfaces to
+ * implementations of {@link IActivityRecognitionHardware}.
+ *
+ * @deprecated use {@link IActivityRecognitionHardwareClient} instead.
+
+ * @hide
+ */
+interface IActivityRecognitionHardwareWatcher {
+    /**
+     * Hardware Activity-Recognition availability event.
+     */
+    void onInstanceChanged(in IActivityRecognitionHardware instance);
+}
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 d3509d5..5b3ad77 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,27 @@
         @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);
+                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 +621,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) {
+                    // TODO: notify visibility to insets consumer.
+                    if (DEBUG) {
+                        Log.v(TAG, "Making IME window visible");
+                    }
+                    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 +1015,7 @@
 
     void initViews() {
         mInitialized = false;
-        mWindowCreated = false;
+        mViewsCreated = false;
         mShowInputRequested = false;
         mShowInputFlags = 0;
 
@@ -1046,7 +1089,7 @@
     }
 
     private void resetStateForNewConfiguration() {
-        boolean visible = mWindowVisible;
+        boolean visible = mDecorViewVisible;
         int showFlags = mShowInputFlags;
         boolean showingInput = mShowInputRequested;
         CompletionInfo[] completions = mCurCompletions;
@@ -1129,7 +1172,7 @@
             return;
         }
         mBackDisposition = disposition;
-        setImeWindowStatus(mapToImeWindowStatus(isInputViewShown()), mBackDisposition);
+        setImeWindowStatus(mapToImeWindowStatus(), mBackDisposition);
     }
 
     /**
@@ -1380,7 +1423,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 +1482,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 +1501,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 +1542,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 +1847,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 +1858,42 @@
             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();
+        }
+        mDecorViewWasVisible = true;
         mInShowWindow = false;
     }
 
-    void showWindowInner(boolean showInput) {
+    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 +1904,8 @@
         updateFullscreenMode();
         updateInputViewShown();
 
-        if (!mWindowCreated) {
-            mWindowCreated = true;
+        if (!mViewsCreated) {
+            mViewsCreated = true;
             initialize();
             if (DEBUG) Log.v(TAG, "CALL: onCreateCandidatesView");
             View v = onCreateCandidatesView();
@@ -1846,6 +1914,10 @@
                 setCandidatesView(v);
             }
         }
+        return doShowInput;
+    }
+
+    private void startViews(boolean doShowInput) {
         if (mShowInputRequested) {
             if (!mInputViewStarted) {
                 if (DEBUG) Log.v(TAG, "CALL: onStartInputView");
@@ -1857,29 +1929,26 @@
             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() {
+    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 +1960,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 +2028,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 +2049,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 +2060,31 @@
                 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();
+            mDecorViewWasVisible = true;
+            mInShowWindow = false;
+        } else {
+            mIsPreRendered = false;
         }
     }
     
@@ -2087,7 +2177,14 @@
      * protocol, so applications with custom text editing written before this method appeared will
      * not call to inform the IME of this interaction.
      * @param focusChanged true if the user changed the focused view by this click.
+     * @see InputMethodManager#viewClicked(View)
+     * @deprecated The method may not be called for composite {@link View} that works as a giant
+     *             "Canvas", which can host its own UI hierarchy and sub focus state.
+     *             {@link android.webkit.WebView} is a good example. Application / IME developers
+     *             should not rely on this method. If your goal is just being notified when an
+     *             on-going input is interrupted, simply monitor {@link #onFinishInput()}.
      */
+    @Deprecated
     public void onViewClicked(boolean focusChanged) {
         // Intentionally empty
     }
@@ -2146,7 +2243,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.
@@ -2173,7 +2270,6 @@
         return mExtractEditText;
     }
 
-
     /**
      * Called back when a {@link KeyEvent} is forwarded from the target application.
      *
@@ -2886,8 +2982,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);
     }
 
     /**
@@ -2897,9 +2996,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);
@@ -2919,6 +3019,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/CaptivePortal.java b/core/java/android/net/CaptivePortal.java
index 4047068..3b01266 100644
--- a/core/java/android/net/CaptivePortal.java
+++ b/core/java/android/net/CaptivePortal.java
@@ -45,6 +45,8 @@
     private final IBinder mBinder;
 
     /** @hide */
+    @SystemApi
+    @TestApi
     public CaptivePortal(IBinder binder) {
         mBinder = binder;
     }
@@ -107,6 +109,8 @@
      * connectivity for apps because the captive portal is still in place.
      * @hide
      */
+    @SystemApi
+    @TestApi
     public void useNetwork() {
         try {
             ICaptivePortal.Stub.asInterface(mBinder).appResponse(APP_RETURN_WANTED_AS_IS);
diff --git a/core/java/android/net/ConnectivityManager.java b/core/java/android/net/ConnectivityManager.java
index cee3a40..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.
@@ -3699,6 +3766,19 @@
     }
 
     /**
+     * Determine whether the device is configured to avoid bad wifi.
+     * @hide
+     */
+    @SystemApi
+    public boolean getAvoidBadWifi() {
+        try {
+            return mService.getAvoidBadWifi();
+        } catch (RemoteException e) {
+            throw e.rethrowFromSystemServer();
+        }
+    }
+
+    /**
      * It is acceptable to briefly use multipath data to provide seamless connectivity for
      * time-sensitive user-facing operations when the system default network is temporarily
      * unresponsive. The amount of data should be limited (less than one megabyte for every call to
diff --git a/core/java/android/net/DnsPacket.java b/core/java/android/net/DnsPacket.java
new file mode 100644
index 0000000..458fb34
--- /dev/null
+++ b/core/java/android/net/DnsPacket.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 android.net;
+
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.text.TextUtils;
+
+import com.android.internal.util.BitUtils;
+
+import java.nio.BufferUnderflowException;
+import java.nio.ByteBuffer;
+import java.text.DecimalFormat;
+import java.text.FieldPosition;
+import java.util.ArrayList;
+import java.util.List;
+import java.util.StringJoiner;
+
+/**
+ * Defines basic data for DNS protocol based on RFC 1035.
+ * Subclasses create the specific format used in DNS packet.
+ *
+ * @hide
+ */
+public abstract class DnsPacket {
+    public class DnsHeader {
+        private static final String TAG = "DnsHeader";
+        public final int id;
+        public final int flags;
+        public final int rcode;
+        private final int[] mSectionCount;
+
+        /**
+         * Create a new DnsHeader from a positioned ByteBuffer.
+         *
+         * The ByteBuffer must be in network byte order (which is the default).
+         * Reads the passed ByteBuffer from its current position and decodes a DNS header.
+         * When this constructor returns, the reading position of the ByteBuffer has been
+         * advanced to the end of the DNS header record.
+         * This is meant to chain with other methods reading a DNS response in sequence.
+         *
+         */
+        DnsHeader(@NonNull ByteBuffer buf) throws BufferUnderflowException {
+            id = BitUtils.uint16(buf.getShort());
+            flags = BitUtils.uint16(buf.getShort());
+            rcode = flags & 0xF;
+            mSectionCount = new int[NUM_SECTIONS];
+            for (int i = 0; i < NUM_SECTIONS; ++i) {
+                mSectionCount[i] = BitUtils.uint16(buf.getShort());
+            }
+        }
+
+        /**
+         * Get section count by section type.
+         */
+        public int getSectionCount(int sectionType) {
+            return mSectionCount[sectionType];
+        }
+    }
+
+    public class DnsSection {
+        private static final int MAXNAMESIZE = 255;
+        private static final int MAXLABELSIZE = 63;
+        private static final int MAXLABELCOUNT = 128;
+        private static final int NAME_NORMAL = 0;
+        private static final int NAME_COMPRESSION = 0xC0;
+        private final DecimalFormat byteFormat = new DecimalFormat();
+        private final FieldPosition pos = new FieldPosition(0);
+
+        private static final String TAG = "DnsSection";
+
+        public final String dName;
+        public final int nsType;
+        public final int nsClass;
+        public final long ttl;
+        private final byte[] mRR;
+
+        /**
+         * Create a new DnsSection from a positioned ByteBuffer.
+         *
+         * The ByteBuffer must be in network byte order (which is the default).
+         * Reads the passed ByteBuffer from its current position and decodes a DNS section.
+         * When this constructor returns, the reading position of the ByteBuffer has been
+         * advanced to the end of the DNS header record.
+         * This is meant to chain with other methods reading a DNS response in sequence.
+         *
+         */
+        DnsSection(int sectionType, @NonNull ByteBuffer buf)
+                throws BufferUnderflowException, ParseException {
+            dName = parseName(buf, 0 /* Parse depth */);
+            if (dName.length() > MAXNAMESIZE) {
+                throw new ParseException("Parse name fail, name size is too long");
+            }
+            nsType = BitUtils.uint16(buf.getShort());
+            nsClass = BitUtils.uint16(buf.getShort());
+
+            if (sectionType != QDSECTION) {
+                ttl = BitUtils.uint32(buf.getInt());
+                final int length = BitUtils.uint16(buf.getShort());
+                mRR = new byte[length];
+                buf.get(mRR);
+            } else {
+                ttl = 0;
+                mRR = null;
+            }
+        }
+
+        /**
+         * Get a copy of rr.
+         */
+        @Nullable public byte[] getRR() {
+            return (mRR == null) ? null : mRR.clone();
+        }
+
+        /**
+         * Convert label from {@code byte[]} to {@code String}
+         *
+         * It follows the same converting rule as native layer.
+         * (See ns_name.c in libc)
+         *
+         */
+        private String labelToString(@NonNull byte[] label) {
+            final StringBuffer sb = new StringBuffer();
+            for (int i = 0; i < label.length; ++i) {
+                int b = BitUtils.uint8(label[i]);
+                // Control characters and non-ASCII characters.
+                if (b <= 0x20 || b >= 0x7f) {
+                    sb.append('\\');
+                    byteFormat.format(b, sb, pos);
+                } else if (b == '"' || b == '.' || b == ';' || b == '\\'
+                        || b == '(' || b == ')' || b == '@' || b == '$') {
+                    sb.append('\\');
+                    sb.append((char) b);
+                } else {
+                    sb.append((char) b);
+                }
+            }
+            return sb.toString();
+        }
+
+        private String parseName(@NonNull ByteBuffer buf, int depth) throws
+                BufferUnderflowException, ParseException {
+            if (depth > MAXLABELCOUNT) throw new ParseException("Parse name fails, too many labels");
+            final int len = BitUtils.uint8(buf.get());
+            final int mask = len & NAME_COMPRESSION;
+            if (0 == len) {
+                return "";
+            } else if (mask != NAME_NORMAL && mask != NAME_COMPRESSION) {
+                throw new ParseException("Parse name fail, bad label type");
+            } else if (mask == NAME_COMPRESSION) {
+                // Name compression based on RFC 1035 - 4.1.4 Message compression
+                final int offset = ((len & ~NAME_COMPRESSION) << 8) + BitUtils.uint8(buf.get());
+                final int oldPos = buf.position();
+                if (offset >= oldPos - 2) {
+                    throw new ParseException("Parse compression name fail, invalid compression");
+                }
+                buf.position(offset);
+                final String pointed = parseName(buf, depth + 1);
+                buf.position(oldPos);
+                return pointed;
+            } else {
+                final byte[] label = new byte[len];
+                buf.get(label);
+                final String head = labelToString(label);
+                if (head.length() > MAXLABELSIZE) {
+                    throw new ParseException("Parse name fail, invalid label length");
+                }
+                final String tail = parseName(buf, depth + 1);
+                return TextUtils.isEmpty(tail) ? head : head + "." + tail;
+            }
+        }
+    }
+
+    public static final int QDSECTION = 0;
+    public static final int ANSECTION = 1;
+    public static final int NSSECTION = 2;
+    public static final int ARSECTION = 3;
+    private static final int NUM_SECTIONS = ARSECTION + 1;
+
+    private static final String TAG = DnsPacket.class.getSimpleName();
+
+    protected final DnsHeader mHeader;
+    protected final List<DnsSection>[] mSections;
+
+    public static class ParseException extends Exception {
+        public ParseException(String msg) {
+            super(msg);
+        }
+
+        public ParseException(String msg, Throwable cause) {
+            super(msg, cause);
+        }
+    }
+
+    protected DnsPacket(@NonNull byte[] data) throws ParseException {
+        if (null == data) throw new ParseException("Parse header failed, null input data");
+        final ByteBuffer buffer;
+        try {
+            buffer = ByteBuffer.wrap(data);
+            mHeader = new DnsHeader(buffer);
+        } catch (BufferUnderflowException e) {
+            throw new ParseException("Parse Header fail, bad input data", e);
+        }
+
+        mSections = new ArrayList[NUM_SECTIONS];
+
+        for (int i = 0; i < NUM_SECTIONS; ++i) {
+            final int count = mHeader.getSectionCount(i);
+            if (count > 0) {
+                mSections[i] = new ArrayList(count);
+            }
+            for (int j = 0; j < count; ++j) {
+                try {
+                    mSections[i].add(new DnsSection(i, buffer));
+                } catch (BufferUnderflowException e) {
+                    throw new ParseException("Parse section fail", e);
+                }
+            }
+        }
+    }
+}
diff --git a/core/java/android/net/DnsResolver.java b/core/java/android/net/DnsResolver.java
new file mode 100644
index 0000000..6d54264
--- /dev/null
+++ b/core/java/android/net/DnsResolver.java
@@ -0,0 +1,289 @@
+/*
+ * 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 static android.net.NetworkUtils.resNetworkQuery;
+import static android.net.NetworkUtils.resNetworkResult;
+import static android.net.NetworkUtils.resNetworkSend;
+import static android.os.MessageQueue.OnFileDescriptorEventListener.EVENT_ERROR;
+import static android.os.MessageQueue.OnFileDescriptorEventListener.EVENT_INPUT;
+
+import android.annotation.IntDef;
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.os.Handler;
+import android.os.MessageQueue;
+import android.system.ErrnoException;
+import android.util.Log;
+
+import java.io.FileDescriptor;
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+import java.net.InetAddress;
+import java.net.UnknownHostException;
+import java.util.ArrayList;
+import java.util.List;
+import java.util.function.Consumer;
+
+
+/**
+ * Dns resolver class for asynchronous dns querying
+ *
+ */
+public final class DnsResolver {
+    private static final String TAG = "DnsResolver";
+    private static final int FD_EVENTS = EVENT_INPUT | EVENT_ERROR;
+    private static final int MAXPACKET = 8 * 1024;
+
+    @IntDef(prefix = { "CLASS_" }, value = {
+            CLASS_IN
+    })
+    @Retention(RetentionPolicy.SOURCE)
+    @interface QueryClass {}
+    public static final int CLASS_IN = 1;
+
+    @IntDef(prefix = { "TYPE_" },  value = {
+            TYPE_A,
+            TYPE_AAAA
+    })
+    @Retention(RetentionPolicy.SOURCE)
+    @interface QueryType {}
+    public static final int TYPE_A = 1;
+    public static final int TYPE_AAAA = 28;
+
+    @IntDef(prefix = { "FLAG_" }, value = {
+            FLAG_EMPTY,
+            FLAG_NO_RETRY,
+            FLAG_NO_CACHE_STORE,
+            FLAG_NO_CACHE_LOOKUP
+    })
+    @Retention(RetentionPolicy.SOURCE)
+    @interface QueryFlag {}
+    public static final int FLAG_EMPTY = 0;
+    public static final int FLAG_NO_RETRY = 1 << 0;
+    public static final int FLAG_NO_CACHE_STORE = 1 << 1;
+    public static final int FLAG_NO_CACHE_LOOKUP = 1 << 2;
+
+    private static final int DNS_RAW_RESPONSE = 1;
+
+    private static final int NETID_UNSET = 0;
+
+    private static final DnsResolver sInstance = new DnsResolver();
+
+    /**
+     * listener for receiving raw answers
+     */
+    public interface RawAnswerListener {
+        /**
+         * {@code byte[]} is {@code null} if query timed out
+         */
+        void onAnswer(@Nullable byte[] answer);
+    }
+
+    /**
+     * listener for receiving parsed answers
+     */
+    public interface InetAddressAnswerListener {
+        /**
+         * Will be called exactly once with all the answers to the query.
+         * size of addresses will be zero if no available answer could be parsed.
+         */
+        void onAnswer(@NonNull List<InetAddress> addresses);
+    }
+
+    /**
+     * Get instance for DnsResolver
+     */
+    public static DnsResolver getInstance() {
+        return sInstance;
+    }
+
+    private DnsResolver() {}
+
+    /**
+     * Pass in a blob and corresponding setting,
+     * get a blob back asynchronously with the entire raw answer.
+     *
+     * @param network {@link Network} specifying which network for querying.
+     *         {@code null} for query on default network.
+     * @param query blob message
+     * @param flags flags as a combination of the FLAGS_* constants
+     * @param handler {@link Handler} to specify the thread
+     *         upon which the {@link RawAnswerListener} will be invoked.
+     * @param listener a {@link RawAnswerListener} which will be called to notify the caller
+     *         of the result of dns query.
+     */
+    public void query(@Nullable Network network, @NonNull byte[] query, @QueryFlag int flags,
+            @NonNull Handler handler, @NonNull RawAnswerListener listener) throws ErrnoException {
+        final FileDescriptor queryfd = resNetworkSend((network != null
+                ? network.netId : NETID_UNSET), query, query.length, flags);
+        registerFDListener(handler.getLooper().getQueue(), queryfd,
+                answerbuf -> listener.onAnswer(answerbuf));
+    }
+
+    /**
+     * Pass in a domain name and corresponding setting,
+     * get a blob back asynchronously with the entire raw answer.
+     *
+     * @param network {@link Network} specifying which network for querying.
+     *         {@code null} for query on default network.
+     * @param domain domain name for querying
+     * @param nsClass dns class as one of the CLASS_* constants
+     * @param nsType dns resource record (RR) type as one of the TYPE_* constants
+     * @param flags flags as a combination of the FLAGS_* constants
+     * @param handler {@link Handler} to specify the thread
+     *         upon which the {@link RawAnswerListener} will be invoked.
+     * @param listener a {@link RawAnswerListener} which will be called to notify the caller
+     *         of the result of dns query.
+     */
+    public void query(@Nullable Network network, @NonNull String domain, @QueryClass int nsClass,
+            @QueryType int nsType, @QueryFlag int flags,
+            @NonNull Handler handler, @NonNull RawAnswerListener listener) throws ErrnoException {
+        final FileDescriptor queryfd = resNetworkQuery((network != null
+                ? network.netId : NETID_UNSET), domain, nsClass, nsType, flags);
+        registerFDListener(handler.getLooper().getQueue(), queryfd,
+                answerbuf -> listener.onAnswer(answerbuf));
+    }
+
+    /**
+     * Pass in a domain name and corresponding setting,
+     * get back a set of InetAddresses asynchronously.
+     *
+     * @param network {@link Network} specifying which network for querying.
+     *         {@code null} for query on default network.
+     * @param domain domain name for querying
+     * @param flags flags as a combination of the FLAGS_* constants
+     * @param handler {@link Handler} to specify the thread
+     *         upon which the {@link InetAddressAnswerListener} will be invoked.
+     * @param listener an {@link InetAddressAnswerListener} which will be called to
+     *         notify the caller of the result of dns query.
+     *
+     */
+    public void query(@Nullable Network network, @NonNull String domain, @QueryFlag int flags,
+            @NonNull Handler handler, @NonNull InetAddressAnswerListener listener)
+            throws ErrnoException {
+        final FileDescriptor v4fd = resNetworkQuery((network != null
+                ? network.netId : NETID_UNSET), domain, CLASS_IN, TYPE_A, flags);
+        final FileDescriptor v6fd = resNetworkQuery((network != null
+                ? network.netId : NETID_UNSET), domain, CLASS_IN, TYPE_AAAA, flags);
+
+        final InetAddressAnswerAccumulator accmulator =
+                new InetAddressAnswerAccumulator(2, listener);
+        final Consumer<byte[]> consumer = answerbuf ->
+                accmulator.accumulate(parseAnswers(answerbuf));
+
+        registerFDListener(handler.getLooper().getQueue(), v4fd, consumer);
+        registerFDListener(handler.getLooper().getQueue(), v6fd, consumer);
+    }
+
+    private void registerFDListener(@NonNull MessageQueue queue,
+            @NonNull FileDescriptor queryfd, @NonNull Consumer<byte[]> answerConsumer) {
+        queue.addOnFileDescriptorEventListener(
+                queryfd,
+                FD_EVENTS,
+                (fd, events) -> {
+                    byte[] answerbuf = null;
+                    try {
+                    // TODO: Implement result function in Java side instead of using JNI
+                    //       Because JNI method close fd prior than unregistering fd on
+                    //       event listener.
+                        answerbuf = resNetworkResult(fd);
+                    } catch (ErrnoException e) {
+                        Log.e(TAG, "resNetworkResult:" + e.toString());
+                    }
+                    answerConsumer.accept(answerbuf);
+
+                    // Unregister this fd listener
+                    return 0;
+                });
+    }
+
+    private class DnsAddressAnswer extends DnsPacket {
+        private static final String TAG = "DnsResolver.DnsAddressAnswer";
+        private static final boolean DBG = false;
+
+        private final int mQueryType;
+
+        DnsAddressAnswer(@NonNull byte[] data) throws ParseException {
+            super(data);
+            if ((mHeader.flags & (1 << 15)) == 0) {
+                throw new ParseException("Not an answer packet");
+            }
+            if (mHeader.rcode != 0) {
+                throw new ParseException("Response error, rcode:" + mHeader.rcode);
+            }
+            if (mHeader.getSectionCount(ANSECTION) == 0) {
+                throw new ParseException("No available answer");
+            }
+            if (mHeader.getSectionCount(QDSECTION) == 0) {
+                throw new ParseException("No question found");
+            }
+            // Assume only one question per answer packet. (RFC1035)
+            mQueryType = mSections[QDSECTION].get(0).nsType;
+        }
+
+        public @NonNull List<InetAddress> getAddresses() {
+            final List<InetAddress> results = new ArrayList<InetAddress>();
+            for (final DnsSection ansSec : mSections[ANSECTION]) {
+                // Only support A and AAAA, also ignore answers if query type != answer type.
+                int nsType = ansSec.nsType;
+                if (nsType != mQueryType || (nsType != TYPE_A && nsType != TYPE_AAAA)) {
+                    continue;
+                }
+                try {
+                    results.add(InetAddress.getByAddress(ansSec.getRR()));
+                } catch (UnknownHostException e) {
+                    if (DBG) {
+                        Log.w(TAG, "rr to address fail");
+                    }
+                }
+            }
+            return results;
+        }
+    }
+
+    private @Nullable List<InetAddress> parseAnswers(@Nullable byte[] data) {
+        try {
+            return (data == null) ? null : new DnsAddressAnswer(data).getAddresses();
+        } catch (DnsPacket.ParseException e) {
+            Log.e(TAG, "Parse answer fail " + e.getMessage());
+            return null;
+        }
+    }
+
+    private class InetAddressAnswerAccumulator {
+        private final List<InetAddress> mAllAnswers;
+        private final InetAddressAnswerListener mAnswerListener;
+        private final int mTargetAnswerCount;
+        private int mReceivedAnswerCount = 0;
+
+        InetAddressAnswerAccumulator(int size, @NonNull InetAddressAnswerListener listener) {
+            mTargetAnswerCount = size;
+            mAllAnswers = new ArrayList<>();
+            mAnswerListener = listener;
+        }
+
+        public void accumulate(@Nullable List<InetAddress> answer) {
+            if (null != answer) {
+                mAllAnswers.addAll(answer);
+            }
+            if (++mReceivedAnswerCount == mTargetAnswerCount) {
+                mAnswerListener.onAnswer(mAllAnswers);
+            }
+        }
+    }
+}
diff --git a/core/java/android/net/ICaptivePortal.aidl b/core/java/android/net/ICaptivePortal.aidl
index a013e79..56ae57d 100644
--- a/core/java/android/net/ICaptivePortal.aidl
+++ b/core/java/android/net/ICaptivePortal.aidl
@@ -20,7 +20,6 @@
  * Interface to inform NetworkMonitor of decisions of app handling captive portal.
  * @hide
  */
-interface ICaptivePortal
-{
-    oneway void appResponse(int response);
+oneway interface ICaptivePortal {
+    void appResponse(int response);
 }
diff --git a/core/java/android/net/IConnectivityManager.aidl b/core/java/android/net/IConnectivityManager.aidl
index da5d96e..e97060a 100644
--- a/core/java/android/net/IConnectivityManager.aidl
+++ b/core/java/android/net/IConnectivityManager.aidl
@@ -165,6 +165,7 @@
     void setAvoidUnvalidated(in Network network);
     void startCaptivePortalApp(in Network network);
 
+    boolean getAvoidBadWifi();
     int getMultipathPreference(in Network Network);
 
     NetworkRequest getDefaultRequest();
@@ -180,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();
@@ -187,4 +192,6 @@
     byte[] getNetworkWatchlistConfigHash();
 
     int getConnectionOwnerUid(in ConnectionInfo connectionInfo);
+    boolean isCallerCurrentAlwaysOnVpnApp();
+    boolean isCallerCurrentAlwaysOnVpnLockdownApp();
 }
diff --git a/core/java/android/net/INetworkManagementEventObserver.aidl b/core/java/android/net/INetworkManagementEventObserver.aidl
index b7af374..f0fe92e 100644
--- a/core/java/android/net/INetworkManagementEventObserver.aidl
+++ b/core/java/android/net/INetworkManagementEventObserver.aidl
@@ -24,7 +24,7 @@
  *
  * @hide
  */
-interface INetworkManagementEventObserver {
+oneway interface INetworkManagementEventObserver {
     /**
      * Interface configuration status has changed.
      *
diff --git a/core/java/android/net/INetworkStackConnector.aidl b/core/java/android/net/INetworkStackConnector.aidl
index 2df8ab7..8b64f1c 100644
--- a/core/java/android/net/INetworkStackConnector.aidl
+++ b/core/java/android/net/INetworkStackConnector.aidl
@@ -18,10 +18,12 @@
 import android.net.INetworkMonitorCallbacks;
 import android.net.dhcp.DhcpServingParamsParcel;
 import android.net.dhcp.IDhcpServerCallbacks;
+import android.net.ip.IIpClientCallbacks;
 
 /** @hide */
 oneway interface INetworkStackConnector {
     void makeDhcpServer(in String ifName, in DhcpServingParamsParcel params,
         in IDhcpServerCallbacks cb);
     void makeNetworkMonitor(int netId, String name, in INetworkMonitorCallbacks cb);
+    void makeIpClient(in String ifName, in IIpClientCallbacks callbacks);
 }
\ No newline at end of file
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/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/NetworkStack.java b/core/java/android/net/NetworkStack.java
index af043ee..d277034 100644
--- a/core/java/android/net/NetworkStack.java
+++ b/core/java/android/net/NetworkStack.java
@@ -27,6 +27,7 @@
 import android.content.ServiceConnection;
 import android.net.dhcp.DhcpServingParamsParcel;
 import android.net.dhcp.IDhcpServerCallbacks;
+import android.net.ip.IIpClientCallbacks;
 import android.os.Binder;
 import android.os.IBinder;
 import android.os.Process;
@@ -84,6 +85,21 @@
     }
 
     /**
+     * Create an IpClient on the specified interface.
+     *
+     * <p>The IpClient will be returned asynchronously through the provided callbacks.
+     */
+    public void makeIpClient(String ifName, IIpClientCallbacks cb) {
+        requestConnector(connector -> {
+            try {
+                connector.makeIpClient(ifName, cb);
+            } catch (RemoteException e) {
+                e.rethrowFromSystemServer();
+            }
+        });
+    }
+
+    /**
      * Create a NetworkMonitor.
      *
      * <p>The INetworkMonitor will be returned asynchronously through the provided callbacks.
diff --git a/core/java/android/net/NetworkUtils.java b/core/java/android/net/NetworkUtils.java
index 4eab49c..c996d01 100644
--- a/core/java/android/net/NetworkUtils.java
+++ b/core/java/android/net/NetworkUtils.java
@@ -39,6 +39,8 @@
 import java.util.Locale;
 import java.util.TreeSet;
 
+import android.system.ErrnoException;
+
 /**
  * Native methods for managing network interfaces.
  *
@@ -138,6 +140,32 @@
     public native static boolean queryUserAccess(int uid, int netId);
 
     /**
+     * DNS resolver series jni method.
+     * Issue the query {@code msg} on the network designated by {@code netId}.
+     * {@code flags} is an additional config to control actual querying behavior.
+     * @return a file descriptor to watch for read events
+     */
+    public static native FileDescriptor resNetworkSend(
+            int netId, byte[] msg, int msglen, int flags) throws ErrnoException;
+
+    /**
+     * DNS resolver series jni method.
+     * Look up the {@code nsClass} {@code nsType} Resource Record (RR) associated
+     * with Domain Name {@code dname} on the network designated by {@code netId}.
+     * {@code flags} is an additional config to control actual querying behavior.
+     * @return a file descriptor to watch for read events
+     */
+    public static native FileDescriptor resNetworkQuery(
+            int netId, String dname, int nsClass, int nsType, int flags) throws ErrnoException;
+
+    /**
+     * DNS resolver series jni method.
+     * Read a result for the query associated with the {@code fd}.
+     * @return a byte array containing blob answer
+     */
+    public static native byte[] resNetworkResult(FileDescriptor fd) throws ErrnoException;
+
+    /**
      * Add an entry into the ARP cache.
      */
     public static void addArpEntry(Inet4Address ipv4Addr, MacAddress ethAddr, String ifname,
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/SSLCertificateSocketFactory.java b/core/java/android/net/SSLCertificateSocketFactory.java
index abc1cac..90dccb5 100644
--- a/core/java/android/net/SSLCertificateSocketFactory.java
+++ b/core/java/android/net/SSLCertificateSocketFactory.java
@@ -86,7 +86,16 @@
  * <p>On development devices, "setprop socket.relaxsslcheck yes" bypasses all
  * SSL certificate and hostname checks for testing purposes.  This setting
  * requires root access.
+ *
+ * @deprecated This class has less error-prone replacements using standard APIs.  To create an
+ * {@code SSLSocket}, obtain an {@link SSLSocketFactory} from {@link SSLSocketFactory#getDefault()}
+ * or {@link javax.net.ssl.SSLContext#getSocketFactory()}.  To verify hostnames, pass
+ * {@code "HTTPS"} to
+ * {@link javax.net.ssl.SSLParameters#setEndpointIdentificationAlgorithm(String)}.  To enable ALPN,
+ * use {@link javax.net.ssl.SSLParameters#setApplicationProtocols(String[])}.  To enable SNI,
+ * use {@link javax.net.ssl.SSLParameters#setServerNames(java.util.List)}.
  */
+@Deprecated
 public class SSLCertificateSocketFactory extends SSLSocketFactory {
     @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.P, trackingBug = 115609023)
     private static final String TAG = "SSLCertificateSocketFactory";
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/VpnService.java b/core/java/android/net/VpnService.java
index f0c0462..dc099a4 100644
--- a/core/java/android/net/VpnService.java
+++ b/core/java/android/net/VpnService.java
@@ -368,6 +368,29 @@
     }
 
     /**
+     * Returns whether the service is running in always-on VPN mode.
+     */
+    public final boolean isAlwaysOn() {
+        try {
+            return getService().isCallerCurrentAlwaysOnVpnApp();
+        } catch (RemoteException e) {
+            throw e.rethrowFromSystemServer();
+        }
+    }
+
+    /**
+     * Returns whether the service is running in always-on VPN mode blocking connections without
+     * VPN.
+     */
+    public final boolean isLockdownEnabled() {
+        try {
+            return getService().isCallerCurrentAlwaysOnVpnLockdownApp();
+        } catch (RemoteException e) {
+            throw e.rethrowFromSystemServer();
+        }
+    }
+
+    /**
      * Return the communication interface to the service. This method returns
      * {@code null} on {@link Intent}s other than {@link #SERVICE_INTERFACE}
      * action. Applications overriding this method must identify the intent
@@ -486,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/ipmemorystore/IOnNetworkAttributesRetrieved.aidl b/core/java/android/net/ipmemorystore/IOnNetworkAttributesRetrieved.aidl
index 57f59a1..fb4ca3b 100644
--- a/core/java/android/net/ipmemorystore/IOnNetworkAttributesRetrieved.aidl
+++ b/core/java/android/net/ipmemorystore/IOnNetworkAttributesRetrieved.aidl
@@ -25,6 +25,6 @@
      * Network attributes were fetched for the specified L2 key. While the L2 key will never
      * be null, the attributes may be if no data is stored about this L2 key.
      */
-     void onL2KeyResponse(in StatusParcelable status, in String l2Key,
+     void onNetworkAttributesRetrieved(in StatusParcelable status, in String l2Key,
              in NetworkAttributesParcelable attributes);
 }
diff --git a/core/java/android/net/ipmemorystore/NetworkAttributes.java b/core/java/android/net/ipmemorystore/NetworkAttributes.java
index b932d21..6a9eae0 100644
--- a/core/java/android/net/ipmemorystore/NetworkAttributes.java
+++ b/core/java/android/net/ipmemorystore/NetworkAttributes.java
@@ -37,27 +37,57 @@
 public class NetworkAttributes {
     private static final boolean DBG = true;
 
+    // Weight cutoff for grouping. To group, a similarity score is computed with the following
+    // algorithm : if both fields are non-null and equals() then add their assigned weight, else if
+    // both are null then add a portion of their assigned weight (see NULL_MATCH_WEIGHT),
+    // otherwise add nothing.
+    // As a guideline, this should be something like 60~75% of the total weights in this class. The
+    // design states "in essence a reader should imagine that if two important columns don't match,
+    // or one important and several unimportant columns don't match then the two records are
+    // considered a different group".
+    private static final float TOTAL_WEIGHT_CUTOFF = 520.0f;
+    // The portion of the weight that is earned when scoring group-sameness by having both columns
+    // being null. This is because some networks rightfully don't have some attributes (e.g. a
+    // V6-only network won't have an assigned V4 address) and both being null should count for
+    // something, but attributes may also be null just because data is unavailable.
+    private static final float NULL_MATCH_WEIGHT = 0.25f;
+
     // The v4 address that was assigned to this device the last time it joined this network.
     // This typically comes from DHCP but could be something else like static configuration.
     // This does not apply to IPv6.
     // TODO : add a list of v6 prefixes for the v6 case.
     @Nullable
     public final Inet4Address assignedV4Address;
+    private static final float WEIGHT_ASSIGNEDV4ADDR = 300.0f;
 
     // Optionally supplied by the client if it has an opinion on L3 network. For example, this
     // could be a hash of the SSID + security type on WiFi.
     @Nullable
     public final String groupHint;
+    private static final float WEIGHT_GROUPHINT = 300.0f;
 
     // The list of DNS server addresses.
     @Nullable
     public final List<InetAddress> dnsAddresses;
+    private static final float WEIGHT_DNSADDRESSES = 200.0f;
 
     // The mtu on this network.
     @Nullable
     public final Integer mtu;
+    private static final float WEIGHT_MTU = 50.0f;
 
-    NetworkAttributes(
+    // The sum of all weights in this class. Tests ensure that this stays equal to the total of
+    // all weights.
+    /** @hide */
+    @VisibleForTesting
+    public static final float TOTAL_WEIGHT = WEIGHT_ASSIGNEDV4ADDR
+            + WEIGHT_GROUPHINT
+            + WEIGHT_DNSADDRESSES
+            + WEIGHT_MTU;
+
+    /** @hide */
+    @VisibleForTesting
+    public NetworkAttributes(
             @Nullable final Inet4Address assignedV4Address,
             @Nullable final String groupHint,
             @Nullable final List<InetAddress> dnsAddresses,
@@ -126,6 +156,34 @@
         return parcelable;
     }
 
+    private float samenessContribution(final float weight,
+            @Nullable final Object o1, @Nullable final Object o2) {
+        if (null == o1) {
+            return (null == o2) ? weight * NULL_MATCH_WEIGHT : 0f;
+        }
+        return Objects.equals(o1, o2) ? weight : 0f;
+    }
+
+    /** @hide */
+    public float getNetworkGroupSamenessConfidence(@NonNull final NetworkAttributes o) {
+        final float samenessScore =
+                samenessContribution(WEIGHT_ASSIGNEDV4ADDR, assignedV4Address, o.assignedV4Address)
+                + samenessContribution(WEIGHT_GROUPHINT, groupHint, o.groupHint)
+                + samenessContribution(WEIGHT_DNSADDRESSES, dnsAddresses, o.dnsAddresses)
+                + samenessContribution(WEIGHT_MTU, mtu, o.mtu);
+        // The minimum is 0, the max is TOTAL_WEIGHT and should be represented by 1.0, and
+        // TOTAL_WEIGHT_CUTOFF should represent 0.5, but there is no requirement that
+        // TOTAL_WEIGHT_CUTOFF would be half of TOTAL_WEIGHT (indeed, it should not be).
+        // So scale scores under the cutoff between 0 and 0.5, and the scores over the cutoff
+        // between 0.5 and 1.0.
+        if (samenessScore < TOTAL_WEIGHT_CUTOFF) {
+            return samenessScore / (TOTAL_WEIGHT_CUTOFF * 2);
+        } else {
+            return (samenessScore - TOTAL_WEIGHT_CUTOFF) / (TOTAL_WEIGHT - TOTAL_WEIGHT_CUTOFF) / 2
+                    + 0.5f;
+        }
+    }
+
     /** @hide */
     public static class Builder {
         @Nullable
@@ -194,6 +252,12 @@
         }
     }
 
+    /** @hide */
+    public boolean isEmpty() {
+        return (null == assignedV4Address) && (null == groupHint)
+                && (null == dnsAddresses) && (null == mtu);
+    }
+
     @Override
     public boolean equals(@Nullable final Object o) {
         if (!(o instanceof NetworkAttributes)) return false;
diff --git a/core/java/android/net/ipmemorystore/SameL3NetworkResponse.java b/core/java/android/net/ipmemorystore/SameL3NetworkResponse.java
index d040dcc..291aca8 100644
--- a/core/java/android/net/ipmemorystore/SameL3NetworkResponse.java
+++ b/core/java/android/net/ipmemorystore/SameL3NetworkResponse.java
@@ -91,7 +91,8 @@
         return confidence > 0.5 ? NETWORK_SAME : NETWORK_DIFFERENT;
     }
 
-    SameL3NetworkResponse(@NonNull final String l2Key1, @NonNull final String l2Key2,
+    /** @hide */
+    public SameL3NetworkResponse(@NonNull final String l2Key1, @NonNull final String l2Key2,
             final float confidence) {
         this.l2Key1 = l2Key1;
         this.l2Key2 = l2Key2;
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/services/net/java/android/net/util/MultinetworkPolicyTracker.java b/core/java/android/net/util/MultinetworkPolicyTracker.java
similarity index 100%
rename from services/net/java/android/net/util/MultinetworkPolicyTracker.java
rename to core/java/android/net/util/MultinetworkPolicyTracker.java
diff --git a/core/java/android/os/AppZygote.java b/core/java/android/os/AppZygote.java
index 950f381..6daa5b4 100644
--- a/core/java/android/os/AppZygote.java
+++ b/core/java/android/os/AppZygote.java
@@ -103,7 +103,7 @@
         String abi = mAppInfo.primaryCpuAbi != null ? mAppInfo.primaryCpuAbi :
                 Build.SUPPORTED_ABIS[0];
         try {
-            mZygote = Process.zygoteProcess.startChildZygote(
+            mZygote = Process.ZYGOTE_PROCESS.startChildZygote(
                     "com.android.internal.os.AppZygoteInit",
                     mAppInfo.processName + "_zygote",
                     mZygoteUid,
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 c5a51f1..6f5f807 100644
--- a/core/java/android/os/BugreportManager.java
+++ b/core/java/android/os/BugreportManager.java
@@ -16,6 +16,7 @@
 
 package android.os;
 
+import android.annotation.CallbackExecutor;
 import android.annotation.IntDef;
 import android.annotation.NonNull;
 import android.annotation.Nullable;
@@ -27,6 +28,7 @@
 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.
@@ -34,7 +36,7 @@
  * @hide
  */
 // TODO: Expose API when the implementation is more complete.
-// @SystemApi
+//@SystemApi
 @SystemService(Context.BUGREPORT_SERVICE)
 public class BugreportManager {
     private final Context mContext;
@@ -47,86 +49,119 @@
     }
 
     /**
-     * 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}.
+         * <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
-         *
-         * @param durationMs time capturing bugreport took in milliseconds
-         * @param title title for the bugreport; helpful in reminding the user why they took it
-         * @param description detailed description for the bugreport
+         * Called when taking bugreport finishes successfully.
          */
-        void onFinished(long durationMs, @NonNull String title,
-                @NonNull String description);
+        public void onFinished() {}
     }
 
     /**
-     * Starts a bugreport asynchronously.
+     * 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 callback} will receive progress and status
+     * updates.
+     *
+     * <p>The bugreport artifacts will be copied over to the given file descriptors only if the
+     * user consents to sharing with the calling app.
      *
      * @param bugreportFd file to write the bugreport. This should be opened in write-only,
      *     append mode.
      * @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, @Nullable 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();
         }
     }
 
+    /*
+     * Cancels a currently running bugreport.
+     */
+    @RequiresPermission(android.Manifest.permission.DUMP)
+    public void cancelBugreport() {
+        try {
+            mBinder.cancelBugreport();
+        } catch (RemoteException e) {
+            throw e.rethrowFromSystemServer();
+        }
+    }
+
     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
@@ -136,18 +171,40 @@
 
         @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(long durationMs, String title, String description)
-                throws RemoteException {
-            mListener.onFinished(durationMs, title, description);
+        public void onFinished() throws RemoteException {
+            final long identity = Binder.clearCallingIdentity();
+            try {
+                mExecutor.execute(() -> {
+                    mCallback.onFinished();
+                });
+            } finally {
+                Binder.restoreCallingIdentity(identity);
+                // The bugreport has finished. Let's shutdown the service to minimize its footprint.
+                cancelBugreport();
+            }
         }
 
         // Old methods; should go away
diff --git a/core/java/android/os/FileUtils.java b/core/java/android/os/FileUtils.java
index ca39051..51c3c4c 100644
--- a/core/java/android/os/FileUtils.java
+++ b/core/java/android/os/FileUtils.java
@@ -72,6 +72,7 @@
 import java.security.MessageDigest;
 import java.security.NoSuchAlgorithmException;
 import java.util.Arrays;
+import java.util.Collection;
 import java.util.Comparator;
 import java.util.Objects;
 import java.util.concurrent.Executor;
@@ -831,6 +832,16 @@
         return false;
     }
 
+    /** {@hide} */
+    public static boolean contains(Collection<File> dirs, File file) {
+        for (File dir : dirs) {
+            if (contains(dir, file)) {
+                return true;
+            }
+        }
+        return false;
+    }
+
     /**
      * Test if a file lives under the given directory, either as a direct child
      * or a distant grandchild.
diff --git a/core/java/android/os/GraphicsEnvironment.java b/core/java/android/os/GraphicsEnvironment.java
index f51ba9a..efcad3e 100644
--- a/core/java/android/os/GraphicsEnvironment.java
+++ b/core/java/android/os/GraphicsEnvironment.java
@@ -23,10 +23,15 @@
 import android.content.pm.ResolveInfo;
 import android.content.res.AssetFileDescriptor;
 import android.content.res.AssetManager;
+import android.gamedriver.GameDriverProto.Blacklist;
+import android.gamedriver.GameDriverProto.Blacklists;
 import android.opengl.EGL14;
 import android.provider.Settings;
+import android.util.Base64;
 import android.util.Log;
 
+import com.android.framework.protobuf.InvalidProtocolBufferException;
+
 import dalvik.system.VMRuntime;
 
 import java.io.BufferedReader;
@@ -62,6 +67,8 @@
     private static final String ANGLE_RULES_FILE = "a4a_rules.json";
     private static final String ANGLE_TEMP_RULES = "debug.angle.rules";
     private static final String ACTION_ANGLE_FOR_ANDROID = "android.app.action.ANGLE_FOR_ANDROID";
+    private static final String GAME_DRIVER_BLACKLIST_FLAG = "blacklist";
+    private static final int BASE64_FLAGS = Base64.NO_PADDING | Base64.NO_WRAP;
 
     private ClassLoader mClassLoader;
     private String mLayerPath;
@@ -362,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.
@@ -418,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.");
@@ -459,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;
@@ -480,47 +502,6 @@
             return;
         }
 
-        // To minimize risk of driver updates crippling the device beyond user repair, never use an
-        // updated driver for privileged or non-updated system apps. Presumably pre-installed apps
-        // were tested thoroughly with the pre-installed driver.
-        ApplicationInfo ai = context.getApplicationInfo();
-        if (ai.isPrivilegedApp() || (ai.isSystemApp() && !ai.isUpdatedSystemApp())) {
-            if (DEBUG) Log.v(TAG, "ignoring driver package for privileged/non-updated system app");
-            return;
-        }
-
-        // GUP_DEV_ALL_APPS
-        // 0: Default (Invalid values fallback to default as well)
-        // 1: All apps use Game Update Package
-        // 2: All apps use system graphics driver
-        int gupDevAllApps = coreSettings.getInt(Settings.Global.GUP_DEV_ALL_APPS, 0);
-        if (gupDevAllApps == 2) {
-            if (DEBUG) {
-                Log.w(TAG, "GUP is turned off on this device");
-            }
-            return;
-        }
-
-        if (gupDevAllApps != 1) {
-            // GUP_DEV_OPT_OUT_APPS has higher priority than GUP_DEV_OPT_IN_APPS
-            if (getGlobalSettingsString(coreSettings, Settings.Global.GUP_DEV_OPT_OUT_APPS)
-                            .contains(ai.packageName)) {
-                if (DEBUG) {
-                    Log.w(TAG, ai.packageName + " opts out from GUP.");
-                }
-                return;
-            }
-
-            if (!getGlobalSettingsString(coreSettings, Settings.Global.GUP_DEV_OPT_IN_APPS)
-                            .contains(ai.packageName)
-                    && !onWhitelist(context, driverPackageName, ai.packageName)) {
-                if (DEBUG) {
-                    Log.w(TAG, ai.packageName + " is not on the whitelist.");
-                }
-                return;
-            }
-        }
-
         ApplicationInfo driverInfo;
         try {
             driverInfo = context.getPackageManager().getApplicationInfo(driverPackageName,
@@ -539,6 +520,77 @@
             return;
         }
 
+        // To minimize risk of driver updates crippling the device beyond user repair, never use an
+        // updated driver for privileged or non-updated system apps. Presumably pre-installed apps
+        // were tested thoroughly with the pre-installed driver.
+        ApplicationInfo ai = context.getApplicationInfo();
+        if (ai.isPrivilegedApp() || (ai.isSystemApp() && !ai.isUpdatedSystemApp())) {
+            if (DEBUG) Log.v(TAG, "ignoring driver package for privileged/non-updated system app");
+            return;
+        }
+
+        // GUP_DEV_ALL_APPS
+        // 0: Default (Invalid values fallback to default as well)
+        // 1: All apps use Game Driver
+        // 2: All apps use system graphics driver
+        int gupDevAllApps = coreSettings.getInt(Settings.Global.GUP_DEV_ALL_APPS, 0);
+        if (gupDevAllApps == 2) {
+            if (DEBUG) {
+                Log.w(TAG, "GUP is turned off on this device");
+            }
+            return;
+        }
+
+        if (gupDevAllApps != 1) {
+            // GUP_DEV_OPT_OUT_APPS has higher priority than GUP_DEV_OPT_IN_APPS
+            if (getGlobalSettingsString(coreSettings, Settings.Global.GUP_DEV_OPT_OUT_APPS)
+                            .contains(ai.packageName)) {
+                if (DEBUG) {
+                    Log.w(TAG, ai.packageName + " opts out from GUP.");
+                }
+                return;
+            }
+            boolean isDevOptIn = getGlobalSettingsString(coreSettings,
+                                                         Settings.Global.GUP_DEV_OPT_IN_APPS)
+                              .contains(ai.packageName);
+
+            if (!isDevOptIn && !onWhitelist(context, driverPackageName, ai.packageName)) {
+                if (DEBUG) {
+                    Log.w(TAG, ai.packageName + " is not on the whitelist.");
+                }
+                return;
+            }
+
+            if (!isDevOptIn) {
+                // At this point, the application is on the whitelist only, check whether it's
+                // on the blacklist, terminate early when it's on the blacklist.
+                try {
+                    // TODO(b/121350991) Switch to DeviceConfig with property listener.
+                    String base64String = coreSettings.getString(Settings.Global.GUP_BLACKLIST);
+                    if (base64String != null && !base64String.isEmpty()) {
+                        Blacklists blacklistsProto = Blacklists.parseFrom(
+                                Base64.decode(base64String, BASE64_FLAGS));
+                        List<Blacklist> blacklists = blacklistsProto.getBlacklistsList();
+                        long driverVersionCode = driverInfo.longVersionCode;
+                        for (Blacklist blacklist : blacklists) {
+                            if (blacklist.getVersionCode() == driverVersionCode) {
+                                for (String packageName : blacklist.getPackageNamesList()) {
+                                    if (packageName == ai.packageName) {
+                                        return;
+                                    }
+                                }
+                                break;
+                            }
+                        }
+                    }
+                } catch (InvalidProtocolBufferException e) {
+                    if (DEBUG) {
+                        Log.w(TAG, "Can't parse blacklist, skip and continue...");
+                    }
+                }
+            }
+        }
+
         String abi = chooseAbi(driverInfo);
         if (abi == null) {
             if (DEBUG) {
diff --git a/core/java/android/os/HandlerThread.java b/core/java/android/os/HandlerThread.java
index a4d5c6f..92fcbb6 100644
--- a/core/java/android/os/HandlerThread.java
+++ b/core/java/android/os/HandlerThread.java
@@ -20,8 +20,10 @@
 import android.annotation.Nullable;
 
 /**
- * Handy class for starting a new thread that has a looper. The looper can then be 
- * used to create handler classes. Note that start() must still be called.
+ * A {@link Thread} that has a {@link Looper}.
+ * The {@link Looper} can then be used to create {@link Handler}s.
+ * <p>
+ * Note that just like with a regular {@link Thread}, {@link #start()} must still be called.
  */
 public class HandlerThread extends Thread {
     int mPriority;
diff --git a/core/java/android/os/IDeviceIdleController.aidl b/core/java/android/os/IDeviceIdleController.aidl
index 8271701..fe17c6b 100644
--- a/core/java/android/os/IDeviceIdleController.aidl
+++ b/core/java/android/os/IDeviceIdleController.aidl
@@ -45,4 +45,6 @@
     void exitIdle(String reason);
     boolean registerMaintenanceActivityListener(IMaintenanceActivityListener listener);
     void unregisterMaintenanceActivityListener(IMaintenanceActivityListener listener);
+    int setPreIdleTimeoutMode(int Mode);
+    void resetPreIdleTimeoutMode();
 }
diff --git a/core/java/android/os/ParcelFileDescriptor.java b/core/java/android/os/ParcelFileDescriptor.java
index 63912ec..630bd2e 100644
--- a/core/java/android/os/ParcelFileDescriptor.java
+++ b/core/java/android/os/ParcelFileDescriptor.java
@@ -54,6 +54,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 +394,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 +558,7 @@
         }
         file.deactivate();
         FileDescriptor fd = file.getFileDescriptor();
-        return fd != null ? new ParcelFileDescriptor(fd) : null;
+        return fd != null ? ParcelFileDescriptor.dup(fd) : null;
     }
 
     /**
diff --git a/core/java/android/os/PowerManager.java b/core/java/android/os/PowerManager.java
index 0942d97..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
@@ -1724,6 +1731,25 @@
             = "android.os.action.SCREEN_BRIGHTNESS_BOOST_CHANGED";
 
     /**
+     * Constant for PreIdleTimeout normal mode (default mode, not short nor extend timeout) .
+     * @hide
+     */
+    public static final int PRE_IDLE_TIMEOUT_MODE_NORMAL = 0;
+
+    /**
+     * Constant for PreIdleTimeout long mode (extend timeout to keep in inactive mode
+     * longer).
+     * @hide
+     */
+    public static final int PRE_IDLE_TIMEOUT_MODE_LONG = 1;
+
+    /**
+     * Constant for PreIdleTimeout short mode (short timeout to go to doze mode quickly)
+     * @hide
+     */
+    public static final int PRE_IDLE_TIMEOUT_MODE_SHORT = 2;
+
+    /**
      * A wake lock is a mechanism to indicate that your application needs
      * to have the device stay on.
      * <p>
diff --git a/core/java/android/os/Process.java b/core/java/android/os/Process.java
index f2a9adb..d2ab053 100644
--- a/core/java/android/os/Process.java
+++ b/core/java/android/os/Process.java
@@ -32,16 +32,6 @@
     private static final String LOG_TAG = "Process";
 
     /**
-     * @hide for internal use only.
-     */
-    public static final String ZYGOTE_SOCKET = "zygote";
-
-    /**
-     * @hide for internal use only.
-     */
-    public static final String SECONDARY_ZYGOTE_SOCKET = "zygote_secondary";
-
-    /**
      * An invalid UID value.
      */
     public static final int INVALID_UID = -1;
@@ -479,8 +469,7 @@
      * State associated with the zygote process.
      * @hide
      */
-    public static final ZygoteProcess zygoteProcess =
-            new ZygoteProcess(ZYGOTE_SOCKET, SECONDARY_ZYGOTE_SOCKET);
+    public static final ZygoteProcess ZYGOTE_PROCESS = new ZygoteProcess();
 
     /**
      * Start a new process.
@@ -538,10 +527,10 @@
                                   @Nullable String[] packagesForUid,
                                   @Nullable String[] visibleVols,
                                   @Nullable String[] zygoteArgs) {
-        return zygoteProcess.start(processClass, niceName, uid, gid, gids,
+        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 */
@@ -562,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/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/WorkSource.java b/core/java/android/os/WorkSource.java
index 2299ab2..76fe560 100644
--- a/core/java/android/os/WorkSource.java
+++ b/core/java/android/os/WorkSource.java
@@ -140,6 +140,21 @@
         return mUids[index];
     }
 
+    /**
+     * Return the UID to which this WorkSource should be attributed to, i.e, the UID that
+     * initiated the work and not the UID performing it. If the WorkSource has no UIDs, returns -1
+     * instead.
+     *
+     * @hide
+     */
+    public int getAttributionUid() {
+        if (isEmpty()) {
+            return -1;
+        }
+
+        return mNum > 0 ? mUids[0] : mChains.get(0).getAttributionUid();
+    }
+
     /** @hide */
     @TestApi
     public String getName(int index) {
@@ -912,17 +927,18 @@
 
         /**
          * Return the UID to which this WorkChain should be attributed to, i.e, the UID that
-         * initiated the work and not the UID performing it.
+         * initiated the work and not the UID performing it. Returns -1 if the chain is empty.
          */
         public int getAttributionUid() {
-            return mUids[0];
+            return mSize > 0 ? mUids[0] : -1;
         }
 
         /**
          * Return the tag associated with the attribution UID. See (@link #getAttributionUid}.
+         * Returns null if the chain is empty.
          */
         public String getAttributionTag() {
-            return mTags[0];
+            return mTags.length > 0 ? mTags[0] : null;
         }
 
         // TODO: The following three trivial getters are purely for testing and will be removed
diff --git a/core/java/android/os/ZygoteProcess.java b/core/java/android/os/ZygoteProcess.java
index ec77821..9e47179 100644
--- a/core/java/android/os/ZygoteProcess.java
+++ b/core/java/android/os/ZygoteProcess.java
@@ -62,87 +62,161 @@
  * {@hide}
  */
 public class ZygoteProcess {
+
+    /**
+     * @hide for internal use only.
+     */
+    public static final String ZYGOTE_SOCKET_NAME = "zygote";
+
+    /**
+     * @hide for internal use only.
+     */
+    public static final String ZYGOTE_SECONDARY_SOCKET_NAME = "zygote_secondary";
+
+    /**
+     * @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";
 
     /**
      * The name of the socket used to communicate with the primary zygote.
      */
-    private final LocalSocketAddress mSocket;
+    private final LocalSocketAddress mZygoteSocketAddress;
 
     /**
      * The name of the secondary (alternate ABI) zygote socket.
      */
-    private final LocalSocketAddress mSecondarySocket;
+    private final LocalSocketAddress mZygoteSecondarySocketAddress;
+    /**
+     * The name of the socket used to communicate with the primary blastula pool.
+     */
+    private final LocalSocketAddress mBlastulaPoolSocketAddress;
 
-    public ZygoteProcess(String primarySocket, String secondarySocket) {
-        this(new LocalSocketAddress(primarySocket, LocalSocketAddress.Namespace.RESERVED),
-                new LocalSocketAddress(secondarySocket, LocalSocketAddress.Namespace.RESERVED));
+    /**
+     * The name of the socket used to communicate with the secondary (alternate ABI) blastula pool.
+     */
+    private final LocalSocketAddress mBlastulaPoolSecondarySocketAddress;
+
+    public ZygoteProcess() {
+        mZygoteSocketAddress =
+                new LocalSocketAddress(ZYGOTE_SOCKET_NAME, LocalSocketAddress.Namespace.RESERVED);
+        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 primarySocket, LocalSocketAddress secondarySocket) {
-        mSocket = primarySocket;
-        mSecondarySocket = secondarySocket;
+    public ZygoteProcess(LocalSocketAddress primarySocketAddress,
+                         LocalSocketAddress secondarySocketAddress) {
+        mZygoteSocketAddress = primarySocketAddress;
+        mZygoteSecondarySocketAddress = secondarySocketAddress;
+
+        mBlastulaPoolSocketAddress = null;
+        mBlastulaPoolSecondarySocketAddress = null;
     }
 
     public LocalSocketAddress getPrimarySocketAddress() {
-        return mSocket;
+        return mZygoteSocketAddress;
     }
 
     /**
      * State for communicating with the zygote process.
      */
     public static class ZygoteState {
-        final LocalSocket socket;
-        final DataInputStream inputStream;
-        final BufferedWriter writer;
-        final List<String> abiList;
+        final LocalSocketAddress mZygoteSocketAddress;
+        final LocalSocketAddress mBlastulaSocketAddress;
 
-        boolean mClosed;
+        private final LocalSocket mZygoteSessionSocket;
 
-        private ZygoteState(LocalSocket socket, DataInputStream inputStream,
-                BufferedWriter writer, List<String> abiList) {
-            this.socket = socket;
-            this.inputStream = inputStream;
-            this.writer = writer;
-            this.abiList = abiList;
+        final DataInputStream mZygoteInputStream;
+        final BufferedWriter mZygoteOutputWriter;
+
+        private final List<String> mABIList;
+
+        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;
+            this.mABIList = abiList;
         }
 
-        public static ZygoteState connect(LocalSocketAddress address) throws IOException {
+        /**
+         * 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,
+                                          LocalSocketAddress blastulaSocketAddress)
+                throws IOException {
+
             DataInputStream zygoteInputStream = null;
-            BufferedWriter zygoteWriter = null;
-            final LocalSocket zygoteSocket = new LocalSocket();
+            BufferedWriter zygoteOutputWriter = null;
+            final LocalSocket zygoteSessionSocket = new LocalSocket();
 
             try {
-                zygoteSocket.connect(address);
-
-                zygoteInputStream = new DataInputStream(zygoteSocket.getInputStream());
-
-                zygoteWriter = new BufferedWriter(new OutputStreamWriter(
-                        zygoteSocket.getOutputStream()), 256);
+                zygoteSessionSocket.connect(zygoteSocketAddress);
+                zygoteInputStream = new DataInputStream(zygoteSessionSocket.getInputStream());
+                zygoteOutputWriter =
+                        new BufferedWriter(
+                                new OutputStreamWriter(zygoteSessionSocket.getOutputStream()),
+                                Zygote.SOCKET_BUFFER_SIZE);
             } catch (IOException ex) {
                 try {
-                    zygoteSocket.close();
-                } catch (IOException ignore) {
-                }
+                    zygoteSessionSocket.close();
+                } catch (IOException ignore) { }
 
                 throw ex;
             }
 
-            String abiListString = getAbiList(zygoteWriter, zygoteInputStream);
-            Log.i("Zygote", "Process: zygote socket " + address.getNamespace() + "/"
-                    + address.getName() + " opened, supported ABIS: " + abiListString);
+            return new ZygoteState(zygoteSocketAddress, blastulaSocketAddress,
+                                   zygoteSessionSocket, zygoteInputStream, zygoteOutputWriter,
+                                   getAbiList(zygoteOutputWriter, zygoteInputStream));
+        }
 
-            return new ZygoteState(zygoteSocket, zygoteInputStream, zygoteWriter,
-                    Arrays.asList(abiListString.split(",")));
+        LocalSocket getBlastulaSessionSocket() throws IOException {
+            final LocalSocket blastulaSessionSocket = new LocalSocket();
+            blastulaSessionSocket.connect(this.mBlastulaSocketAddress);
+
+            return blastulaSessionSocket;
         }
 
         boolean matches(String abi) {
-            return abiList.contains(abi);
+            return mABIList.contains(abi);
         }
 
         public void close() {
             try {
-                socket.close();
+                mZygoteSessionSocket.close();
             } catch (IOException ex) {
                 Log.e(LOG_TAG,"I/O exception on routine close", ex);
             }
@@ -237,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, false /* startChildZygote */,
-                    packageName, packagesForUid, visibleVols, zygoteArgs);
+                    abi, instructionSet, appDataDir, invokeWith, /*startChildZygote=*/false,
+                    packageName, packagesForUid, visibleVols, useBlastulaPool, zygoteArgs);
         } catch (ZygoteStartFailedEx ex) {
             Log.e(LOG_TAG,
                     "Starting VM process through Zygote failed");
@@ -260,7 +335,7 @@
      * @throws ZygoteStartFailedEx if the query failed.
      */
     @GuardedBy("mLock")
-    private static String getAbiList(BufferedWriter writer, DataInputStream inputStream)
+    private static List<String> getAbiList(BufferedWriter writer, DataInputStream inputStream)
             throws IOException {
         // Each query starts with the argument count (1 in this case)
         writer.write("1");
@@ -276,7 +351,9 @@
         byte[] bytes = new byte[numBytes];
         inputStream.readFully(bytes);
 
-        return new String(bytes, StandardCharsets.US_ASCII);
+        String rawList = new String(bytes, StandardCharsets.US_ASCII);
+
+        return Arrays.asList(rawList.split(","));
     }
 
     /**
@@ -288,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.writer;
-            final DataInputStream inputStream = zygoteState.inputStream;
+        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;
     }
 
     /**
@@ -382,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>();
@@ -488,7 +635,9 @@
         }
 
         synchronized(mLock) {
-            return zygoteSendArgsAndGetResult(openZygoteSocketIfNeeded(abi), argsForZygote);
+            return zygoteSendArgsAndGetResult(openZygoteSocketIfNeeded(abi),
+                                              useBlastulaPool,
+                                              argsForZygote);
         }
     }
 
@@ -528,18 +677,18 @@
                 ZygoteState state = openZygoteSocketIfNeeded(abi);
 
                 // Each query starts with the argument count (1 in this case)
-                state.writer.write("1");
+                state.mZygoteOutputWriter.write("1");
                 // ... followed by a new-line.
-                state.writer.newLine();
+                state.mZygoteOutputWriter.newLine();
                 // ... followed by our only argument.
-                state.writer.write("--get-pid");
-                state.writer.newLine();
-                state.writer.flush();
+                state.mZygoteOutputWriter.write("--get-pid");
+                state.mZygoteOutputWriter.newLine();
+                state.mZygoteOutputWriter.flush();
 
                 // The response is a length prefixed stream of ASCII bytes.
-                int numBytes = state.inputStream.readInt();
+                int numBytes = state.mZygoteInputStream.readInt();
                 byte[] bytes = new byte[numBytes];
-                state.inputStream.readFully(bytes);
+                state.mZygoteInputStream.readFully(bytes);
 
                 return Integer.parseInt(new String(bytes, StandardCharsets.US_ASCII));
             }
@@ -593,16 +742,16 @@
             return true;
         }
         try {
-            state.writer.write(Integer.toString(mApiBlacklistExemptions.size() + 1));
-            state.writer.newLine();
-            state.writer.write("--set-api-blacklist-exemptions");
-            state.writer.newLine();
+            state.mZygoteOutputWriter.write(Integer.toString(mApiBlacklistExemptions.size() + 1));
+            state.mZygoteOutputWriter.newLine();
+            state.mZygoteOutputWriter.write("--set-api-blacklist-exemptions");
+            state.mZygoteOutputWriter.newLine();
             for (int i = 0; i < mApiBlacklistExemptions.size(); ++i) {
-                state.writer.write(mApiBlacklistExemptions.get(i));
-                state.writer.newLine();
+                state.mZygoteOutputWriter.write(mApiBlacklistExemptions.get(i));
+                state.mZygoteOutputWriter.newLine();
             }
-            state.writer.flush();
-            int status = state.inputStream.readInt();
+            state.mZygoteOutputWriter.flush();
+            int status = state.mZygoteInputStream.readInt();
             if (status != 0) {
                 Slog.e(LOG_TAG, "Failed to set API blacklist exemptions; status " + status);
             }
@@ -622,13 +771,13 @@
             return;
         }
         try {
-            state.writer.write(Integer.toString(1));
-            state.writer.newLine();
-            state.writer.write("--hidden-api-log-sampling-rate="
+            state.mZygoteOutputWriter.write(Integer.toString(1));
+            state.mZygoteOutputWriter.newLine();
+            state.mZygoteOutputWriter.write("--hidden-api-log-sampling-rate="
                     + Integer.toString(mHiddenApiAccessLogSampleRate));
-            state.writer.newLine();
-            state.writer.flush();
-            int status = state.inputStream.readInt();
+            state.mZygoteOutputWriter.newLine();
+            state.mZygoteOutputWriter.flush();
+            int status = state.mZygoteInputStream.readInt();
             if (status != 0) {
                 Slog.e(LOG_TAG, "Failed to set hidden API log sampling rate; status " + status);
             }
@@ -638,22 +787,29 @@
     }
 
     /**
-     * Tries to open socket to Zygote process if not already open. If
-     * already open, does nothing.  May block and retry.  Requires that mLock be held.
+     * Tries to open a session socket to a Zygote process with a compatible ABI if one is not
+     * already open. If a compatible session socket is already open that session socket is returned.
+     * This function may block and may have to try connecting to multiple Zygotes to find the
+     * appropriate one.  Requires that mLock be held.
      */
     @GuardedBy("mLock")
-    private ZygoteState openZygoteSocketIfNeeded(String abi) throws ZygoteStartFailedEx {
+    private ZygoteState openZygoteSocketIfNeeded(String abi)
+            throws ZygoteStartFailedEx {
+
         Preconditions.checkState(Thread.holdsLock(mLock), "ZygoteProcess lock not held");
 
         if (primaryZygoteState == null || primaryZygoteState.isClosed()) {
             try {
-                primaryZygoteState = ZygoteState.connect(mSocket);
+                primaryZygoteState =
+                    ZygoteState.connect(mZygoteSocketAddress, mBlastulaPoolSocketAddress);
             } catch (IOException ioe) {
                 throw new ZygoteStartFailedEx("Error connecting to primary zygote", ioe);
             }
+
             maybeSetApiBlacklistExemptions(primaryZygoteState, false);
             maybeSetHiddenApiAccessLogSampleRate(primaryZygoteState);
         }
+
         if (primaryZygoteState.matches(abi)) {
             return primaryZygoteState;
         }
@@ -661,10 +817,13 @@
         // The primary zygote didn't match. Try the secondary.
         if (secondaryZygoteState == null || secondaryZygoteState.isClosed()) {
             try {
-                secondaryZygoteState = ZygoteState.connect(mSecondarySocket);
+                secondaryZygoteState =
+                    ZygoteState.connect(mZygoteSecondarySocketAddress,
+                                        mBlastulaPoolSecondarySocketAddress);
             } catch (IOException ioe) {
                 throw new ZygoteStartFailedEx("Error connecting to secondary zygote", ioe);
             }
+
             maybeSetApiBlacklistExemptions(secondaryZygoteState, false);
             maybeSetHiddenApiAccessLogSampleRate(secondaryZygoteState);
         }
@@ -685,11 +844,11 @@
                                                                           IOException {
         synchronized (mLock) {
             ZygoteState state = openZygoteSocketIfNeeded(abi);
-            state.writer.write("2");
-            state.writer.newLine();
+            state.mZygoteOutputWriter.write("2");
+            state.mZygoteOutputWriter.newLine();
 
-            state.writer.write("--preload-app");
-            state.writer.newLine();
+            state.mZygoteOutputWriter.write("--preload-app");
+            state.mZygoteOutputWriter.newLine();
 
             // Zygote args needs to be strings, so in order to pass ApplicationInfo,
             // write it to a Parcel, and base64 the raw Parcel bytes to the other side.
@@ -697,12 +856,12 @@
             appInfo.writeToParcel(parcel, 0 /* flags */);
             String encodedParcelData = Base64.getEncoder().encodeToString(parcel.marshall());
             parcel.recycle();
-            state.writer.write(encodedParcelData);
-            state.writer.newLine();
+            state.mZygoteOutputWriter.write(encodedParcelData);
+            state.mZygoteOutputWriter.newLine();
 
-            state.writer.flush();
+            state.mZygoteOutputWriter.flush();
 
-            return (state.inputStream.readInt() == 0);
+            return (state.mZygoteInputStream.readInt() == 0);
         }
     }
 
@@ -715,27 +874,27 @@
                                                                             IOException {
         synchronized(mLock) {
             ZygoteState state = openZygoteSocketIfNeeded(abi);
-            state.writer.write("5");
-            state.writer.newLine();
+            state.mZygoteOutputWriter.write("5");
+            state.mZygoteOutputWriter.newLine();
 
-            state.writer.write("--preload-package");
-            state.writer.newLine();
+            state.mZygoteOutputWriter.write("--preload-package");
+            state.mZygoteOutputWriter.newLine();
 
-            state.writer.write(packagePath);
-            state.writer.newLine();
+            state.mZygoteOutputWriter.write(packagePath);
+            state.mZygoteOutputWriter.newLine();
 
-            state.writer.write(libsPath);
-            state.writer.newLine();
+            state.mZygoteOutputWriter.write(libsPath);
+            state.mZygoteOutputWriter.newLine();
 
-            state.writer.write(libFileName);
-            state.writer.newLine();
+            state.mZygoteOutputWriter.write(libFileName);
+            state.mZygoteOutputWriter.newLine();
 
-            state.writer.write(cacheKey);
-            state.writer.newLine();
+            state.mZygoteOutputWriter.write(cacheKey);
+            state.mZygoteOutputWriter.newLine();
 
-            state.writer.flush();
+            state.mZygoteOutputWriter.flush();
 
-            return (state.inputStream.readInt() == 0);
+            return (state.mZygoteInputStream.readInt() == 0);
         }
     }
 
@@ -749,13 +908,13 @@
         synchronized (mLock) {
             ZygoteState state = openZygoteSocketIfNeeded(abi);
             // Each query starts with the argument count (1 in this case)
-            state.writer.write("1");
-            state.writer.newLine();
-            state.writer.write("--preload-default");
-            state.writer.newLine();
-            state.writer.flush();
+            state.mZygoteOutputWriter.write("1");
+            state.mZygoteOutputWriter.newLine();
+            state.mZygoteOutputWriter.write("--preload-default");
+            state.mZygoteOutputWriter.newLine();
+            state.mZygoteOutputWriter.flush();
 
-            return (state.inputStream.readInt() == 0);
+            return (state.mZygoteInputStream.readInt() == 0);
         }
     }
 
@@ -763,20 +922,21 @@
      * Try connecting to the Zygote over and over again until we hit a time-out.
      * @param socketName The name of the socket to connect to.
      */
-    public static void waitForConnectionToZygote(String socketName) {
-        final LocalSocketAddress address =
-                new LocalSocketAddress(socketName, LocalSocketAddress.Namespace.RESERVED);
-        waitForConnectionToZygote(address);
+    public static void waitForConnectionToZygote(String zygoteSocketName) {
+        final LocalSocketAddress zygoteSocketAddress =
+                new LocalSocketAddress(zygoteSocketName, LocalSocketAddress.Namespace.RESERVED);
+        waitForConnectionToZygote(zygoteSocketAddress);
     }
 
     /**
      * Try connecting to the Zygote over and over again until we hit a time-out.
      * @param address The name of the socket to connect to.
      */
-    public static void waitForConnectionToZygote(LocalSocketAddress address) {
+    public static void waitForConnectionToZygote(LocalSocketAddress zygoteSocketAddress) {
         for (int n = 20; n >= 0; n--) {
             try {
-                final ZygoteState zs = ZygoteState.connect(address);
+                final ZygoteState zs =
+                        ZygoteState.connect(zygoteSocketAddress, null);
                 zs.close();
                 return;
             } catch (IOException ioe) {
@@ -789,7 +949,8 @@
             } catch (InterruptedException ie) {
             }
         }
-        Slog.wtf(LOG_TAG, "Failed to connect to Zygote through socket " + address.getName());
+        Slog.wtf(LOG_TAG, "Failed to connect to Zygote through socket "
+                + zygoteSocketAddress.getName());
     }
 
     /**
@@ -839,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/os/storage/StorageVolume.java b/core/java/android/os/storage/StorageVolume.java
index df1a713..714a061 100644
--- a/core/java/android/os/storage/StorageVolume.java
+++ b/core/java/android/os/storage/StorageVolume.java
@@ -35,6 +35,7 @@
 
 import java.io.CharArrayWriter;
 import java.io.File;
+import java.util.Locale;
 
 /**
  * Information about a shared/external storage volume for a specific user.
@@ -263,6 +264,11 @@
         return mFsUuid;
     }
 
+    /** {@hide} */
+    public @Nullable String getNormalizedUuid() {
+        return mFsUuid != null ? mFsUuid.toLowerCase(Locale.US) : null;
+    }
+
     /**
      * Parse and return volume UUID as FAT volume ID, or return -1 if unable to
      * parse or UUID is unknown.
diff --git a/core/java/android/os/storage/VolumeInfo.java b/core/java/android/os/storage/VolumeInfo.java
index 8c3aa17..5d310e1 100644
--- a/core/java/android/os/storage/VolumeInfo.java
+++ b/core/java/android/os/storage/VolumeInfo.java
@@ -42,6 +42,7 @@
 import java.io.CharArrayWriter;
 import java.io.File;
 import java.util.Comparator;
+import java.util.Locale;
 import java.util.Objects;
 
 /**
@@ -254,6 +255,10 @@
         return fsUuid;
     }
 
+    public @Nullable String getNormalizedFsUuid() {
+        return fsUuid != null ? fsUuid.toLowerCase(Locale.US) : null;
+    }
+
     @UnsupportedAppUsage
     public int getMountUserId() {
         return mountUserId;
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/provider/CalendarContract.java b/core/java/android/provider/CalendarContract.java
index c167ea1..8bd75d7 100644
--- a/core/java/android/provider/CalendarContract.java
+++ b/core/java/android/provider/CalendarContract.java
@@ -44,6 +44,8 @@
 
 import com.android.internal.util.Preconditions;
 
+import java.util.Set;
+
 /**
  * <p>
  * The contract between the calendar provider and applications. Contains
@@ -217,7 +219,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 +769,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
@@ -1758,9 +1761,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
@@ -1968,10 +1972,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/DeviceConfig.java b/core/java/android/provider/DeviceConfig.java
index a7e6601..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
@@ -94,6 +95,109 @@
     @SystemApi
     public static final String NAMESPACE_NETD_NATIVE = "netd_native";
 
+    /**
+     * Namespace for features related to the ExtServices Notification Assistant.
+     * These features are applied immediately.
+     *
+     * @hide
+     */
+    @SystemApi
+    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
+     */
+    @SystemApi
+    public interface Telephony {
+        String NAMESPACE = "telephony";
+        /**
+         * Whether to apply ramping ringer on incoming phone calls.
+         */
+        String PROPERTY_ENABLE_RAMPING_RINGER = "enable_ramping_ringer";
+        /**
+         * Ringer ramping time in milliseconds.
+         */
+        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 =
@@ -109,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
@@ -133,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)
@@ -159,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)
@@ -178,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)
@@ -215,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(
@@ -318,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/DocumentsContract.java b/core/java/android/provider/DocumentsContract.java
index a323ed1..5f1c560 100644
--- a/core/java/android/provider/DocumentsContract.java
+++ b/core/java/android/provider/DocumentsContract.java
@@ -16,7 +16,6 @@
 
 package android.provider;
 
-import static com.android.internal.util.Preconditions.checkArgument;
 import static com.android.internal.util.Preconditions.checkCollectionElementsNotNull;
 import static com.android.internal.util.Preconditions.checkCollectionNotEmpty;
 
@@ -50,6 +49,8 @@
 import android.os.RemoteException;
 import android.util.Log;
 
+import com.android.internal.util.Preconditions;
+
 import dalvik.system.VMRuntime;
 
 import java.io.File;
@@ -640,6 +641,28 @@
         public static final String COLUMN_MIME_TYPES = "mime_types";
 
         /**
+         * Query arguments supported by this root. This column is optional
+         * and related to {@link #COLUMN_FLAGS} and {@link #FLAG_SUPPORTS_SEARCH}.
+         * If the flags include {@link #FLAG_SUPPORTS_SEARCH}, and the column is
+         * {@code null}, the root is assumed to support {@link #QUERY_ARG_DISPLAY_NAME}
+         * search of {@link Document#COLUMN_DISPLAY_NAME}. Multiple query arguments
+         * can be separated by a newline. For example, a root supporting
+         * {@link #QUERY_ARG_MIME_TYPES} and {@link #QUERY_ARG_DISPLAY_NAME} might
+         * return "android:query-arg-mime-types\nandroid:query-arg-display-name".
+         * <p>
+         * Type: STRING
+         * @see #COLUMN_FLAGS
+         * @see #FLAG_SUPPORTS_SEARCH
+         * @see #QUERY_ARG_DISPLAY_NAME
+         * @see #QUERY_ARG_FILE_SIZE_OVER
+         * @see #QUERY_ARG_LAST_MODIFIED_AFTER
+         * @see #QUERY_ARG_MIME_TYPES
+         * @see DocumentsProvider#querySearchDocuments(String, String[],
+         *      Bundle)
+         */
+        public static final String COLUMN_QUERY_ARGS = "query_args";
+
+        /**
          * MIME type for a root.
          */
         public static final String MIME_TYPE_ITEM = "vnd.android.document/root";
@@ -680,6 +703,8 @@
          *      String)
          * @see DocumentsProvider#querySearchDocuments(String, String,
          *      String[])
+         * @see DocumentsProvider#querySearchDocuments(String, String[],
+         *      Bundle)
          */
         public static final int FLAG_SUPPORTS_SEARCH = 1 << 3;
 
@@ -1109,11 +1134,13 @@
     }
 
     /**
-     * Test if the given URI represents roots backed by {@link DocumentsProvider}.
+     * Test if the given URI represents all roots of the authority
+     * backed by {@link DocumentsProvider}.
      *
      * @see #buildRootsUri(String)
      */
-    public static boolean isRootsUri(Context context, @Nullable Uri uri) {
+    public static boolean isRootsUri(@NonNull Context context, @Nullable Uri uri) {
+        Preconditions.checkNotNull(context, "context can not be null");
         return isRootUri(context, uri, 1 /* pathSize */);
     }
 
@@ -1122,7 +1149,8 @@
      *
      * @see #buildRootUri(String, String)
      */
-    public static boolean isRootUri(Context context, @Nullable Uri uri) {
+    public static boolean isRootUri(@NonNull Context context, @Nullable Uri uri) {
+        Preconditions.checkNotNull(context, "context can not be null");
         return isRootUri(context, uri, 2 /* pathSize */);
     }
 
@@ -1215,6 +1243,7 @@
      * {@hide}
      */
     public static String getSearchDocumentsQuery(@NonNull Bundle bundle) {
+        Preconditions.checkNotNull(bundle, "bundle can not be null");
         return bundle.getString(QUERY_ARG_DISPLAY_NAME, "" /* defaultValue */);
     }
 
@@ -1315,8 +1344,12 @@
      * @return if given document is a descendant of the given parent.
      * @see Root#FLAG_SUPPORTS_IS_CHILD
      */
-    public static boolean isChildDocument(ContentInterface content, Uri parentDocumentUri,
-            Uri childDocumentUri) throws FileNotFoundException {
+    public static boolean isChildDocument(@NonNull ContentInterface content,
+            @NonNull Uri parentDocumentUri, @NonNull Uri childDocumentUri)
+            throws FileNotFoundException {
+        Preconditions.checkNotNull(content, "content can not be null");
+        Preconditions.checkNotNull(parentDocumentUri, "parentDocumentUri can not be null");
+        Preconditions.checkNotNull(childDocumentUri, "childDocumentUri can not be null");
         try {
             final Bundle in = new Bundle();
             in.putParcelable(DocumentsContract.EXTRA_URI, parentDocumentUri);
@@ -1325,7 +1358,7 @@
             final Bundle out = content.call(parentDocumentUri.getAuthority(),
                     METHOD_IS_CHILD_DOCUMENT, null, in);
             if (out == null) {
-                throw new RemoteException("Failed to get a reponse from isChildDocument query.");
+                throw new RemoteException("Failed to get a response from isChildDocument query.");
             }
             if (!out.containsKey(DocumentsContract.EXTRA_RESULT)) {
                 throw new RemoteException("Response did not include result field..");
@@ -1559,8 +1592,10 @@
      * @param documentUri a Document URI
      * @return a Bundle of Bundles.
      */
-    public static Bundle getDocumentMetadata(ContentInterface content, Uri documentUri)
-            throws FileNotFoundException {
+    public static @Nullable Bundle getDocumentMetadata(@NonNull ContentInterface content,
+            @NonNull Uri documentUri) throws FileNotFoundException {
+        Preconditions.checkNotNull(content, "content can not be null");
+        Preconditions.checkNotNull(documentUri, "documentUri can not be null");
         try {
             final Bundle in = new Bundle();
             in.putParcelable(EXTRA_URI, documentUri);
@@ -1595,8 +1630,6 @@
      */
     public static Path findDocumentPath(ContentInterface content, Uri treeUri)
             throws FileNotFoundException {
-        checkArgument(isTreeUri(treeUri), treeUri + " is not a tree uri.");
-
         try {
             final Bundle in = new Bundle();
             in.putParcelable(DocumentsContract.EXTRA_URI, treeUri);
diff --git a/core/java/android/provider/DocumentsProvider.java b/core/java/android/provider/DocumentsProvider.java
index 70c84f8..9b9e2de 100644
--- a/core/java/android/provider/DocumentsProvider.java
+++ b/core/java/android/provider/DocumentsProvider.java
@@ -37,6 +37,7 @@
 
 import android.Manifest;
 import android.annotation.CallSuper;
+import android.annotation.NonNull;
 import android.annotation.Nullable;
 import android.app.AuthenticationRequiredException;
 import android.content.ClipDescription;
@@ -63,6 +64,8 @@
 import android.provider.DocumentsContract.Root;
 import android.util.Log;
 
+import com.android.internal.util.Preconditions;
+
 import libcore.io.IoUtils;
 
 import java.io.FileNotFoundException;
@@ -687,14 +690,17 @@
      *         extras {@link Bundle} when any QUERY_ARG_* value was honored
      *         during the preparation of the results.
      *
+     * @see Root#COLUMN_QUERY_ARGS
      * @see ContentResolver#EXTRA_HONORED_ARGS
      * @see DocumentsContract#EXTRA_LOADING
      * @see DocumentsContract#EXTRA_INFO
      * @see DocumentsContract#EXTRA_ERROR
      */
     @SuppressWarnings("unused")
-    public Cursor querySearchDocuments(String rootId, String[] projection, Bundle queryArgs)
-            throws FileNotFoundException {
+    public Cursor querySearchDocuments(@NonNull String rootId, @Nullable String[] projection,
+            @NonNull Bundle queryArgs) throws FileNotFoundException {
+        Preconditions.checkNotNull(rootId, "rootId can not be null");
+        Preconditions.checkNotNull(queryArgs, "queryArgs can not be null");
         return querySearchDocuments(rootId, DocumentsContract.getSearchDocumentsQuery(queryArgs),
                 projection);
     }
@@ -732,7 +738,7 @@
      * @return a Bundle of Bundles.
      * @see DocumentsContract#getDocumentMetadata(ContentResolver, Uri)
      */
-    public @Nullable Bundle getDocumentMetadata(String documentId)
+    public @Nullable Bundle getDocumentMetadata(@NonNull String documentId)
             throws FileNotFoundException {
         throw new UnsupportedOperationException("Metadata not supported");
     }
diff --git a/core/java/android/provider/MediaStore.java b/core/java/android/provider/MediaStore.java
index 3a49986..f5c442f 100644
--- a/core/java/android/provider/MediaStore.java
+++ b/core/java/android/provider/MediaStore.java
@@ -70,6 +70,8 @@
 import java.io.IOException;
 import java.io.InputStream;
 import java.io.OutputStream;
+import java.util.ArrayList;
+import java.util.Collection;
 import java.util.List;
 import java.util.Objects;
 import java.util.Set;
@@ -87,9 +89,19 @@
     /** 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";
 
     /**
@@ -1224,7 +1236,7 @@
                 if (sv.isPrimary()) {
                     return VOLUME_EXTERNAL;
                 } else {
-                    return checkArgumentVolumeName(sv.getUuid());
+                    return checkArgumentVolumeName(sv.getNormalizedUuid());
                 }
             }
             throw new IllegalStateException("Unknown volume at " + path);
@@ -1564,7 +1576,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);
@@ -2741,7 +2759,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
@@ -2919,7 +2943,7 @@
                 if (vi.isPrimary()) {
                     volumeNames.add(VOLUME_EXTERNAL);
                 } else {
-                    volumeNames.add(vi.getFsUuid());
+                    volumeNames.add(vi.getNormalizedFsUuid());
                 }
             }
         }
@@ -2953,8 +2977,7 @@
         // When not one of the well-known values above, it must be a hex UUID
         for (int i = 0; i < volumeName.length(); i++) {
             final char c = volumeName.charAt(i);
-            if (('a' <= c && c <= 'f') || ('A' <= c && c <= 'F')
-                    || ('0' <= c && c <= '9') || (c == '-')) {
+            if (('a' <= c && c <= 'f') || ('0' <= c && c <= '9') || (c == '-')) {
                 continue;
             } else {
                 throw new IllegalArgumentException("Invalid volume name: " + volumeName);
@@ -2963,23 +2986,26 @@
         return volumeName;
     }
 
-    /** {@hide} */
+    /**
+     * Return path where the given volume is mounted. Not valid for
+     * {@link #VOLUME_INTERNAL}.
+     *
+     * @hide
+     */
     public static @NonNull File getVolumePath(@NonNull String volumeName)
             throws FileNotFoundException {
         if (TextUtils.isEmpty(volumeName)) {
             throw new IllegalArgumentException();
         }
 
-        if (VOLUME_INTERNAL.equals(volumeName)) {
-            return Environment.getDataDirectory();
-        } else if (VOLUME_EXTERNAL.equals(volumeName)) {
+        if (VOLUME_EXTERNAL.equals(volumeName)) {
             return Environment.getExternalStorageDirectory();
         }
 
         final StorageManager sm = AppGlobals.getInitialApplication()
                 .getSystemService(StorageManager.class);
         for (VolumeInfo vi : sm.getVolumes()) {
-            if (Objects.equals(vi.getFsUuid(), volumeName)) {
+            if (Objects.equals(vi.getNormalizedFsUuid(), volumeName)) {
                 final File path = vi.getPathForUser(UserHandle.myUserId());
                 if (path != null) {
                     return path;
@@ -2992,6 +3018,33 @@
     }
 
     /**
+     * Return paths that should be scanned for the given volume.
+     *
+     * @hide
+     */
+    public static @NonNull Collection<File> getVolumeScanPaths(@NonNull String volumeName)
+            throws FileNotFoundException {
+        if (TextUtils.isEmpty(volumeName)) {
+            throw new IllegalArgumentException();
+        }
+
+        final ArrayList<File> res = new ArrayList<>();
+        if (VOLUME_INTERNAL.equals(volumeName)) {
+            res.add(new File(Environment.getRootDirectory(), "media"));
+            res.add(new File(Environment.getOemDirectory(), "media"));
+            res.add(new File(Environment.getProductDirectory(), "media"));
+        } else {
+            res.add(getVolumePath(volumeName));
+            final UserManager um = AppGlobals.getInitialApplication()
+                    .getSystemService(UserManager.class);
+            if (VOLUME_EXTERNAL.equals(volumeName) && um.isDemoUser()) {
+                res.add(Environment.getDataPreloadsMediaDirectory());
+            }
+        }
+        return res;
+    }
+
+    /**
      * Uri for querying the state of the media scanner.
      */
     public static Uri getMediaScannerUri() {
diff --git a/core/java/android/provider/Settings.java b/core/java/android/provider/Settings.java
index 516f49c..852b65a 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;
@@ -3239,8 +3239,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
@@ -3300,6 +3300,14 @@
         public static final int SCREEN_BRIGHTNESS_MODE_AUTOMATIC = 1;
 
         /**
+         * Control whether to enable adaptive sleep mode.
+         * @hide
+         */
+        public static final String ADAPTIVE_SLEEP = "adaptive_sleep";
+
+        private static final Validator ADAPTIVE_SLEEP_VALIDATOR = BOOLEAN_VALIDATOR;
+
+        /**
          * Control whether the process CPU usage meter should be shown.
          *
          * @deprecated This functionality is no longer available as of
@@ -4232,6 +4240,7 @@
             SCREEN_BRIGHTNESS_MODE,
             SCREEN_AUTO_BRIGHTNESS_ADJ,
             SCREEN_BRIGHTNESS_FOR_VR,
+            ADAPTIVE_SLEEP,
             VIBRATE_INPUT_DEVICES,
             MODE_RINGER_STREAMS_AFFECTED,
             TEXT_AUTO_REPLACE,
@@ -4307,6 +4316,7 @@
             PUBLIC_SETTINGS.add(SCREEN_BRIGHTNESS);
             PUBLIC_SETTINGS.add(SCREEN_BRIGHTNESS_FOR_VR);
             PUBLIC_SETTINGS.add(SCREEN_BRIGHTNESS_MODE);
+            PUBLIC_SETTINGS.add(ADAPTIVE_SLEEP);
             PUBLIC_SETTINGS.add(MODE_RINGER_STREAMS_AFFECTED);
             PUBLIC_SETTINGS.add(MUTE_STREAMS_AFFECTED);
             PUBLIC_SETTINGS.add(VIBRATE_ON);
@@ -4411,6 +4421,7 @@
             VALIDATORS.put(SCREEN_OFF_TIMEOUT, SCREEN_OFF_TIMEOUT_VALIDATOR);
             VALIDATORS.put(SCREEN_BRIGHTNESS_FOR_VR, SCREEN_BRIGHTNESS_FOR_VR_VALIDATOR);
             VALIDATORS.put(SCREEN_BRIGHTNESS_MODE, SCREEN_BRIGHTNESS_MODE_VALIDATOR);
+            VALIDATORS.put(ADAPTIVE_SLEEP, ADAPTIVE_SLEEP_VALIDATOR);
             VALIDATORS.put(MODE_RINGER_STREAMS_AFFECTED, MODE_RINGER_STREAMS_AFFECTED_VALIDATOR);
             VALIDATORS.put(MUTE_STREAMS_AFFECTED, MUTE_STREAMS_AFFECTED_VALIDATOR);
             VALIDATORS.put(VIBRATE_ON, VIBRATE_ON_VALIDATOR);
@@ -7791,6 +7802,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
@@ -8225,6 +8239,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
          */
@@ -8410,6 +8434,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.
          *
@@ -8505,6 +8550,7 @@
             ASSIST_GESTURE_WAKE_ENABLED,
             VR_DISPLAY_MODE,
             NOTIFICATION_BADGING,
+            NOTIFICATION_DISMISS_RTL,
             QS_AUTO_ADDED_TILES,
             SCREENSAVER_ENABLED,
             SCREENSAVER_COMPONENTS,
@@ -8533,6 +8579,8 @@
             LOCK_SCREEN_WHEN_TRUST_LOST,
             SKIP_GESTURE,
             SILENCE_GESTURE,
+            THEME_CUSTOMIZATION_OVERLAY_PACKAGES,
+            AWARE_ENABLED,
         };
 
         /**
@@ -8665,6 +8713,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);
@@ -8703,6 +8752,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);
         }
 
         /**
@@ -9441,23 +9493,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.
          *
@@ -9482,6 +9517,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
          */
@@ -11495,6 +11538,20 @@
         public static final String DISPLAY_PANEL_LPM = "display_panel_lpm";
 
         /**
+         * App time limit usage source setting.
+         * This controls which app in a task will be considered the source of usage when
+         * calculating app usage time limits.
+         *
+         * 1 -> task root app
+         * 2 -> current app
+         * Any other value defaults to task root app.
+         *
+         * Need to reboot the device for this setting to take effect.
+         * @hide
+         */
+        public static final String APP_TIME_LIMIT_USAGE_SOURCE = "app_time_limit_usage_source";
+
+        /**
          * App standby (app idle) specific settings.
          * This is encoded as a key=value list, separated by commas. Ex:
          * <p>
@@ -11856,6 +11913,28 @@
         public static final String KEEP_PROFILE_IN_BACKGROUND = "keep_profile_in_background";
 
         /**
+         * The default time in ms within which a subsequent connection from an always allowed system
+         * is allowed to reconnect without user interaction.
+         *
+         * @hide
+         */
+        public static final long DEFAULT_ADB_ALLOWED_CONNECTION_TIME = 604800000;
+
+        /**
+         * When the user first connects their device to a system a prompt is displayed to allow
+         * the adb connection with an option to 'Always allow' connections from this system. If the
+         * user selects this always allow option then the connection time is stored for the system.
+         * This setting is the time in ms within which a subsequent connection from an always
+         * allowed system is allowed to reconnect without user interaction.
+         *
+         * Type: long
+         *
+         * @hide
+         */
+        public static final String ADB_ALLOWED_CONNECTION_TIME =
+                "adb_allowed_connection_time";
+
+        /**
          * Get the key that retrieves a bluetooth headset's priority.
          * @hide
          */
@@ -12074,6 +12153,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
@@ -12103,6 +12189,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
@@ -12147,6 +12241,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
@@ -12158,7 +12277,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);
 
@@ -12912,28 +13030,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.
          *
@@ -12956,6 +13052,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.
          *
@@ -13149,6 +13256,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,
@@ -13187,6 +13296,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);
@@ -13701,6 +13814,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
@@ -13917,11 +14043,12 @@
          * The following keys are supported:
          *
          * <pre>
-         * enabled                         (boolean)
-         * requires_targeting_p            (boolean)
-         * max_squeeze_remeasure_attempts  (int)
-         * edit_choices_before_sending     (boolean)
-         * show_in_heads_up                (boolean)
+         * enabled                           (boolean)
+         * requires_targeting_p              (boolean)
+         * max_squeeze_remeasure_attempts    (int)
+         * edit_choices_before_sending       (boolean)
+         * show_in_heads_up                  (boolean)
+         * min_num_system_generated_replies  (int)
          * </pre>
          * @see com.android.systemui.statusbar.policy.SmartReplyConstants
          * @hide
@@ -14153,6 +14280,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";
     }
 
     /**
@@ -14476,6 +14614,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/service/autofill/augmented/AugmentedAutofillService.java b/core/java/android/service/autofill/augmented/AugmentedAutofillService.java
index aaba85b..8e0f522 100644
--- a/core/java/android/service/autofill/augmented/AugmentedAutofillService.java
+++ b/core/java/android/service/autofill/augmented/AugmentedAutofillService.java
@@ -67,7 +67,7 @@
 
     private static final String TAG = AugmentedAutofillService.class.getSimpleName();
 
-    // TODO(b/111330312): STOPSHIP use dynamic value, or change to false
+    // TODO(b/123100811): STOPSHIP use dynamic value, or change to false
     static final boolean DEBUG = true;
     static final boolean VERBOSE = false;
 
@@ -127,8 +127,6 @@
         return false;
     }
 
-    // TODO(b/111330312): add methods to disable autofill per app / activity?
-
     /**
      * Asks the service to handle an "augmented" autofill request.
      *
@@ -175,12 +173,11 @@
                     focusedValue, requestTime, callback);
             mAutofillProxies.put(sessionId,  proxy);
         } else {
-            // TODO(b/111330312): figure out if it's ok to reuse the proxy; add logging
-            // TODO(b/111330312): also make sure to cover scenario on CTS test
+            // TODO(b/123099468): figure out if it's ok to reuse the proxy; add logging
             if (DEBUG) Log.d(TAG, "Reusing proxy for session " + sessionId);
             proxy.update(focusedId, focusedValue);
         }
-        // TODO(b/111330312): set cancellation signal
+        // TODO(b/123101711): set cancellation signal
         final CancellationSignal cancellationSignal = null;
         onFillRequest(new FillRequest(proxy), cancellationSignal, new FillController(proxy),
                 new FillCallback(proxy));
@@ -193,7 +190,7 @@
                 final int sessionId = mAutofillProxies.keyAt(i);
                 final AutofillProxy proxy = mAutofillProxies.valueAt(i);
                 if (proxy == null) {
-                    // TODO(b/111330312): this might be fine, in which case we should logv it
+                    // TODO(b/123100811): this might be fine, in which case we should logv it
                     Log.w(TAG, "No proxy for session " + sessionId);
                     return;
                 }
@@ -303,7 +300,7 @@
             this.mFocusedId = focusedId;
             this.mFocusedValue = focusedValue;
             this.mRequestTime = requestTime;
-            // TODO(b/111330312): linkToDeath
+            // TODO(b/123099468): linkToDeath
         }
 
         @NonNull
@@ -366,7 +363,7 @@
 
         private void update(@NonNull AutofillId focusedId, @NonNull AutofillValue focusedValue) {
             synchronized (mLock) {
-                // TODO(b/111330312): should we close the popupwindow if the focused id changed?
+                // TODO(b/123099468): should we close the popupwindow if the focused id changed?
                 mFocusedId = focusedId;
                 mFocusedValue = focusedValue;
             }
@@ -425,7 +422,7 @@
                 default:
                     Slog.w(TAG, "invalid event reported: " + event);
             }
-            // TODO(b/111330312): log metrics as well
+            // TODO(b/122858578): log metrics as well
         }
 
         public void dump(@NonNull String prefix, @NonNull PrintWriter pw) {
diff --git a/core/java/android/service/autofill/augmented/FillCallback.java b/core/java/android/service/autofill/augmented/FillCallback.java
index bfb4aad..f2a7a35 100644
--- a/core/java/android/service/autofill/augmented/FillCallback.java
+++ b/core/java/android/service/autofill/augmented/FillCallback.java
@@ -59,7 +59,8 @@
         if (fillWindow != null) {
             fillWindow.show();
         }
-        // TODO(b/111330312): properly implement on server-side by updating the Session state
-        // accordingly (and adding CTS tests)
+        // TODO(b/123099468): must notify the server so it can update the session state to avoid
+        // showing conflicting UIs (for example, if a new request is made to the main autofill
+        // service and it now wants to show something).
     }
 }
diff --git a/core/java/android/service/autofill/augmented/FillRequest.java b/core/java/android/service/autofill/augmented/FillRequest.java
index dad5067..af9905f 100644
--- a/core/java/android/service/autofill/augmented/FillRequest.java
+++ b/core/java/android/service/autofill/augmented/FillRequest.java
@@ -29,7 +29,7 @@
  * @hide
  */
 @SystemApi
-// TODO(b/111330312): pass a requestId and/or sessionId
+// TODO(b/123100811): pass a requestId and/or sessionId?
 @TestApi
 // TODO(b/122654591): @TestApi is needed because CtsAutoFillServiceTestCases hosts the service
 // in the same package as the test, and that module is compiled with SDK=test_current
diff --git a/core/java/android/service/autofill/augmented/FillResponse.java b/core/java/android/service/autofill/augmented/FillResponse.java
index 5285132..f1e904a7 100644
--- a/core/java/android/service/autofill/augmented/FillResponse.java
+++ b/core/java/android/service/autofill/augmented/FillResponse.java
@@ -19,8 +19,6 @@
 import android.annotation.Nullable;
 import android.annotation.SystemApi;
 import android.annotation.TestApi;
-import android.os.Parcel;
-import android.os.Parcelable;
 import android.view.autofill.AutofillId;
 
 import java.util.List;
@@ -34,7 +32,7 @@
 @TestApi
 //TODO(b/122654591): @TestApi is needed because CtsAutoFillServiceTestCases hosts the service
 //in the same package as the test, and that module is compiled with SDK=test_current
-public final class FillResponse implements Parcelable {
+public final class FillResponse {
 
     private final FillWindow mFillWindow;
 
@@ -70,8 +68,8 @@
          * @return this builder
          */
         public Builder setFillWindow(@NonNull FillWindow fillWindow) {
-            // TODO(b/111330312): implement / check not null / unit test
-            // TODO(b/111330312): throw exception if FillWindow not updated yet
+            // TODO(b/123100712): check not null / unit test / throw exception if FillWindow not
+            // updated yet
             mFillWindow = fillWindow;
             return this;
         }
@@ -85,7 +83,7 @@
          * @return this builder
          */
         public Builder setIgnoredIds(@NonNull List<AutofillId> ids) {
-            // TODO(b/111330312): implement / check not null / unit test
+            // TODO(b/123100695): implement / check not null / unit test
             return this;
         }
 
@@ -102,37 +100,10 @@
          * @return A built response.
          */
         public FillResponse build() {
-            // TODO(b/111330312): check conditions / add unit test
+            // TODO(b/123100712): check conditions / add unit test
             return new FillResponse(this);
         }
-
-        // TODO(b/111330312): add methods to disable app / activity, either here or on manager
     }
 
-    // TODO(b/111330312): implement to String
-
-    @Override
-    public int describeContents() {
-        return 0;
-    }
-
-    @Override
-    public void writeToParcel(Parcel parcel, int flags) {
-        // TODO(b/111330312): implement
-    }
-
-    public static final Parcelable.Creator<FillResponse> CREATOR =
-            new Parcelable.Creator<FillResponse>() {
-
-                @Override
-                public FillResponse createFromParcel(Parcel parcel) {
-                    // TODO(b/111330312): implement
-                    return null;
-                }
-
-                @Override
-                public FillResponse[] newArray(int size) {
-                    return new FillResponse[size];
-                }
-    };
+    // TODO(b/123100811): implement to String
 }
diff --git a/core/java/android/service/autofill/augmented/FillWindow.java b/core/java/android/service/autofill/augmented/FillWindow.java
index 51b0f01..40e3a12 100644
--- a/core/java/android/service/autofill/augmented/FillWindow.java
+++ b/core/java/android/service/autofill/augmented/FillWindow.java
@@ -20,7 +20,6 @@
 
 import static com.android.internal.util.function.pooled.PooledLambda.obtainMessage;
 
-import android.annotation.LongDef;
 import android.annotation.NonNull;
 import android.annotation.SystemApi;
 import android.annotation.TestApi;
@@ -42,8 +41,6 @@
 import dalvik.system.CloseGuard;
 
 import java.io.PrintWriter;
-import java.lang.annotation.Retention;
-import java.lang.annotation.RetentionPolicy;
 
 /**
  * Handle to a window used to display the augmented autofill UI.
@@ -71,18 +68,6 @@
 public final class FillWindow implements AutoCloseable {
     private static final String TAG = "FillWindow";
 
-    /** Indicates the data being shown is a physical address */
-    public static final long FLAG_METADATA_ADDRESS = 0x1;
-
-    // TODO(b/111330312): add more flags
-
-    /** @hide */
-    @LongDef(prefix = { "FLAG" }, value = {
-            FLAG_METADATA_ADDRESS,
-    })
-    @Retention(RetentionPolicy.SOURCE)
-    @interface Flags{}
-
     private final Object mLock = new Object();
     private final CloseGuard mCloseGuard = CloseGuard.get();
 
@@ -108,29 +93,22 @@
      *
      * @param rootView new root view
      * @param area coordinates to render the view.
-     * @param flags optional flags such as metadata of what will be rendered in the window. The
-     * Smart Suggestion host might decide whether or not to render the UI based on them.
+     * @param flags currently not used.
      *
      * @return boolean whether the window was updated or not.
      *
      * @throws IllegalArgumentException if the area is not compatible with this window
      */
-    public boolean update(@NonNull Area area, @NonNull View rootView, @Flags long flags) {
+    public boolean update(@NonNull Area area, @NonNull View rootView, long flags) {
         if (DEBUG) {
             Log.d(TAG, "Updating " + area + " + with " + rootView);
         }
-        // TODO(b/111330312): add test case for null
+        // TODO(b/123100712): add test case for null
         Preconditions.checkNotNull(area);
         Preconditions.checkNotNull(rootView);
-        // TODO(b/111330312): must check the area is a valid object returned by
+        // TODO(b/123100712): must check the area is a valid object returned by
         // SmartSuggestionParams, throw IAE if not
 
-        // TODO(b/111330312): must some how pass metadata to the SmartSuggestiongs provider
-
-
-        // TODO(b/111330312): use a SurfaceControl approach; for now, we're manually creating
-        // the window underneath the existing view.
-
         final PresentationParams smartSuggestion = area.proxy.getSmartSuggestionParams();
         if (smartSuggestion == null) {
             Log.w(TAG, "No SmartSuggestionParams");
@@ -148,12 +126,12 @@
 
             mProxy = area.proxy;
 
-            // TODO(b/111330312): once we have the SurfaceControl approach, we should update the
+            // TODO(b/123227534): once we have the SurfaceControl approach, we should update the
             // window instead of destroying. In fact, it might be better to allocate a full window
             // initially, which is transparent (and let touches get through) everywhere but in the
             // rect boundaries.
 
-            // TODO(b/111330312): make sure all touch events are handled, window is always closed,
+            // TODO(b/123099468): make sure all touch events are handled, window is always closed,
             // etc.
 
             mWm = rootView.getContext().getSystemService(WindowManager.class);
@@ -181,7 +159,7 @@
 
     /** @hide */
     void show() {
-        // TODO(b/111330312): check if updated first / throw exception
+        // TODO(b/123100712): check if updated first / throw exception
         if (DEBUG) Log.d(TAG, "show()");
         synchronized (mLock) {
             checkNotDestroyedLocked();
diff --git a/core/java/android/service/autofill/augmented/IFillCallback.aidl b/core/java/android/service/autofill/augmented/IFillCallback.aidl
index dac7590..2b072664 100644
--- a/core/java/android/service/autofill/augmented/IFillCallback.aidl
+++ b/core/java/android/service/autofill/augmented/IFillCallback.aidl
@@ -24,8 +24,7 @@
  * @hide
  */
 interface IFillCallback {
-    // TODO(b/111330312): add cancellation (after we have CTS tests, so we can test it)
+    // TODO(b/123101711): add cancellation (after we have CTS tests, so we can test it)
 //    void onCancellable(in ICancellationSignal cancellation);
-    // TODO(b/111330312): might need to pass the response (once IME implements Smart Suggestions)
     void onSuccess();
 }
diff --git a/core/java/android/service/autofill/augmented/PresentationParams.java b/core/java/android/service/autofill/augmented/PresentationParams.java
index b60064e..1fb9032 100644
--- a/core/java/android/service/autofill/augmented/PresentationParams.java
+++ b/core/java/android/service/autofill/augmented/PresentationParams.java
@@ -190,7 +190,7 @@
          */
         @Nullable
         public Area getSubArea(@NonNull Rect bounds) {
-            // TODO(b/111330312): implement / check boundaries / throw IAE / add unit test
+            // TODO(b/123100712): implement / check boundaries / throw IAE / add unit test
             return null;
         }
 
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/euicc/EuiccService.java b/core/java/android/service/euicc/EuiccService.java
index 4dc10cd..ffb524d 100644
--- a/core/java/android/service/euicc/EuiccService.java
+++ b/core/java/android/service/euicc/EuiccService.java
@@ -103,10 +103,23 @@
      */
     public static final String ACTION_MANAGE_EMBEDDED_SUBSCRIPTIONS =
             "android.service.euicc.action.MANAGE_EMBEDDED_SUBSCRIPTIONS";
+
     /** @see android.telephony.euicc.EuiccManager#ACTION_PROVISION_EMBEDDED_SUBSCRIPTION */
     public static final String ACTION_PROVISION_EMBEDDED_SUBSCRIPTION =
             "android.service.euicc.action.PROVISION_EMBEDDED_SUBSCRIPTION";
 
+    /** @see android.telephony.euicc.EuiccManager#ACTION_TOGGLE_SUBSCRIPTION_PRIVILEGED */
+    public static final String ACTION_TOGGLE_SUBSCRIPTION_PRIVILEGED =
+            "android.service.euicc.action.TOGGLE_SUBSCRIPTION_PRIVILEGED";
+
+    /** @see android.telephony.euicc.EuiccManager#ACTION_DELETE_SUBSCRIPTION_PRIVILEGED */
+    public static final String ACTION_DELETE_SUBSCRIPTION_PRIVILEGED =
+            "android.service.euicc.action.DELETE_SUBSCRIPTION_PRIVILEGED";
+
+    /** @see android.telephony.euicc.EuiccManager#ACTION_RENAME_SUBSCRIPTION_PRIVILEGED */
+    public static final String ACTION_RENAME_SUBSCRIPTION_PRIVILEGED =
+            "android.service.euicc.action.RENAME_SUBSCRIPTION_PRIVILEGED";
+
     // LUI resolution actions. These are called by the platform to resolve errors in situations that
     // require user interaction.
     // TODO(b/33075886): Define extras for any input parameters to these dialogs once they are
diff --git a/core/java/android/service/notification/Adjustment.java b/core/java/android/service/notification/Adjustment.java
index de532b7..b6788f5 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";
     /**
@@ -112,7 +109,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/service/vr/IVrManager.aidl b/core/java/android/service/vr/IVrManager.aidl
index f7acfc5..b0269e3 100644
--- a/core/java/android/service/vr/IVrManager.aidl
+++ b/core/java/android/service/vr/IVrManager.aidl
@@ -110,13 +110,5 @@
      * @param standy True if the device is entering standby, false if it's exiting standby.
      */
     void setStandbyEnabled(boolean standby);
-
-    /**
-     * Start VR Input method for the given packageName in {@param componentName}.
-     * This method notifies InputMethodManagerService to use VR IME instead of
-     * regular phone IME.
-     */
-    void setVrInputMethod(in ComponentName componentName);
-
 }
 
diff --git a/core/java/android/service/wallpaper/WallpaperService.java b/core/java/android/service/wallpaper/WallpaperService.java
index 0d7223d..b197c8a 100644
--- a/core/java/android/service/wallpaper/WallpaperService.java
+++ b/core/java/android/service/wallpaper/WallpaperService.java
@@ -784,8 +784,11 @@
                         // only internal implementations like ImageWallpaper
                         DisplayInfo displayInfo = new DisplayInfo();
                         mDisplay.getDisplayInfo(displayInfo);
-                        mLayout.width = Math.max(displayInfo.logicalWidth, myWidth);
-                        mLayout.height = Math.max(displayInfo.logicalHeight, myHeight);
+                        final float layoutScale = Math.max(
+                                (float) displayInfo.logicalHeight / (float) myHeight,
+                                (float) displayInfo.logicalWidth / (float) myWidth);
+                        mLayout.height = (int) (myHeight * layoutScale);
+                        mLayout.width = (int) (myWidth * layoutScale);
                         mWindowFlags |= WindowManager.LayoutParams.FLAG_SCALED;
                     }
 
diff --git a/core/java/android/util/DocumentsStatsLog.java b/core/java/android/util/DocumentsStatsLog.java
new file mode 100644
index 0000000..f483944
--- /dev/null
+++ b/core/java/android/util/DocumentsStatsLog.java
@@ -0,0 +1,168 @@
+/*
+ * 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 android.annotation.SystemApi;
+import android.provider.DocumentsContract;
+import android.provider.DocumentsProvider;
+
+/**
+ * DocumentsStatsLog provides APIs to send DocumentsUI related events to statsd.
+ * @hide
+ */
+@SystemApi
+public class DocumentsStatsLog {
+
+    private DocumentsStatsLog() {}
+
+    /**
+     * Logs when DocumentsUI is started, and how. Call this when DocumentsUI first starts up.
+     *
+     * @param action action that launches DocumentsUI.
+     * @param hasInitialUri is DocumentsUI launched with
+     *                      {@link DocumentsContract#EXTRA_INITIAL_URI}.
+     * @param mimeType the requested mime type.
+     * @param rootUri the resolved rootUri, or {@code null} if the provider doesn't
+     *                support {@link DocumentsProvider#findDocumentPath(String, String)}
+     */
+    public static void logActivityLaunch(
+            int action, boolean hasInitialUri, int mimeType, int rootUri) {
+        StatsLog.write(StatsLog.DOCS_UI_LAUNCH_REPORTED, action, hasInitialUri, mimeType, rootUri);
+    }
+
+    /**
+     * Logs root visited event.
+     *
+     * @param scope whether it's in FILES or PICKER mode.
+     * @param root the root that user visited
+     */
+    public static void logRootVisited(int scope, int root) {
+        StatsLog.write(StatsLog.DOCS_UI_ROOT_VISITED, scope, root);
+    }
+
+    /**
+     * Logs file operation stats. Call this when a file operation has completed.
+     *
+     * @param provider whether it's system or external provider
+     * @param fileOp the file operation
+     */
+    public static void logFileOperation(int provider, int fileOp) {
+        StatsLog.write(StatsLog.DOCS_UI_PROVIDER_FILE_OP, provider, fileOp);
+    }
+
+    /**
+     * Logs file operation stats. Call this when a copy/move operation has completed with a specific
+     * mode.
+     *
+     * @param fileOp copy or move file operation
+     * @param mode the mode for copy and move operation
+     */
+    public static void logFileOperationCopyMoveMode(int fileOp, int mode) {
+        StatsLog.write(StatsLog.DOCS_UI_FILE_OP_COPY_MOVE_MODE_REPORTED, fileOp, mode);
+    }
+
+    /**
+     * Logs file sub operation stats. Call this when a file operation has failed.
+     *
+     * @param authority the authority of the source document
+     * @param subOp the sub-file operation
+     */
+    public static void logFileOperationFailure(int authority, int subOp) {
+        StatsLog.write(StatsLog.DOCS_UI_FILE_OP_FAILURE, authority, subOp);
+    }
+
+    /**
+     * Logs the cancellation of a file operation. Call this when a job is canceled
+     *
+     * @param fileOp the file operation.
+     */
+    public static void logFileOperationCanceled(int fileOp) {
+        StatsLog.write(StatsLog.DOCS_UI_FILE_OP_CANCELED, fileOp);
+    }
+
+    /**
+     * Logs startup time in milliseconds.
+     *
+     * @param startupMs
+     */
+    public static void logStartupMs(int startupMs) {
+        StatsLog.write(StatsLog.DOCS_UI_STARTUP_MS, startupMs);
+    }
+
+    /**
+     * Logs the action that was started by user.
+     *
+     * @param userAction
+     */
+    public static void logUserAction(int userAction) {
+        StatsLog.write(StatsLog.DOCS_UI_USER_ACTION_REPORTED, userAction);
+    }
+
+    /**
+     * Logs the invalid type when invalid scoped access is requested.
+     *
+     * @param type the type of invalid scoped access request.
+     */
+    public static void logInvalidScopedAccessRequest(int type) {
+        StatsLog.write(StatsLog.DOCS_UI_INVALID_SCOPED_ACCESS_REQUEST, type);
+    }
+
+    /**
+     * Logs the package name that launches docsui picker mode.
+     *
+     * @param packageName
+     */
+    public static void logPickerLaunchedFrom(String packageName) {
+        StatsLog.write(StatsLog.DOCS_UI_PICKER_LAUNCHED_FROM_REPORTED, packageName);
+    }
+
+    /**
+     * Logs the search type.
+     *
+     * @param searchType
+     */
+    public static void logSearchType(int searchType) {
+        StatsLog.write(StatsLog.DOCS_UI_SEARCH_TYPE_REPORTED, searchType);
+    }
+
+    /**
+     * Logs the search mode.
+     *
+     * @param searchMode
+     */
+    public static void logSearchMode(int searchMode) {
+        StatsLog.write(StatsLog.DOCS_UI_SEARCH_MODE_REPORTED, searchMode);
+    }
+
+    /**
+     * Logs the pick result information.
+     *
+     * @param actionCount total user action count during pick process.
+     * @param duration total time spent on pick process.
+     * @param fileCount number of picked files.
+     * @param isSearching are the picked files found by search.
+     * @param root the root where the picked files located.
+     * @param mimeType the mime type of the picked file. Only for single-select case.
+     * @param repeatedlyPickTimes number of times that the file has been picked before. Only for
+     *                            single-select case.
+     */
+    public static void logFilePick(int actionCount, long duration, int fileCount,
+            boolean isSearching, int root, int mimeType, int repeatedlyPickTimes) {
+        StatsLog.write(StatsLog.DOCS_UI_PICK_RESULT_REPORTED, actionCount, duration, fileCount,
+                isSearching, root, mimeType, repeatedlyPickTimes);
+    }
+}
diff --git a/core/java/android/util/FeatureFlagUtils.java b/core/java/android/util/FeatureFlagUtils.java
index 0edcb3d..4052ed7 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;
@@ -53,13 +54,14 @@
         DEFAULT_FLAGS.put("settings_seamless_transfer", "false");
         DEFAULT_FLAGS.put("settings_slice_injection", "false");
         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/GestureDetector.java b/core/java/android/view/GestureDetector.java
index 33b3ff4f..7d9ec70 100644
--- a/core/java/android/view/GestureDetector.java
+++ b/core/java/android/view/GestureDetector.java
@@ -237,6 +237,16 @@
     private static final int LONG_PRESS = 2;
     private static final int TAP = 3;
 
+    /**
+     * If a MotionEvent has CLASSIFICATION_AMBIGUOUS_GESTURE set, then certain actions, such as
+     * scrolling, will be inhibited. However, to account for the possibility of incorrect
+     * classification, the default scrolling will only be inhibited if the gesture moves beyond
+     * (default touch slop * AMBIGUOUS_GESTURE_MULTIPLIER). Likewise, the default long press
+     * timeout will be increased for some situations where the default behaviour
+     * is to cancel it.
+     */
+    private static final int AMBIGUOUS_GESTURE_MULTIPLIER = 2;
+
     private final Handler mHandler;
     @UnsupportedAppUsage
     private final OnGestureListener mListener;
@@ -292,27 +302,27 @@
         @Override
         public void handleMessage(Message msg) {
             switch (msg.what) {
-            case SHOW_PRESS:
-                mListener.onShowPress(mCurrentDownEvent);
-                break;
-                
-            case LONG_PRESS:
-                dispatchLongPress();
-                break;
-                
-            case TAP:
-                // If the user's finger is still down, do not count it as a tap
-                if (mDoubleTapListener != null) {
-                    if (!mStillDown) {
-                        mDoubleTapListener.onSingleTapConfirmed(mCurrentDownEvent);
-                    } else {
-                        mDeferConfirmSingleTap = true;
-                    }
-                }
-                break;
+                case SHOW_PRESS:
+                    mListener.onShowPress(mCurrentDownEvent);
+                    break;
 
-            default:
-                throw new RuntimeException("Unknown message " + msg); //never
+                case LONG_PRESS:
+                    dispatchLongPress();
+                    break;
+
+                case TAP:
+                    // If the user's finger is still down, do not count it as a tap
+                    if (mDoubleTapListener != null) {
+                        if (!mStillDown) {
+                            mDoubleTapListener.onSingleTapConfirmed(mCurrentDownEvent);
+                        } else {
+                            mDeferConfirmSingleTap = true;
+                        }
+                    }
+                    break;
+
+                default:
+                    throw new RuntimeException("Unknown message " + msg); //never
             }
         }
     }
@@ -427,7 +437,7 @@
         if (context == null) {
             //noinspection deprecation
             touchSlop = ViewConfiguration.getTouchSlop();
-            doubleTapTouchSlop = touchSlop; // Hack rather than adding a hiden method for this
+            doubleTapTouchSlop = touchSlop; // Hack rather than adding a hidden method for this
             doubleTapSlop = ViewConfiguration.getDoubleTapSlop();
             //noinspection deprecation
             mMinimumFlingVelocity = ViewConfiguration.getMinimumFlingVelocity();
@@ -605,6 +615,10 @@
                 if (mInLongPress || mInContextClick) {
                     break;
                 }
+
+                final int motionClassification = ev.getClassification();
+                final boolean hasPendingLongPress = mHandler.hasMessages(LONG_PRESS);
+
                 final float scrollX = mLastFocusX - focusX;
                 final float scrollY = mLastFocusY - focusY;
                 if (mIsDoubleTapping) {
@@ -615,6 +629,31 @@
                     final int deltaY = (int) (focusY - mDownFocusY);
                     int distance = (deltaX * deltaX) + (deltaY * deltaY);
                     int slopSquare = isGeneratedGesture ? 0 : mTouchSlopSquare;
+
+                    final boolean ambiguousGesture =
+                            motionClassification == MotionEvent.CLASSIFICATION_AMBIGUOUS_GESTURE;
+                    final boolean shouldInhibitDefaultAction =
+                            hasPendingLongPress && ambiguousGesture;
+                    if (shouldInhibitDefaultAction) {
+                        // Inhibit default long press
+                        if (distance > slopSquare) {
+                            // The default action here is to remove long press. But if the touch
+                            // slop below gets increased, and we never exceed the modified touch
+                            // slop while still receiving AMBIGUOUS_GESTURE, we risk that *nothing*
+                            // will happen in response to user input. To prevent this,
+                            // reschedule long press with a modified timeout.
+                            mHandler.removeMessages(LONG_PRESS);
+                            final int longPressTimeout = ViewConfiguration.getLongPressTimeout();
+                            mHandler.sendEmptyMessageAtTime(LONG_PRESS, ev.getDownTime()
+                                    + longPressTimeout * AMBIGUOUS_GESTURE_MULTIPLIER);
+                        }
+                        // Inhibit default scroll. If a gesture is ambiguous, we prevent scroll
+                        // until the gesture is resolved.
+                        // However, for safety, simply increase the touch slop in case the
+                        // classification is erroneous. Since the value is squared, multiply twice.
+                        slopSquare *= AMBIGUOUS_GESTURE_MULTIPLIER * AMBIGUOUS_GESTURE_MULTIPLIER;
+                    }
+
                     if (distance > slopSquare) {
                         handled = mListener.onScroll(mCurrentDownEvent, ev, scrollX, scrollY);
                         mLastFocusX = focusX;
@@ -633,6 +672,12 @@
                     mLastFocusX = focusX;
                     mLastFocusY = focusY;
                 }
+                final boolean deepPress =
+                        motionClassification == MotionEvent.CLASSIFICATION_DEEP_PRESS;
+                if (deepPress && hasPendingLongPress) {
+                    mHandler.removeMessages(LONG_PRESS);
+                    mHandler.sendEmptyMessage(LONG_PRESS);
+                }
                 break;
 
             case MotionEvent.ACTION_UP:
diff --git a/core/java/android/view/IWindowManager.aidl b/core/java/android/view/IWindowManager.aidl
index 330d72f..42ac880 100644
--- a/core/java/android/view/IWindowManager.aidl
+++ b/core/java/android/view/IWindowManager.aidl
@@ -51,6 +51,7 @@
 import android.view.AppTransitionAnimationSpec;
 import android.view.WindowContentFrameStats;
 import android.view.WindowManager;
+import android.view.SurfaceControl;
 
 /**
  * System private interface to the window manager.
@@ -555,8 +556,8 @@
      * display content info to any SurfaceControl, as this would be a security issue.
      *
      * @param displayId The id of the display.
-     * @param surfaceControlHandle The SurfaceControl handle that the top level layers for the
+     * @param surfaceControlHandle The SurfaceControl that the top level layers for the
      *        display should be re-parented to.
      */
-    void reparentDisplayContent(int displayId, in IBinder surfaceControlHandle);
+    void reparentDisplayContent(int displayId, in SurfaceControl sc);
 }
diff --git a/core/java/android/view/InsetsAnimationControlImpl.java b/core/java/android/view/InsetsAnimationControlImpl.java
index 7c1465b..7291d0b 100644
--- a/core/java/android/view/InsetsAnimationControlImpl.java
+++ b/core/java/android/view/InsetsAnimationControlImpl.java
@@ -122,7 +122,8 @@
         mController.scheduleApplyChangeInsets();
     }
 
-    void applyChangeInsets(InsetsState state) {
+    @VisibleForTesting
+    public void applyChangeInsets(InsetsState state) {
         final Insets offset = Insets.subtract(mShownInsets, mPendingInsets);
         ArrayList<SurfaceParams> params = new ArrayList<>();
         if (offset.left != 0) {
diff --git a/core/java/android/view/InsetsController.java b/core/java/android/view/InsetsController.java
index 4b1d1ec..2142c36 100644
--- a/core/java/android/view/InsetsController.java
+++ b/core/java/android/view/InsetsController.java
@@ -16,17 +16,22 @@
 
 package android.view;
 
+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 +44,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 +98,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 +164,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 +191,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
@@ -226,20 +293,96 @@
         }
     }
 
+    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);
     }
 
-    void dispatchAnimationStarted(WindowInsetsAnimationListener.InsetsAnimation animation) {
+    @VisibleForTesting
+    public void dispatchAnimationStarted(WindowInsetsAnimationListener.InsetsAnimation animation) {
         mViewRoot.mView.dispatchWindowInsetsAnimationStarted(animation);
     }
 
-    void dispatchAnimationFinished(WindowInsetsAnimationListener.InsetsAnimation animation) {
+    @VisibleForTesting
+    public void dispatchAnimationFinished(WindowInsetsAnimationListener.InsetsAnimation animation) {
         mViewRoot.mView.dispatchWindowInsetsAnimationFinished(animation);
     }
 
-    void scheduleApplyChangeInsets() {
+    @VisibleForTesting
+    public void scheduleApplyChangeInsets() {
         if (!mAnimCallbackScheduled) {
             mViewRoot.mChoreographer.postCallback(Choreographer.CALLBACK_INSETS_ANIMATION,
                     mAnimCallback, null /* token*/);
diff --git a/core/java/android/view/InsetsSourceConsumer.java b/core/java/android/view/InsetsSourceConsumer.java
index 145b097..7937cb6 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;
 
@@ -89,6 +89,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/Surface.java b/core/java/android/view/Surface.java
index f3cb376..7fcb2af 100644
--- a/core/java/android/view/Surface.java
+++ b/core/java/android/view/Surface.java
@@ -185,6 +185,18 @@
     }
 
     /**
+     * Create a Surface assosciated with a given {@link SurfaceControl}. Buffers submitted to this
+     * surface will be displayed by the system compositor according to the parameters
+     * specified by the control. Multiple surfaces may be constructed from one SurfaceControl,
+     * but only one can be connected (e.g. have an active EGL context) at a time.
+     *
+     * @param from The SurfaceControl to assosciate this Surface with
+     */
+    public Surface(SurfaceControl from) {
+        copyFrom(from);
+    }
+
+    /**
      * Create Surface from a {@link SurfaceTexture}.
      *
      * Images drawn to the Surface will be made available to the {@link
@@ -494,7 +506,6 @@
      * in to it.
      *
      * @param other {@link SurfaceControl} to copy from.
-     *
      * @hide
      */
     @UnsupportedAppUsage
diff --git a/core/java/android/view/SurfaceControl.java b/core/java/android/view/SurfaceControl.java
index 5e98236..4032a6b8 100644
--- a/core/java/android/view/SurfaceControl.java
+++ b/core/java/android/view/SurfaceControl.java
@@ -27,6 +27,10 @@
 import static android.view.SurfaceControlProto.HASH_CODE;
 import static android.view.SurfaceControlProto.NAME;
 
+import android.annotation.FloatRange;
+import android.annotation.IntRange;
+import android.annotation.NonNull;
+import android.annotation.Nullable;
 import android.annotation.Size;
 import android.annotation.UnsupportedAppUsage;
 import android.graphics.Bitmap;
@@ -42,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;
 
@@ -56,16 +59,23 @@
 import libcore.util.NativeAllocationRegistry;
 
 import java.io.Closeable;
+import java.nio.ByteBuffer;
 
 /**
- * SurfaceControl
- * @hide
+ * Handle to an on-screen Surface managed by the system compositor. The SurfaceControl is
+ * a combination of a buffer source, and metadata about how to display the buffers.
+ * By constructing a {@link Surface} from this SurfaceControl you can submit buffers to be
+ * composited. Using {@link SurfaceControl.Transaction} you can manipulate various
+ * properties of how the buffer will be displayed on-screen. SurfaceControl's are
+ * arranged into a scene-graph like hierarchy, and as such any SurfaceControl may have
+ * a parent. Geometric properties like transform, crop, and Z-ordering will be inherited
+ * from the parent, as if the child were content in the parents buffer stream.
  */
-public class SurfaceControl implements Parcelable {
+public final class SurfaceControl implements Parcelable {
     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);
@@ -103,6 +113,8 @@
             float dtdy, float dsdy);
     private static native void nativeSetColorTransform(long transactionObj, long nativeObject,
             float[] matrix, float[] translation);
+    private static native void nativeSetGeometry(long transactionObj, long nativeObject,
+            Rect sourceCrop, Rect dest, long orientation);
     private static native void nativeSetColor(long transactionObj, long nativeObject, float[] color);
     private static native void nativeSetFlags(long transactionObj, long nativeObject,
             int flags, int mask);
@@ -156,7 +168,7 @@
     private static native void nativeReparentChildren(long transactionObj, long nativeObject,
             IBinder handle);
     private static native void nativeReparent(long transactionObj, long nativeObject,
-            IBinder parentHandle);
+            long newParentNativeObject);
     private static native void nativeSeverChildren(long transactionObj, long nativeObject);
     private static native void nativeSetOverrideScalingMode(long transactionObj, long nativeObject,
             int scalingMode);
@@ -170,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;
@@ -331,8 +344,7 @@
      */
     public static final int BUILT_IN_DISPLAY_ID_HDMI = 1;
 
-    /* Display power modes * /
-
+    // Display power modes.
     /**
      * Display power mode off: used while blanking the screen.
      * Use only with {@link SurfaceControl#setDisplayPowerMode}.
@@ -402,9 +414,26 @@
     }
 
     /**
-     * Builder class for {@link SurfaceControl} objects.
+     * 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 {
         private SurfaceSession mSession;
         private int mFlags = HIDDEN;
@@ -413,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}.
@@ -427,8 +455,14 @@
         }
 
         /**
-         * Construct a new {@link SurfaceControl} with the set parameters.
-         * @hide
+         * Begin building a SurfaceControl.
+         */
+        public Builder() {
+        }
+
+        /**
+         * Construct a new {@link SurfaceControl} with the set parameters. The builder
+         * remains valid.
          */
         public SurfaceControl build() {
             if (mWidth < 0 || mHeight < 0) {
@@ -439,15 +473,14 @@
                 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);
         }
 
         /**
          * Set a debugging-name for the SurfaceControl.
          *
          * @param name A name to identify the Surface in debugging.
-         * @hide
          */
         public Builder setName(String name) {
             mName = name;
@@ -459,9 +492,9 @@
          *
          * @param width The buffer width in pixels.
          * @param height The buffer height in pixels.
-         * @hide
          */
-        public Builder setBufferSize(int width, int height) {
+        public Builder setBufferSize(@IntRange(from = 0) int width,
+                @IntRange(from = 0) int height) {
             if (width < 0 || height < 0) {
                 throw new IllegalArgumentException(
                         "width and height must be positive");
@@ -474,8 +507,8 @@
         /**
          * Set the pixel format of the controlled surface's buffers, using constants from
          * {@link android.graphics.PixelFormat}.
-         * @hide
          */
+        @NonNull
         public Builder setFormat(@PixelFormat.Format int format) {
             mFormat = format;
             return this;
@@ -490,6 +523,7 @@
          * @param protectedContent Whether to require a protected sink.
          * @hide
          */
+        @NonNull
         public Builder setProtected(boolean protectedContent) {
             if (protectedContent) {
                 mFlags |= PROTECTED_APP;
@@ -506,6 +540,7 @@
          * not a complete prevention of readback as {@link #setProtected}.
          * @hide
          */
+        @NonNull
         public Builder setSecure(boolean secure) {
             if (secure) {
                 mFlags |= SECURE;
@@ -537,8 +572,8 @@
          * If the underlying buffer lacks an alpha channel, it is as if setOpaque(true)
          * were set automatically.
          * @param opaque Whether the Surface is OPAQUE.
-         * @hide
          */
+        @NonNull
         public Builder setOpaque(boolean opaque) {
             if (opaque) {
                 mFlags |= OPAQUE;
@@ -556,31 +591,25 @@
          * of the parent.
          *
          * @param parent The parent control.
-         * @hide
          */
-        public Builder setParent(SurfaceControl parent) {
+        @NonNull
+        public Builder setParent(@Nullable SurfaceControl parent) {
             mParent = parent;
             return this;
         }
 
         /**
-         * 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;
         }
 
@@ -665,17 +694,13 @@
      * @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 (session == null) {
-            throw new IllegalArgumentException("session must not be null");
-        }
         if (name == null) {
             throw new IllegalArgumentException("name must not be null");
         }
@@ -692,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");
@@ -729,9 +767,6 @@
         mCloseGuard.open("release");
     }
 
-    /**
-     * @hide
-     */
     public void readFromParcel(Parcel in) {
         if (in == null) {
             throw new IllegalArgumentException("source must not be null");
@@ -748,17 +783,11 @@
         assignNativeObject(object);
     }
 
-    /**
-     * @hide
-     */
     @Override
     public int describeContents() {
         return 0;
     }
 
-    /**
-     * @hide
-     */
     @Override
     public void writeToParcel(Parcel dest, int flags) {
         dest.writeString(mName);
@@ -791,9 +820,6 @@
         proto.end(token);
     }
 
-    /**
-     * @hide
-     */
     public static final Creator<SurfaceControl> CREATOR
             = new Creator<SurfaceControl>() {
         public SurfaceControl createFromParcel(Parcel in) {
@@ -823,10 +849,12 @@
     }
 
     /**
-     * Release the local reference to the server-side surface.
-     * Always call release() when you're done with a Surface.
-     * This will make the surface invalid.
-     * @hide
+     * Release the local reference to the server-side surface. The surface
+     * may continue to exist on-screen as long as its parent continues
+     * to exist. To explicitly remove a surface from the screen use
+     * {@link Transaction#reparent} with a null-parent.
+     *
+     * Always call release() when you're done with a SurfaceControl.
      */
     public void release() {
         if (mNativeObject != 0) {
@@ -866,7 +894,10 @@
     }
 
     /**
-     * @hide
+     * Check whether this instance points to a valid layer with the system-compositor. For
+     * example this may be false if construction failed, or the layer was released.
+     *
+     * @return Whether this SurfaceControl is valid.
      */
     public boolean isValid() {
         return mNativeObject != 0;
@@ -962,9 +993,9 @@
     /**
      * @hide
      */
-    public void reparent(IBinder newParentHandle) {
+    public void reparent(SurfaceControl newParent) {
         synchronized(SurfaceControl.class) {
-            sGlobalTransaction.reparent(this, newParentHandle);
+            sGlobalTransaction.reparent(this, newParent);
         }
     }
 
@@ -1270,9 +1301,6 @@
         }
     }
 
-    /**
-     * @hide
-     */
     @Override
     public String toString() {
         return "Surface(name=" + mName + ")/@0x" +
@@ -1286,6 +1314,7 @@
 
     /**
      * Describes the properties of a physical display known to surface flinger.
+     * @hide
      */
     public static final class PhysicalDisplayInfo {
         /**
@@ -1777,9 +1806,12 @@
     }
 
     /**
-     * @hide
+     * An atomic set of changes to a set of SurfaceControl.
      */
     public static class Transaction implements Closeable {
+        /**
+         * @hide
+         */
         public static final NativeAllocationRegistry sRegistry = new NativeAllocationRegistry(
                 Transaction.class.getClassLoader(),
                 nativeGetNativeTransactionFinalizer(), 512);
@@ -1789,9 +1821,12 @@
         Runnable mFreeNativeResources;
 
         /**
-         * @hide
+         * Open a new transaction object. The transaction may be filed with commands to
+         * manipulate {@link SurfaceControl} instances, and then applied atomically with
+         * {@link #apply}. Eventually the user should invoke {@link #close}, when the object
+         * is no longer required. Note however that re-using a transaction after a call to apply
+         * is allowed as a convenience.
          */
-        @UnsupportedAppUsage
         public Transaction() {
             mNativeObject = nativeCreateTransaction();
             mFreeNativeResources
@@ -1801,9 +1836,7 @@
         /**
          * Apply the transaction, clearing it's state, and making it usable
          * as a new transaction.
-         * @hide
          */
-        @UnsupportedAppUsage
         public void apply() {
             apply(false);
         }
@@ -1811,7 +1844,6 @@
         /**
          * Close the transaction, if the transaction was not already applied this will cancel the
          * transaction.
-         * @hide
          */
         @Override
         public void close() {
@@ -1841,6 +1873,27 @@
         }
 
         /**
+         * Toggle the visibility of a given Layer and it's sub-tree.
+         *
+         * @param sc The SurfaceControl for which to set the visibility
+         * @param visible The new visibility
+         * @return This transaction object.
+         */
+        @NonNull
+        public Transaction setVisibility(@NonNull SurfaceControl sc, boolean visible) {
+            sc.checkNotReleased();
+            if (visible) {
+                return show(sc);
+            } else {
+                return hide(sc);
+            }
+        }
+
+        /**
+         * Request that a given surface and it's sub-tree be shown.
+         *
+         * @param sc The surface to show.
+         * @return This transaction.
          * @hide
          */
         @UnsupportedAppUsage
@@ -1851,6 +1904,10 @@
         }
 
         /**
+         * Request that a given surface and it's sub-tree be hidden.
+         *
+         * @param sc The surface to hidden.
+         * @return This transaction.
          * @hide
          */
         @UnsupportedAppUsage
@@ -1871,10 +1928,17 @@
         }
 
         /**
-         * @hide
+         * Set the default buffer size for the SurfaceControl, if there is an
+         * {@link Surface} assosciated with the control, then
+         * this will be the default size for buffers dequeued from it.
+         * @param sc The surface to set the buffer size for.
+         * @param w The default width
+         * @param h The default height
+         * @return This Transaction
          */
-        @UnsupportedAppUsage
-        public Transaction setBufferSize(SurfaceControl sc, int w, int h) {
+        @NonNull
+        public Transaction setBufferSize(@NonNull SurfaceControl sc,
+                @IntRange(from = 0) int w, @IntRange(from = 0) int h) {
             sc.checkNotReleased();
             mResizedSurfaces.put(sc, new Point(w, h));
             nativeSetSize(mNativeObject, sc.mNativeObject, w, h);
@@ -1882,10 +1946,17 @@
         }
 
         /**
-         * @hide
+         * Set the Z-order for a given SurfaceControl, relative to it's siblings.
+         * If two siblings share the same Z order the ordering is undefined. Surfaces
+         * with a negative Z will be placed below the parent surface.
+         *
+         * @param sc The SurfaceControl to set the Z order on
+         * @param z The Z-order
+         * @return This Transaction.
          */
-        @UnsupportedAppUsage
-        public Transaction setLayer(SurfaceControl sc, int z) {
+        @NonNull
+        public Transaction setLayer(@NonNull SurfaceControl sc,
+                @IntRange(from = Integer.MIN_VALUE, to = Integer.MAX_VALUE) int z) {
             sc.checkNotReleased();
             nativeSetLayer(mNativeObject, sc.mNativeObject, z);
             return this;
@@ -1912,10 +1983,15 @@
         }
 
         /**
-         * @hide
+         * Set the alpha for a given surface. If the alpha is non-zero the SurfaceControl
+         * will be blended with the Surfaces under it according to the specified ratio.
+         *
+         * @param sc The given SurfaceControl.
+         * @param alpha The alpha to set.
          */
-        @UnsupportedAppUsage
-        public Transaction setAlpha(SurfaceControl sc, float alpha) {
+        @NonNull
+        public Transaction setAlpha(@NonNull SurfaceControl sc,
+                @FloatRange(from = 0.0, to = 1.0) float alpha) {
             sc.checkNotReleased();
             nativeSetAlpha(mNativeObject, sc.mNativeObject, alpha);
             return this;
@@ -1947,6 +2023,25 @@
         }
 
         /**
+         * Specify how the buffer assosciated with this Surface is mapped in to the
+         * parent coordinate space. The source frame will be scaled to fit the destination
+         * frame, after being rotated according to the orientation parameter.
+         *
+         * @param sc The SurfaceControl to specify the geometry of
+         * @param sourceCrop The source rectangle in buffer space. Or null for the entire buffer.
+         * @param destFrame The destination rectangle in parent space. Or null for the source frame.
+         * @param orientation The buffer rotation
+         * @return This transaction object.
+         */
+        @NonNull
+        public Transaction setGeometry(@NonNull SurfaceControl sc, @Nullable Rect sourceCrop,
+                @Nullable Rect destFrame, @Surface.Rotation int orientation) {
+            sc.checkNotReleased();
+            nativeSetGeometry(mNativeObject, sc.mNativeObject, sourceCrop, destFrame, orientation);
+            return this;
+        }
+
+        /**
          * @hide
          */
         @UnsupportedAppUsage
@@ -2023,20 +2118,20 @@
             return this;
         }
 
-        @UnsupportedAppUsage
         /**
          * @hide
          */
+        @UnsupportedAppUsage
         public Transaction setLayerStack(SurfaceControl sc, int layerStack) {
             sc.checkNotReleased();
             nativeSetLayerStack(mNativeObject, sc.mNativeObject, layerStack);
             return this;
         }
 
-        @UnsupportedAppUsage
         /**
          * @hide
          */
+        @UnsupportedAppUsage
         public Transaction deferTransactionUntil(SurfaceControl sc, IBinder handle,
                 long frameNumber) {
             if (frameNumber < 0) {
@@ -2047,10 +2142,10 @@
             return this;
         }
 
-        @UnsupportedAppUsage
         /**
          * @hide
          */
+        @UnsupportedAppUsage
         public Transaction deferTransactionUntilSurface(SurfaceControl sc, Surface barrierSurface,
                 long frameNumber) {
             if (frameNumber < 0) {
@@ -2071,13 +2166,25 @@
             return this;
         }
 
-        /** Re-parents a specific child layer to a new parent 
-         * @hide
+        /**
+         * Re-parents a given layer to a new parent. Children inherit transform (position, scaling)
+         * crop, visibility, and Z-ordering from their parents, as if the children were pixels within the
+         * parent Surface.
+         *
+         * @param sc The SurfaceControl to reparent
+         * @param newParent The new parent for the given control.
+         * @return This Transaction
          */
-        public Transaction reparent(SurfaceControl sc, IBinder newParentHandle) {
+        @NonNull
+        public Transaction reparent(@NonNull SurfaceControl sc,
+                @Nullable SurfaceControl newParent) {
             sc.checkNotReleased();
-            nativeReparent(mNativeObject, sc.mNativeObject,
-                    newParentHandle);
+            long otherObject = 0;
+            if (newParent != null) {
+                newParent.checkNotReleased();
+                otherObject = newParent.mNativeObject;
+            }
+            nativeReparent(mNativeObject, sc.mNativeObject, otherObject);
             return this;
         }
 
@@ -2243,11 +2350,38 @@
         }
 
         /**
-         * Merge the other transaction into this transaction, clearing the
-         * other transaction as if it had been applied.
+         * Sets an arbitrary piece of metadata on the surface. This is a helper for int data.
          * @hide
          */
-        public Transaction merge(Transaction other) {
+        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.
+         *
+         * @param other The transaction to merge in to this one.
+         * @return This transaction.
+         */
+        @NonNull
+        public Transaction merge(@NonNull Transaction other) {
             mResizedSurfaces.putAll(other.mResizedSurfaces);
             other.mResizedSurfaces.clear();
             nativeMergeTransaction(mNativeObject, other.mNativeObject);
diff --git a/core/java/android/view/SurfaceView.java b/core/java/android/view/SurfaceView.java
index 61fb00d..45e6c50 100644
--- a/core/java/android/view/SurfaceView.java
+++ b/core/java/android/view/SurfaceView.java
@@ -120,10 +120,11 @@
     final Rect mScreenRect = new Rect();
     SurfaceSession mSurfaceSession;
 
-    SurfaceControlWithBackground mSurfaceControl;
+    SurfaceControl mSurfaceControl;
     // In the case of format changes we switch out the surface in-place
     // we need to preserve the old one until the new one has drawn.
     SurfaceControl mDeferredDestroySurfaceControl;
+    SurfaceControl mBackgroundControl;
     final Rect mTmpRect = new Rect();
     final Configuration mConfiguration = new Configuration();
 
@@ -487,6 +488,29 @@
         }
     }
 
+    private void updateBackgroundVisibilityInTransaction() {
+        if (mBackgroundControl == null) {
+            return;
+        }
+        if ((mSurfaceFlags & PixelFormat.OPAQUE) == 0) {
+            mBackgroundControl.show();
+            mBackgroundControl.setLayer(Integer.MIN_VALUE);
+        } else {
+            mBackgroundControl.hide();
+        }
+    }
+
+    private void releaseSurfaces() {
+        if (mSurfaceControl != null) {
+            mSurfaceControl.destroy();
+            mSurfaceControl = null;
+        }
+        if (mBackgroundControl != null) {
+            mBackgroundControl.destroy();
+            mBackgroundControl = null;
+        }
+    }
+
     /** @hide */
     protected void updateSurface() {
         if (!mHaveFrame) {
@@ -553,14 +577,21 @@
                     updateOpaqueFlag();
                     final String name = "SurfaceView - " + viewRoot.getTitle().toString();
 
-                    mSurfaceControl = new SurfaceControlWithBackground(
-                            name,
-                            (mSurfaceFlags & SurfaceControl.OPAQUE) != 0,
-                            new SurfaceControl.Builder(mSurfaceSession)
-                                    .setBufferSize(mSurfaceWidth, mSurfaceHeight)
-                                    .setFormat(mFormat)
-                                    .setParent(viewRoot.getSurfaceControl())
-                                    .setFlags(mSurfaceFlags));
+                    mSurfaceControl = new SurfaceControl.Builder(mSurfaceSession)
+                        .setName(name)
+                        .setOpaque((mSurfaceFlags & SurfaceControl.OPAQUE) != 0)
+                        .setBufferSize(mSurfaceWidth, mSurfaceHeight)
+                        .setFormat(mFormat)
+                        .setParent(viewRoot.getSurfaceControl())
+                        .setFlags(mSurfaceFlags)
+                        .build();
+                    mBackgroundControl = new SurfaceControl.Builder(mSurfaceSession)
+                        .setName("Background for -" + name)
+                        .setOpaque(true)
+                        .setColorLayer(true)
+                        .setParent(mSurfaceControl)
+                        .build();
+
                 } else if (mSurfaceControl == null) {
                     return;
                 }
@@ -577,11 +608,13 @@
                     SurfaceControl.openTransaction();
                     try {
                         mSurfaceControl.setLayer(mSubLayer);
+
                         if (mViewVisibility) {
                             mSurfaceControl.show();
                         } else {
                             mSurfaceControl.hide();
                         }
+                        updateBackgroundVisibilityInTransaction();
 
                         // While creating the surface, we will set it's initial
                         // geometry. Outside of that though, we should generally
@@ -724,8 +757,7 @@
                     if (mSurfaceControl != null && !mSurfaceCreated) {
                         mSurface.release();
 
-                        mSurfaceControl.destroy();
-                        mSurfaceControl = null;
+                        releaseSurfaces();
                     }
                 }
             } catch (Exception ex) {
@@ -823,7 +855,6 @@
         final ViewRootImpl viewRoot = getViewRootImpl();
 
         applySurfaceTransforms(mSurfaceControl, position, frameNumber);
-        applySurfaceTransforms(mSurfaceControl.mBackgroundControl, position, frameNumber);
 
         applyChildSurfaceTransaction_renderWorker(mRtTransaction, viewRoot.mSurface,
                 frameNumber);
@@ -950,7 +981,19 @@
      * @hide
      */
     public void setResizeBackgroundColor(int bgColor) {
-        mSurfaceControl.setBackgroundColor(bgColor);
+        if (mBackgroundControl == null) {
+            return;
+        }
+
+        final float[] colorComponents = new float[] { Color.red(bgColor) / 255.f,
+                Color.green(bgColor) / 255.f, Color.blue(bgColor) / 255.f };
+
+        SurfaceControl.openTransaction();
+        try {
+            mBackgroundControl.setColor(colorComponents);
+        } finally {
+            SurfaceControl.closeTransaction();
+        }
     }
 
     @UnsupportedAppUsage
@@ -1128,154 +1171,12 @@
     };
 
     /**
-     * @hide
+     * Return a SurfaceControl which can be used for parenting Surfaces to
+     * this SurfaceView.
+     *
+     * @return The SurfaceControl for this SurfaceView.
      */
     public SurfaceControl getSurfaceControl() {
         return mSurfaceControl;
     }
-
-    class SurfaceControlWithBackground extends SurfaceControl {
-        SurfaceControl mBackgroundControl;
-        private boolean mOpaque = true;
-        public boolean mVisible = false;
-
-        public SurfaceControlWithBackground(String name, boolean opaque, SurfaceControl.Builder b)
-                       throws Exception {
-            super(b.setName(name).build());
-
-            mBackgroundControl = b.setName("Background for -" + name)
-                    .setFormat(OPAQUE)
-                    // Unset the buffer size of the background color layer.
-                    .setBufferSize(0, 0)
-                    .setColorLayer(true)
-                    .build();
-            mOpaque = opaque;
-        }
-
-        @Override
-        public void setAlpha(float alpha) {
-            super.setAlpha(alpha);
-            mBackgroundControl.setAlpha(alpha);
-        }
-
-        @Override
-        public void setLayer(int zorder) {
-            super.setLayer(zorder);
-            // -3 is below all other child layers as SurfaceView never goes below -2
-            mBackgroundControl.setLayer(-3);
-        }
-
-        @Override
-        public void setPosition(float x, float y) {
-            super.setPosition(x, y);
-            mBackgroundControl.setPosition(x, y);
-        }
-
-        @Override
-        public void setBufferSize(int w, int h) {
-            super.setBufferSize(w, h);
-            // The background surface is a color layer so we do not set a size.
-        }
-
-        @Override
-        public void setWindowCrop(Rect crop) {
-            super.setWindowCrop(crop);
-            mBackgroundControl.setWindowCrop(crop);
-        }
-
-        @Override
-        public void setWindowCrop(int width, int height) {
-            super.setWindowCrop(width, height);
-            mBackgroundControl.setWindowCrop(width, height);
-        }
-
-        @Override
-        public void setLayerStack(int layerStack) {
-            super.setLayerStack(layerStack);
-            mBackgroundControl.setLayerStack(layerStack);
-        }
-
-        @Override
-        public void setOpaque(boolean isOpaque) {
-            super.setOpaque(isOpaque);
-            mOpaque = isOpaque;
-            updateBackgroundVisibility();
-        }
-
-        @Override
-        public void setSecure(boolean isSecure) {
-            super.setSecure(isSecure);
-        }
-
-        @Override
-        public void setMatrix(float dsdx, float dtdx, float dsdy, float dtdy) {
-            super.setMatrix(dsdx, dtdx, dsdy, dtdy);
-            mBackgroundControl.setMatrix(dsdx, dtdx, dsdy, dtdy);
-        }
-
-        @Override
-        public void hide() {
-            super.hide();
-            mVisible = false;
-            updateBackgroundVisibility();
-        }
-
-        @Override
-        public void show() {
-            super.show();
-            mVisible = true;
-            updateBackgroundVisibility();
-        }
-
-        @Override
-        public void destroy() {
-            super.destroy();
-            mBackgroundControl.destroy();
-         }
-
-        @Override
-        public void release() {
-            super.release();
-            mBackgroundControl.release();
-        }
-
-        @Override
-        public void setTransparentRegionHint(Region region) {
-            super.setTransparentRegionHint(region);
-            mBackgroundControl.setTransparentRegionHint(region);
-        }
-
-        @Override
-        public void deferTransactionUntil(IBinder handle, long frame) {
-            super.deferTransactionUntil(handle, frame);
-            mBackgroundControl.deferTransactionUntil(handle, frame);
-        }
-
-        @Override
-        public void deferTransactionUntil(Surface barrier, long frame) {
-            super.deferTransactionUntil(barrier, frame);
-            mBackgroundControl.deferTransactionUntil(barrier, frame);
-        }
-
-        /** Set the color to fill the background with. */
-        private void setBackgroundColor(int bgColor) {
-            final float[] colorComponents = new float[] { Color.red(bgColor) / 255.f,
-                    Color.green(bgColor) / 255.f, Color.blue(bgColor) / 255.f };
-
-            SurfaceControl.openTransaction();
-            try {
-                mBackgroundControl.setColor(colorComponents);
-            } finally {
-                SurfaceControl.closeTransaction();
-            }
-        }
-
-        void updateBackgroundVisibility() {
-            if (mOpaque && mVisible) {
-                mBackgroundControl.show();
-            } else {
-                mBackgroundControl.hide();
-            }
-        }
-    }
 }
diff --git a/core/java/android/view/ThreadedRenderer.java b/core/java/android/view/ThreadedRenderer.java
index 34d076f..47b206ca 100644
--- a/core/java/android/view/ThreadedRenderer.java
+++ b/core/java/android/view/ThreadedRenderer.java
@@ -20,6 +20,7 @@
 import android.content.Context;
 import android.content.res.TypedArray;
 import android.graphics.HardwareRenderer;
+import android.graphics.Picture;
 import android.graphics.Point;
 import android.graphics.RecordingCanvas;
 import android.graphics.Rect;
@@ -553,6 +554,10 @@
         dumpProfileInfo(fd, flags);
     }
 
+    Picture captureRenderingCommands() {
+        return null;
+    }
+
     @Override
     public boolean loadSystemProperties() {
         boolean changed = super.loadSystemProperties();
diff --git a/core/java/android/view/View.java b/core/java/android/view/View.java
index 2131e6d..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},
@@ -8203,10 +8212,10 @@
      * {@link ContentCaptureSession#notifyViewDisappeared(AutofillId)}, and
      * {@link ContentCaptureSession#notifyViewTextChanged(AutofillId, CharSequence, int)}
      * respectively. The structure for the a child must be created using
-     * {@link ContentCaptureSession#newVirtualViewStructure(AutofillId, int)}, and the
+     * {@link ContentCaptureSession#newVirtualViewStructure(AutofillId, long)}, and the
      * {@code autofillId} for a child can be obtained either through
      * {@code childStructure.getAutofillId()} or
-     * {@link ContentCaptureSession#newAutofillId(AutofillId, int)}.
+     * {@link ContentCaptureSession#newAutofillId(AutofillId, long)}.
      *
      * <p><b>Note: </b>the following methods of the {@code structure} will be ignored:
      * <ul>
@@ -8227,7 +8236,15 @@
      * </ul>
      */
     public void onProvideContentCaptureStructure(@NonNull ViewStructure structure, int flags) {
-        onProvideStructure(structure, VIEW_STRUCTURE_FOR_CONTENT_CAPTURE, flags);
+        if (Trace.isTagEnabled(Trace.TRACE_TAG_VIEW)) {
+            Trace.traceBegin(Trace.TRACE_TAG_VIEW,
+                    "onProvideContentCaptureStructure() for " + getClass().getSimpleName());
+        }
+        try {
+            onProvideStructure(structure, VIEW_STRUCTURE_FOR_CONTENT_CAPTURE, flags);
+        } finally {
+            Trace.traceEnd(Trace.TRACE_TAG_VIEW);
+        }
     }
 
     /** @hide */
@@ -8600,7 +8617,7 @@
         if (isAttachedToWindow()) {
             throw new IllegalStateException("Cannot set autofill id when view is attached");
         }
-        if (id != null && id.isVirtual()) {
+        if (id != null && !id.isNonVirtual()) {
             throw new IllegalStateException("Cannot set autofill id assigned to virtual views");
         }
         if (id == null && (mPrivateFlags3 & PFLAG3_AUTOFILLID_EXPLICITLY_SET) == 0) {
@@ -9017,6 +9034,18 @@
      * </ol>
      */
     private void notifyAppearedOrDisappearedForContentCaptureIfNeeded(boolean appeared) {
+        if (Trace.isTagEnabled(Trace.TRACE_TAG_VIEW)) {
+            Trace.traceBegin(Trace.TRACE_TAG_VIEW,
+                    "notifyContentCapture(" + appeared + ") for " + getClass().getSimpleName());
+        }
+        try {
+            notifyAppearedOrDisappearedForContentCaptureIfNeededNoTrace(appeared);
+        } finally {
+            Trace.traceEnd(Trace.TRACE_TAG_VIEW);
+        }
+    }
+
+    private void notifyAppearedOrDisappearedForContentCaptureIfNeededNoTrace(boolean appeared) {
         // First check if context has client, so it saves a service lookup when it doesn't
         if (!mContext.isContentCaptureSupported()) return;
 
@@ -9038,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);
         }
     }
 
@@ -9134,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();
@@ -13974,7 +14011,7 @@
                     if (clickable) {
                         setPressed(true, x, y);
                     }
-                    checkForLongClick(0, x, y);
+                    checkForLongClick(ViewConfiguration.getLongPressTimeout(), x, y);
                     return true;
                 }
             }
@@ -14715,7 +14752,7 @@
                     mHasPerformedLongPress = false;
 
                     if (!clickable) {
-                        checkForLongClick(0, x, y);
+                        checkForLongClick(ViewConfiguration.getLongPressTimeout(), x, y);
                         break;
                     }
 
@@ -14739,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;
 
@@ -14760,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();
@@ -14771,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;
             }
 
@@ -14805,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
@@ -25414,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;
 
@@ -25424,8 +25504,7 @@
             mPendingCheckForLongPress.setAnchor(x, y);
             mPendingCheckForLongPress.rememberWindowAttachCount();
             mPendingCheckForLongPress.rememberPressedState();
-            postDelayed(mPendingCheckForLongPress,
-                    ViewConfiguration.getLongPressTimeout() - delayOffset);
+            postDelayed(mPendingCheckForLongPress, delay);
         }
     }
 
@@ -27015,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/ViewDebug.java b/core/java/android/view/ViewDebug.java
index 292e933..5afc07f 100644
--- a/core/java/android/view/ViewDebug.java
+++ b/core/java/android/view/ViewDebug.java
@@ -17,17 +17,21 @@
 package android.view;
 
 import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.annotation.TestApi;
 import android.annotation.UnsupportedAppUsage;
 import android.content.Context;
 import android.content.res.Resources;
 import android.graphics.Bitmap;
 import android.graphics.Canvas;
+import android.graphics.HardwareRenderer;
 import android.graphics.Picture;
 import android.graphics.RecordingCanvas;
 import android.graphics.Rect;
 import android.graphics.RenderNode;
 import android.os.Debug;
 import android.os.Handler;
+import android.os.Looper;
 import android.os.RemoteException;
 import android.util.DisplayMetrics;
 import android.util.Log;
@@ -48,16 +52,20 @@
 import java.lang.reflect.Field;
 import java.lang.reflect.InvocationTargetException;
 import java.lang.reflect.Method;
+import java.util.ArrayDeque;
 import java.util.ArrayList;
 import java.util.HashMap;
 import java.util.concurrent.Callable;
 import java.util.concurrent.CancellationException;
 import java.util.concurrent.CountDownLatch;
 import java.util.concurrent.ExecutionException;
+import java.util.concurrent.Executor;
 import java.util.concurrent.FutureTask;
 import java.util.concurrent.TimeUnit;
 import java.util.concurrent.TimeoutException;
 import java.util.concurrent.atomic.AtomicReference;
+import java.util.concurrent.locks.ReentrantLock;
+import java.util.function.Function;
 
 /**
  * Various debugging/tracing tools related to {@link View} and the view hierarchy.
@@ -741,6 +749,123 @@
         root.getViewRootImpl().outputDisplayList(target);
     }
 
+    private static class PictureCallbackHandler implements AutoCloseable,
+            HardwareRenderer.PictureCapturedCallback, Runnable {
+        private final HardwareRenderer mRenderer;
+        private final Function<Picture, Boolean> mCallback;
+        private final Executor mExecutor;
+        private final ReentrantLock mLock = new ReentrantLock(false);
+        private final ArrayDeque<Picture> mQueue = new ArrayDeque<>(3);
+        private boolean mStopListening;
+        private Thread mRenderThread;
+
+        private PictureCallbackHandler(HardwareRenderer renderer,
+                Function<Picture, Boolean> callback, Executor executor) {
+            mRenderer = renderer;
+            mCallback = callback;
+            mExecutor = executor;
+            mRenderer.setPictureCaptureCallback(this);
+        }
+
+        @Override
+        public void close() {
+            mLock.lock();
+            mStopListening = true;
+            mLock.unlock();
+            mRenderer.setPictureCaptureCallback(null);
+        }
+
+        @Override
+        public void onPictureCaptured(Picture picture) {
+            mLock.lock();
+            if (mStopListening) {
+                mLock.unlock();
+                mRenderer.setPictureCaptureCallback(null);
+                return;
+            }
+            if (mRenderThread == null) {
+                mRenderThread = Thread.currentThread();
+            }
+            Picture toDestroy = null;
+            if (mQueue.size() == 3) {
+                toDestroy = mQueue.removeLast();
+            }
+            mQueue.add(picture);
+            mLock.unlock();
+            if (toDestroy == null) {
+                mExecutor.execute(this);
+            } else {
+                toDestroy.close();
+            }
+        }
+
+        @Override
+        public void run() {
+            mLock.lock();
+            final Picture picture = mQueue.poll();
+            final boolean isStopped = mStopListening;
+            mLock.unlock();
+            if (Thread.currentThread() == mRenderThread) {
+                close();
+                throw new IllegalStateException(
+                        "ViewDebug#startRenderingCommandsCapture must be given an executor that "
+                        + "invokes asynchronously");
+            }
+            if (isStopped) {
+                picture.close();
+                return;
+            }
+            final boolean keepReceiving = mCallback.apply(picture);
+            if (!keepReceiving) {
+                close();
+            }
+        }
+    }
+
+    /**
+     * Begins capturing the entire rendering commands for the view tree referenced by the given
+     * view. The view passed may be any View in the tree as long as it is attached. That is,
+     * {@link View#isAttachedToWindow()} must be true.
+     *
+     * Every time a frame is rendered a Picture will be passed to the given callback via the given
+     * executor. As long as the callback returns 'true' it will continue to receive new frames.
+     * The system will only invoke the callback at a rate that the callback is able to keep up with.
+     * That is, if it takes 48ms for the callback to complete and there is a 60fps animation running
+     * then the callback will only receive 33% of the frames produced.
+     *
+     * This method must be called on the same thread as the View tree.
+     *
+     * @param tree The View tree to capture the rendering commands.
+     * @param callback The callback to invoke on every frame produced. Should return true to
+     *                 continue receiving new frames, false to stop capturing.
+     * @param executor The executor to invoke the callback on. Recommend using a background thread
+     *                 to avoid stalling the UI thread. Must be an asynchronous invoke or an
+     *                 exception will be thrown.
+     * @return a closeable that can be used to stop capturing. May be invoked on any thread. Note
+     * that the callback may continue to receive another frame or two depending on thread timings.
+     * Returns null if the capture stream cannot be started, such as if there's no
+     * HardwareRenderer for the given view tree.
+     * @hide
+     */
+    @TestApi
+    @Nullable
+    public static AutoCloseable startRenderingCommandsCapture(View tree, Executor executor,
+            Function<Picture, Boolean> callback) {
+        final View.AttachInfo attachInfo = tree.mAttachInfo;
+        if (attachInfo == null) {
+            throw new IllegalArgumentException("Given view isn't attached");
+        }
+        if (attachInfo.mHandler.getLooper() != Looper.myLooper()) {
+            throw new IllegalStateException("Called on the wrong thread."
+                    + " Must be called on the thread that owns the given View");
+        }
+        final HardwareRenderer renderer = attachInfo.mThreadedRenderer;
+        if (renderer != null) {
+            return new PictureCallbackHandler(renderer, callback, executor);
+        }
+        return null;
+    }
+
     private static void capture(View root, final OutputStream clientStream, String parameter)
             throws IOException {
 
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/autofill/AutofillId.java b/core/java/android/view/autofill/AutofillId.java
index 9c935af..f1c7b69 100644
--- a/core/java/android/view/autofill/AutofillId.java
+++ b/core/java/android/view/autofill/AutofillId.java
@@ -29,12 +29,14 @@
     /** @hide */
     public static final int NO_SESSION = 0;
 
-    private static final int FLAG_IS_VIRTUAL = 0x1;
-    private static final int FLAG_HAS_SESSION = 0x2;
+    private static final int FLAG_IS_VIRTUAL_INT = 0x1;
+    private static final int FLAG_IS_VIRTUAL_LONG = 0x2;
+    private static final int FLAG_HAS_SESSION = 0x4;
 
     private final int mViewId;
     private final int mFlags;
-    private final int mVirtualId;
+    private final int mVirtualIntId;
+    private final long mVirtualLongId;
     private final int mSessionId;
 
     /** @hide */
@@ -46,40 +48,89 @@
     /** @hide */
     @TestApi
     public AutofillId(@NonNull AutofillId parent, int virtualChildId) {
-        this(FLAG_IS_VIRTUAL, parent.mViewId, virtualChildId, NO_SESSION);
+        this(FLAG_IS_VIRTUAL_INT, parent.mViewId, virtualChildId, NO_SESSION);
     }
 
     /** @hide */
     public AutofillId(int parentId, int virtualChildId) {
-        this(FLAG_IS_VIRTUAL, parentId, virtualChildId, NO_SESSION);
+        this(FLAG_IS_VIRTUAL_INT, parentId, virtualChildId, NO_SESSION);
     }
 
     /** @hide */
-    public AutofillId(@NonNull AutofillId parent, int virtualChildId, int sessionId) {
-        this(FLAG_IS_VIRTUAL | FLAG_HAS_SESSION, parent.mViewId, virtualChildId, sessionId);
+    public AutofillId(@NonNull AutofillId parent, long virtualChildId, int sessionId) {
+        this(FLAG_IS_VIRTUAL_LONG | FLAG_HAS_SESSION, parent.mViewId, virtualChildId, sessionId);
     }
 
-    private AutofillId(int flags, int parentId, int virtualChildId, int sessionId) {
+    private AutofillId(int flags, int parentId, long virtualChildId, int sessionId) {
         mFlags = flags;
         mViewId = parentId;
-        mVirtualId = virtualChildId;
+        mVirtualIntId = ((flags & FLAG_IS_VIRTUAL_INT) != 0) ? (int) virtualChildId : View.NO_ID;
+        mVirtualLongId = ((flags & FLAG_IS_VIRTUAL_LONG) != 0) ? virtualChildId : View.NO_ID;
         mSessionId = sessionId;
     }
 
-
     /** @hide */
     public int getViewId() {
         return mViewId;
     }
 
-    /** @hide */
-    public int getVirtualChildId() {
-        return mVirtualId;
+    /**
+     * Gets the virtual child id.
+     *
+     * <p>Should only be used on subsystems where such id is represented by an {@code int}
+     * (Assist and Autofill).
+     *
+     * @hide
+     */
+    public int getVirtualChildIntId() {
+        return mVirtualIntId;
     }
 
-    /** @hide */
-    public boolean isVirtual() {
-        return (mFlags & FLAG_IS_VIRTUAL) != 0;
+    /**
+     * Gets the virtual child id.
+     *
+     * <p>Should only be used on subsystems where such id is represented by a {@code long}
+     * (ContentCapture).
+     *
+     * @hide
+     */
+    public long getVirtualChildLongId() {
+        return mVirtualLongId;
+    }
+
+    /**
+     * Checks whether this node represents a virtual child, whose id is represented by an
+     * {@code int}.
+     *
+     * <p>Should only be used on subsystems where such id is represented by an {@code int}
+     * (Assist and Autofill).
+     *
+     * @hide
+     */
+    public boolean isVirtualInt() {
+        return (mFlags & FLAG_IS_VIRTUAL_INT) != 0;
+    }
+
+    /**
+     * Checks whether this node represents a virtual child, whose id is represented by an
+     * {@code long}.
+     *
+     * <p>Should only be used on subsystems where such id is represented by a {@code long}
+     * (ContentCapture).
+     *
+     * @hide
+     */
+    public boolean isVirtualLong() {
+        return (mFlags & FLAG_IS_VIRTUAL_LONG) != 0;
+    }
+
+    /**
+     * Checks whether this node represents a non-virtual child.
+     *
+     * @hide
+     */
+    public boolean isNonVirtual() {
+        return !isVirtualInt() && !isVirtualLong();
     }
 
     private boolean hasSession() {
@@ -100,7 +151,8 @@
         final int prime = 31;
         int result = 1;
         result = prime * result + mViewId;
-        result = prime * result + mVirtualId;
+        result = prime * result + mVirtualIntId;
+        result = prime * result + (int) (mVirtualLongId ^ (mVirtualLongId >>> 32));
         result = prime * result + mSessionId;
         return result;
     }
@@ -112,7 +164,8 @@
         if (getClass() != obj.getClass()) return false;
         final AutofillId other = (AutofillId) obj;
         if (mViewId != other.mViewId) return false;
-        if (mVirtualId != other.mVirtualId) return false;
+        if (mVirtualIntId != other.mVirtualIntId) return false;
+        if (mVirtualLongId != other.mVirtualLongId) return false;
         if (mSessionId != other.mSessionId) return false;
         return true;
     }
@@ -120,9 +173,12 @@
     @Override
     public String toString() {
         final StringBuilder builder = new StringBuilder().append(mViewId);
-        if (isVirtual()) {
-            builder.append(':').append(mVirtualId);
+        if (isVirtualInt()) {
+            builder.append(':').append(mVirtualIntId);
+        } else if (isVirtualLong()) {
+            builder.append(':').append(mVirtualLongId);
         }
+
         if (hasSession()) {
             builder.append('@').append(mSessionId);
         }
@@ -138,12 +194,14 @@
     public void writeToParcel(Parcel parcel, int flags) {
         parcel.writeInt(mViewId);
         parcel.writeInt(mFlags);
-        if (isVirtual()) {
-            parcel.writeInt(mVirtualId);
-        }
         if (hasSession()) {
             parcel.writeInt(mSessionId);
         }
+        if (isVirtualInt()) {
+            parcel.writeInt(mVirtualIntId);
+        } else if (isVirtualLong()) {
+            parcel.writeLong(mVirtualLongId);
+        }
     }
 
     public static final Parcelable.Creator<AutofillId> CREATOR =
@@ -152,9 +210,14 @@
         public AutofillId createFromParcel(Parcel source) {
             final int viewId = source.readInt();
             final int flags = source.readInt();
-            final int virtualId = (flags & FLAG_IS_VIRTUAL) != 0 ? source.readInt() : View.NO_ID;
             final int sessionId = (flags & FLAG_HAS_SESSION) != 0 ? source.readInt() : NO_SESSION;
-            return new AutofillId(flags, viewId, virtualId, sessionId);
+            if ((flags & FLAG_IS_VIRTUAL_INT) != 0) {
+                return new AutofillId(flags, viewId, source.readInt(), sessionId);
+            }
+            if ((flags & FLAG_IS_VIRTUAL_LONG) != 0) {
+                return new AutofillId(flags, viewId, source.readLong(), sessionId);
+            }
+            return new AutofillId(flags, viewId, View.NO_ID, sessionId);
         }
 
         @Override
diff --git a/core/java/android/view/autofill/AutofillManager.java b/core/java/android/view/autofill/AutofillManager.java
index 888a4c5..64c34f6 100644
--- a/core/java/android/view/autofill/AutofillManager.java
+++ b/core/java/android/view/autofill/AutofillManager.java
@@ -17,6 +17,7 @@
 package android.view.autofill;
 
 import static android.service.autofill.FillRequest.FLAG_MANUAL_REQUEST;
+import static android.util.DebugUtils.flagsToString;
 import static android.view.autofill.Helper.sDebug;
 import static android.view.autofill.Helper.sVerbose;
 
@@ -25,6 +26,7 @@
 import android.annotation.NonNull;
 import android.annotation.Nullable;
 import android.annotation.RequiresFeature;
+import android.annotation.SystemApi;
 import android.annotation.SystemService;
 import android.annotation.TestApi;
 import android.content.ComponentName;
@@ -77,6 +79,7 @@
 import java.util.Collections;
 import java.util.List;
 import java.util.Objects;
+import java.util.Set;
 
 //TODO: use java.lang.ref.Cleaner once Android supports Java 9
 import sun.misc.Cleaner;
@@ -336,6 +339,25 @@
     public static final int MAX_TEMP_AUGMENTED_SERVICE_DURATION_MS = 1_000 * 60 * 2; // 2 minutes
 
     /**
+     * Displays the Augment Autofill window using the same mechanism (such as a popup-window
+     * attached to the focused view) as the standard autofill.
+     *
+     * @hide
+     */
+    @TestApi
+    public static final int FLAG_SMART_SUGGESTION_SYSTEM = 0x1;
+
+    /** @hide */ // TODO(b/123233342): remove when not used anymore
+    public static final int FLAG_SMART_SUGGESTION_LEGACY = 0x2;
+
+    /** @hide */
+    @IntDef(flag = true, prefix = { "FLAG_SMART_SUGGESTION_" }, value = {
+            FLAG_SMART_SUGGESTION_SYSTEM
+    })
+    @Retention(RetentionPolicy.SOURCE)
+    public @interface SmartSuggestionMode {}
+
+    /**
      * Makes an authentication id from a request id and a dataset id.
      *
      * @param requestId The request id.
@@ -1686,7 +1708,7 @@
                 final IAutoFillManager service = mService;
                 final IAutoFillManagerClient serviceClient = mServiceClient;
                 mServiceClientCleaner = Cleaner.create(this, () -> {
-                    // TODO(b/111330312): call service to also remove reference to
+                    // TODO(b/123100811): call service to also remove reference to
                     // mAugmentedAutofillServiceClient
                     try {
                         service.removeClient(serviceClient, userId);
@@ -1746,6 +1768,108 @@
         }
     }
 
+    /**
+     * Defines whether augmented autofill should be triggered for activities with such
+     * {@link android.content.ComponentName}.
+     *
+     * <p>Useful to blacklist a particular activity.
+     *
+     * <p><b>Note:</b> This method should only be called by the app providing the augmented autofill
+     * service, and it's ignored if the caller isn't it.
+     *
+     * @hide
+     */
+    @SystemApi
+    @TestApi
+    //TODO(b/122654591): @TestApi is needed because CtsAutoFillServiceTestCases hosts the service
+    //in the same package as the test, and that module is compiled with SDK=test_current
+    public void setActivityAugmentedAutofillEnabled(@NonNull ComponentName activity,
+            boolean enabled) {
+        // TODO(b/123100824): implement
+    }
+
+    /**
+     * Defines whether augmented autofill should be triggered for activities of the app with such
+     * {@code packageName}.
+     *
+     * <p>Useful to blacklist any activity from a particular app.
+     *
+     * <p><b>Note:</b> This method should only be called by the app providing the augmented autofill
+     * service, and it's ignored if the caller isn't it.
+     *
+     * @hide
+     */
+    @SystemApi
+    @TestApi
+    //TODO(b/122654591): @TestApi is needed because CtsAutoFillServiceTestCases hosts the service
+    //in the same package as the test, and that module is compiled with SDK=test_current
+    public void setPackageAugmentedAutofillEnabled(@NonNull String packageName, boolean enabled) {
+        // TODO(b/123100824): implement
+    }
+
+    /**
+     * Explicitly limits augmented autofill to the given packages and activities.
+     *
+     * <p>When the whitelist is set, it overrides the values passed to
+     * {@link #setActivityAugmentedAutofillEnabled(ComponentName, boolean)}
+     * and {@link #setPackageAugmentedAutofillEnabled(String, boolean)}.
+     *
+     * <p>To reset the whitelist, call it passing {@code null} to both arguments.
+     *
+     * <p>Useful when the service wants to restrict augmented autofill to a category of apps, like
+     * apps that uses addresses. For example, if the service wants to support augmented autofill on
+     * all activities of app {@code AddressApp1} and just activities {@code act1} and {@code act2}
+     * of {@code AddressApp2}, it would call:
+     * {@code setAugmentedAutofillWhitelist(Arrays.asList("AddressApp1"),
+     * Arrays.asList(new ComponentName("AddressApp2", "act1"),
+     * new ComponentName("AddressApp2", "act2")));}
+     *
+     * <p><b>Note:</b> This method should only be called by the app providing the augmented autofill
+     * service, and it's ignored if the caller isn't it.
+     *
+     * @hide
+     */
+    @SystemApi
+    @TestApi
+    //TODO(b/122654591): @TestApi is needed because CtsAutoFillServiceTestCases hosts the service
+    //in the same package as the test, and that module is compiled with SDK=test_current
+    public void setAugmentedAutofillWhitelist(@Nullable List<String> packages,
+            @Nullable List<ComponentName> activities) {
+        // TODO(b/123100824): implement
+    }
+
+    /**
+     * Gets the activities where augmented autofill was disabled by
+     * {@link #setActivityAugmentedAutofillEnabled(ComponentName, boolean)}.
+     *
+     * <p><b>Note:</b> This method should only be called by the app providing the augmented autofill
+     * service, and it's ignored if the caller isn't it.
+     *
+     * @hide
+     */
+    @SystemApi
+    @TestApi
+    @NonNull
+    public Set<ComponentName> getAugmentedAutofillDisabledActivities() {
+        return null; // TODO(b/123100824): implement
+    }
+
+    /**
+     * Gets the apps where content capture was disabled by
+     * {@link #setPackageAugmentedAutofillEnabled(String, boolean)}.
+     *
+     * <p><b>Note:</b> This method should only be called by the app providing the augmented autofill
+     * service, and it's ignored if the caller isn't it.
+     *
+     * @hide
+     */
+    @SystemApi
+    @TestApi
+    @NonNull
+    public Set<String> getAugmentedAutofillDisabledPackages() {
+        return null; // TODO(b/123100824): implement
+    }
+
     private void requestShowFillUi(int sessionId, AutofillId id, int width, int height,
             Rect anchorBounds, IAutofillWindowPresenter presenter) {
         final View anchor = findView(id);
@@ -1769,8 +1893,8 @@
         }
 
         if (callback != null) {
-            if (id.isVirtual()) {
-                callback.onAutofillEvent(anchor, id.getVirtualChildId(),
+            if (id.isVirtualInt()) {
+                callback.onAutofillEvent(anchor, id.getVirtualChildIntId(),
                         AutofillCallback.EVENT_INPUT_SHOWN);
             } else {
                 callback.onAutofillEvent(anchor, AutofillCallback.EVENT_INPUT_SHOWN);
@@ -1896,7 +2020,7 @@
                     failedIds.add(id);
                     continue;
                 }
-                if (id.isVirtual()) {
+                if (id.isVirtualInt()) {
                     if (virtualValues == null) {
                         // Most likely there will be just one view with virtual children.
                         virtualValues = new ArrayMap<>(1);
@@ -1907,7 +2031,7 @@
                         valuesByParent = new SparseArray<>(5);
                         virtualValues.put(view, valuesByParent);
                     }
-                    valuesByParent.put(id.getVirtualChildId(), value);
+                    valuesByParent.put(id.getVirtualChildIntId(), value);
                 } else {
                     // Mark the view as to be autofilled with 'value'
                     if (mLastAutofilledData == null) {
@@ -2142,8 +2266,8 @@
         }
 
         if (callback != null) {
-            if (id.isVirtual()) {
-                callback.onAutofillEvent(anchor, id.getVirtualChildId(),
+            if (id.isVirtualInt()) {
+                callback.onAutofillEvent(anchor, id.getVirtualChildIntId(),
                         AutofillCallback.EVENT_INPUT_HIDDEN);
             } else {
                 callback.onAutofillEvent(anchor, AutofillCallback.EVENT_INPUT_HIDDEN);
@@ -2169,8 +2293,8 @@
         }
 
         if (callback != null) {
-            if (id.isVirtual()) {
-                callback.onAutofillEvent(anchor, id.getVirtualChildId(),
+            if (id.isVirtualInt()) {
+                callback.onAutofillEvent(anchor, id.getVirtualChildIntId(),
                         AutofillCallback.EVENT_INPUT_UNAVAILABLE);
             } else {
                 callback.onAutofillEvent(anchor, AutofillCallback.EVENT_INPUT_UNAVAILABLE);
@@ -2296,6 +2420,11 @@
         }
     }
 
+    /** @hide */
+    public static String getSmartSuggestionModeToString(@SmartSuggestionMode int flags) {
+        return flagsToString(AutofillManager.class, "FLAG_SMART_SUGGESTION_", flags);
+    }
+
     @GuardedBy("mLock")
     private boolean isActiveLocked() {
         return mState == STATE_ACTIVE;
@@ -2972,7 +3101,6 @@
 
         @Override
         public Rect getViewCoordinates(@NonNull AutofillId id) {
-            // TODO(b/111330312): use handler / callback?
             final AutofillManager afm = mAfm.get();
             if (afm == null) return null;
 
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 b620ab1..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.
      *
@@ -352,14 +332,14 @@
      * @throws IllegalArgumentException if {@code virtualIds} is empty
      */
     public final void notifyViewsDisappeared(@NonNull AutofillId hostId,
-            @NonNull int[] virtualIds) {
-        Preconditions.checkArgument(!hostId.isVirtual(), "parent cannot be virtual");
+            @NonNull long[] virtualIds) {
+        Preconditions.checkArgument(hostId.isNonVirtual(), "parent cannot be virtual");
         Preconditions.checkArgument(!ArrayUtils.isEmpty(virtualIds), "virtual ids cannot be empty");
         if (!isContentCaptureEnabled()) return;
 
         // TODO(b/123036895): use a internalNotifyViewsDisappeared that optimizes how the event is
         // parcelized
-        for (int id : virtualIds) {
+        for (long id : virtualIds) {
             internalNotifyViewDisappeared(new AutofillId(hostId, id, getIdAsInt()));
         }
     }
@@ -405,9 +385,9 @@
      *
      * @throws IllegalArgumentException if the {@code parentId} is a virtual child id.
      */
-    public @NonNull AutofillId newAutofillId(@NonNull AutofillId parentId, int virtualChildId) {
+    public @NonNull AutofillId newAutofillId(@NonNull AutofillId parentId, long virtualChildId) {
         Preconditions.checkNotNull(parentId);
-        Preconditions.checkArgument(!parentId.isVirtual(), "virtual ids cannot have children");
+        Preconditions.checkArgument(parentId.isNonVirtual(), "virtual ids cannot have children");
         return new AutofillId(parentId, virtualChildId, getIdAsInt());
     }
 
@@ -423,7 +403,7 @@
      */
     @NonNull
     public final ViewStructure newVirtualViewStructure(@NonNull AutofillId parentId,
-            int virtualId) {
+            long virtualId) {
         return new ViewNode.ViewStructureImpl(parentId, virtualId, getIdAsInt());
     }
 
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/tests/utils/SleepUtils/AlarmService/src/com/android/testing/alarmservice/Alarm.aidl b/core/java/android/view/contentcapture/UserDataRemovalRequest.aidl
similarity index 73%
copy from tests/utils/SleepUtils/AlarmService/src/com/android/testing/alarmservice/Alarm.aidl
copy to core/java/android/view/contentcapture/UserDataRemovalRequest.aidl
index 62a8c48..fbe47e0 100644
--- a/tests/utils/SleepUtils/AlarmService/src/com/android/testing/alarmservice/Alarm.aidl
+++ b/core/java/android/view/contentcapture/UserDataRemovalRequest.aidl
@@ -1,5 +1,5 @@
 /*
- * Copyright (C) 2013 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.
@@ -14,10 +14,6 @@
  * limitations under the License.
  */
 
-package com.android.testing.alarmservice;
+package android.view.contentcapture;
 
-interface Alarm {
-    int prepare();
-    int setAlarmAndWait(long timeoutMills);
-    int done();
-}
\ No newline at end of file
+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/contentcapture/ViewNode.java b/core/java/android/view/contentcapture/ViewNode.java
index cbc946b..0cabafa 100644
--- a/core/java/android/view/contentcapture/ViewNode.java
+++ b/core/java/android/view/contentcapture/ViewNode.java
@@ -617,7 +617,7 @@
         }
 
         @VisibleForTesting // Must be public to be accessed from FrameworkCoreTests' apk.
-        public ViewStructureImpl(@NonNull AutofillId parentId, int virtualId, int sessionId) {
+        public ViewStructureImpl(@NonNull AutofillId parentId, long virtualId, int sessionId) {
             mNode.mParentAutofillId = Preconditions.checkNotNull(parentId);
             mNode.mAutofillId = new AutofillId(parentId, virtualId, sessionId);
         }
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 86c5f18..0cb1800 100644
--- a/core/java/android/view/inputmethod/InputMethodManager.java
+++ b/core/java/android/view/inputmethod/InputMethodManager.java
@@ -27,6 +27,7 @@
 import android.annotation.TestApi;
 import android.annotation.UnsupportedAppUsage;
 import android.app.ActivityThread;
+import android.content.ComponentName;
 import android.content.ContentResolver;
 import android.content.Context;
 import android.content.pm.PackageManager;
@@ -922,19 +923,6 @@
         }
     }
 
-    /**
-     * Returns a list of VR InputMethod currently installed.
-     * @hide
-     */
-    @RequiresPermission(android.Manifest.permission.RESTRICTED_VR_ACCESS)
-    public List<InputMethodInfo> getVrInputMethodList() {
-        try {
-            return mService.getVrInputMethodList();
-        } catch (RemoteException e) {
-            throw e.rethrowFromSystemServer();
-        }
-    }
-
     public List<InputMethodInfo> getEnabledInputMethodList() {
         try {
             return mService.getEnabledInputMethodList();
@@ -1894,7 +1882,15 @@
 
     /**
      * Notify the event when the user tapped or clicked the text view.
+     *
+     * @param view {@link View} which is being clicked.
+     * @see InputMethodService#onViewClicked(boolean)
+     * @deprecated The semantics of this method can never be defined well for composite {@link View}
+     *             that works as a giant "Canvas", which can host its own UI hierarchy and sub focus
+     *             state. {@link android.webkit.WebView} is a good example. Application / IME
+     *             developers should not rely on this method.
      */
+    @Deprecated
     public void viewClicked(View view) {
         // Re-dispatch if there is a context mismatch.
         final InputMethodManager fallbackImm = getFallbackInputMethodManagerIfNecessary(view);
@@ -2494,14 +2490,58 @@
      * @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) {
-        try {
-            return mService.setCurrentInputMethodSubtype(subtype);
-        } catch (RemoteException e) {
-            throw e.rethrowFromSystemServer();
+        if (Process.myUid() == Process.SYSTEM_UID) {
+            Log.w(TAG, "System process should not call setCurrentInputMethodSubtype() because "
+                    + "almost always it is a bug under multi-user / multi-profile environment. "
+                    + "Consider directly interacting with InputMethodManagerService "
+                    + "via LocalServices.");
+            return false;
         }
+        if (subtype == null) {
+            // See the JavaDoc. This is how this method has worked.
+            return false;
+        }
+        final Context fallbackContext = ActivityThread.currentApplication();
+        if (fallbackContext == null) {
+            return false;
+        }
+        if (fallbackContext.checkSelfPermission(WRITE_SECURE_SETTINGS)
+                != PackageManager.PERMISSION_GRANTED) {
+            return false;
+        }
+        final ContentResolver contentResolver = fallbackContext.getContentResolver();
+        final String imeId = Settings.Secure.getString(contentResolver,
+                Settings.Secure.DEFAULT_INPUT_METHOD);
+        if (ComponentName.unflattenFromString(imeId) == null) {
+            // Null or invalid IME ID format.
+            return false;
+        }
+        final List<InputMethodSubtype> enabledSubtypes;
+        try {
+            enabledSubtypes = mService.getEnabledInputMethodSubtypeList(imeId, true);
+        } catch (RemoteException e) {
+            return false;
+        }
+        final int numSubtypes = enabledSubtypes.size();
+        for (int i = 0; i < numSubtypes; ++i) {
+            final InputMethodSubtype enabledSubtype = enabledSubtypes.get(i);
+            if (enabledSubtype.equals(subtype)) {
+                Settings.Secure.putInt(contentResolver,
+                        Settings.Secure.SELECTED_INPUT_METHOD_SUBTYPE, enabledSubtype.hashCode());
+                return true;
+            }
+        }
+        return false;
     }
 
     /**
@@ -2642,7 +2682,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);
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/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..a5b7c62 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;
@@ -567,6 +570,7 @@
         // TODO: Make this configurable.
         final float foreignTextThreshold = typeCount == 0 ? 0.5f : 0.7f;
         boolean isPrimaryAction = true;
+        final ArrayList<Intent> sourceIntents = new ArrayList<>();
         for (LabeledIntent labeledIntent : IntentFactory.create(
                 mContext, classifiedText, isForeignText(classifiedText, foreignTextThreshold),
                 referenceTime, highestScoringResult)) {
@@ -586,9 +590,15 @@
                 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) {
diff --git a/core/java/android/webkit/WebViewZygote.java b/core/java/android/webkit/WebViewZygote.java
index 29b3b3c..de1f3df 100644
--- a/core/java/android/webkit/WebViewZygote.java
+++ b/core/java/android/webkit/WebViewZygote.java
@@ -150,7 +150,8 @@
         }
 
         try {
-            sZygote = Process.zygoteProcess.startChildZygote(
+            String abi = sPackage.applicationInfo.primaryCpuAbi;
+            sZygote = Process.ZYGOTE_PROCESS.startChildZygote(
                     "com.android.internal.os.WebViewZygoteInit",
                     "webview_zygote",
                     Process.WEBVIEW_ZYGOTE_UID,
@@ -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/TextView.java b/core/java/android/widget/TextView.java
index 1085e5d..780fe8d 100644
--- a/core/java/android/widget/TextView.java
+++ b/core/java/android/widget/TextView.java
@@ -5117,7 +5117,7 @@
      * @attr ref android.R.styleable#TextView_scrollHorizontally
      * @see #setHorizontallyScrolling(boolean)
      */
-    public final boolean isHorizontallyScrolling() {
+    public final boolean isHorizontallyScrollable() {
         return mHorizontallyScrolling;
     }
 
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 b4d8322..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,12 +102,28 @@
 
     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.
      */
     // TODO(b/121287573): Replace with a system flag (setprop?)
-    private static final boolean USE_SHORTCUT_MANAGER_FOR_DIRECT_TARGETS = false;
+    private static final boolean USE_SHORTCUT_MANAGER_FOR_DIRECT_TARGETS = true;
+    private static final boolean USE_CHOOSER_TARGET_SERVICE_FOR_DIRECT_TARGETS = true;
+
     // TODO(b/121287224): Re-evaluate this limit
     private static final int SHARE_TARGET_QUERY_PACKAGE_LIMIT = 20;
 
@@ -136,6 +156,7 @@
     private static final int CHOOSER_TARGET_SERVICE_RESULT = 1;
     private static final int CHOOSER_TARGET_SERVICE_WATCHDOG_TIMEOUT = 2;
     private static final int SHORTCUT_MANAGER_SHARE_TARGET_RESULT = 3;
+    private static final int SHORTCUT_MANAGER_SHARE_TARGET_RESULT_COMPLETED = 4;
 
     private final Handler mChooserHandler = new Handler() {
         @Override
@@ -182,6 +203,9 @@
                         mChooserListAdapter.addServiceResults(resultInfo.originalTarget,
                                 resultInfo.resultTargets);
                     }
+                    break;
+
+                case SHORTCUT_MANAGER_SHARE_TARGET_RESULT_COMPLETED:
                     sendVoiceChoicesIfNeeded();
                     mChooserListAdapter.setShowServiceTargets(true);
                     break;
@@ -303,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);
         }
@@ -333,6 +386,10 @@
         }
         unbindRemainingServices();
         mChooserHandler.removeMessages(CHOOSER_TARGET_SERVICE_RESULT);
+        if (USE_PREDICTION_MANAGER_FOR_DIRECT_TARGETS) {
+            mAppPredictor.unregisterPredictionUpdates(mAppPredictorCallback);
+            mAppPredictor.destroy();
+        }
     }
 
     @Override
@@ -507,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);
@@ -516,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;
@@ -594,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);
@@ -622,34 +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.
-            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);
-            }
+            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();
@@ -704,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();
@@ -1178,13 +1262,17 @@
                     mTargetsNeedPruning = true;
                 }
             }
+
             if (USE_SHORTCUT_MANAGER_FOR_DIRECT_TARGETS) {
                 if (DEBUG) {
                     Log.d(TAG, "querying direct share targets from ShortcutManager");
                 }
                 queryDirectShareTargets(this);
-            } else {
-                if (DEBUG) Log.d(TAG, "List built querying services");
+            }
+            if (USE_CHOOSER_TARGET_SERVICE_FOR_DIRECT_TARGETS) {
+                if (DEBUG) {
+                    Log.d(TAG, "List built querying services");
+                }
                 queryTargetServices(this);
             }
         }
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/content/FileSystemProvider.java b/core/java/com/android/internal/content/FileSystemProvider.java
index a27dbea..18c4b46 100644
--- a/core/java/com/android/internal/content/FileSystemProvider.java
+++ b/core/java/com/android/internal/content/FileSystemProvider.java
@@ -74,6 +74,16 @@
 
     private static final boolean LOG_INOTIFY = false;
 
+    protected static final String SUPPORTED_QUERY_ARGS = joinNewline(
+            DocumentsContract.QUERY_ARG_DISPLAY_NAME,
+            DocumentsContract.QUERY_ARG_FILE_SIZE_OVER,
+            DocumentsContract.QUERY_ARG_LAST_MODIFIED_AFTER,
+            DocumentsContract.QUERY_ARG_MIME_TYPES);
+
+    private static String joinNewline(String... args) {
+        return TextUtils.join("\n", args);
+    }
+
     private String[] mDefaultProjection;
 
     @GuardedBy("mObservers")
diff --git a/core/java/com/android/internal/inputmethod/InputMethodPrivilegedOperations.java b/core/java/com/android/internal/inputmethod/InputMethodPrivilegedOperations.java
index 7600dc9..8978496 100644
--- a/core/java/com/android/internal/inputmethod/InputMethodPrivilegedOperations.java
+++ b/core/java/com/android/internal/inputmethod/InputMethodPrivilegedOperations.java
@@ -100,6 +100,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
      */
diff --git a/core/java/com/android/internal/logging/MetricsLogger.java b/core/java/com/android/internal/logging/MetricsLogger.java
index c4aa1d7..a691a24 100644
--- a/core/java/com/android/internal/logging/MetricsLogger.java
+++ b/core/java/com/android/internal/logging/MetricsLogger.java
@@ -25,7 +25,13 @@
 import com.android.internal.logging.nano.MetricsProto.MetricsEvent;
 
 /**
- * Log all the things.
+ * Writes sysui_multi_event records to the system event log.
+ *
+ * Prefer the methods write(LogMaker), or count() or histogram(). Replace legacy methods with
+ * their current equivalents when the opportunity arises.
+ *
+ * This class is a lightweight dependency barrier - it is cheap and easy to construct.
+ * Logging is also cheap, so it is not normally necessary to move logging off of the UI thread.
  *
  * @hide
  */
@@ -52,6 +58,7 @@
     public static final int VIEW_UNKNOWN = MetricsEvent.VIEW_UNKNOWN;
     public static final int LOGTAG = EventLogTags.SYSUI_MULTI_ACTION;
 
+    /** Write an event log record, consisting of content.serialize(). */
     @UnsupportedAppUsage
     public void write(LogMaker content) {
         if (content.getType() == MetricsEvent.TYPE_UNKNOWN) {
@@ -60,128 +67,145 @@
         saveLog(content);
     }
 
+    /** Add an integer value to the monotonically increasing counter with the given name. */
+    public void count(String name, int value) {
+        saveLog(new LogMaker(MetricsEvent.RESERVED_FOR_LOGBUILDER_COUNTER)
+                .setCounterName(name)
+                .setCounterValue(value));
+    }
+
+    /** Increment the bucket with the integer label on the histogram with the given name. */
+    public void histogram(String name, int bucket) {
+        // see LogHistogram in system/core/libmetricslogger/metrics_logger.cpp
+        saveLog(new LogMaker(MetricsEvent.RESERVED_FOR_LOGBUILDER_HISTOGRAM)
+                .setCounterName(name)
+                .setCounterBucket(bucket)
+                .setCounterValue(1));
+    }
+
+    /* Legacy logging methods follow.  These are all simple shorthands and can be replaced
+     * with an equivalent write(). */
+
+    /** Logs an OPEN event on the category.
+     *  Equivalent to write(new LogMaker(category).setType(MetricsEvent.TYPE_OPEN)) */
     public void visible(int category) throws IllegalArgumentException {
         if (Build.IS_DEBUGGABLE && category == VIEW_UNKNOWN) {
             throw new IllegalArgumentException("Must define metric category");
         }
-        EventLogTags.writeSysuiViewVisibility(category, 100);
         saveLog(new LogMaker(category).setType(MetricsEvent.TYPE_OPEN));
     }
 
+    /** Logs a CLOSE event on the category.
+     *  Equivalent to write(new LogMaker(category).setType(MetricsEvent.TYPE_CLOSE)) */
     public void hidden(int category) throws IllegalArgumentException {
         if (Build.IS_DEBUGGABLE && category == VIEW_UNKNOWN) {
             throw new IllegalArgumentException("Must define metric category");
         }
-        EventLogTags.writeSysuiViewVisibility(category, 0);
         saveLog(new LogMaker(category).setType(MetricsEvent.TYPE_CLOSE));
     }
 
-    public void visibility(int category, boolean visibile)
+    /** Logs an OPEN or CLOSE event on the category, depending on visible.
+     *  Equivalent to write(new LogMaker(category)
+     *                     .setType(visible ? MetricsEvent.TYPE_OPEN : MetricsEvent.TYPE_CLOSE)) */
+    public void visibility(int category, boolean visible)
             throws IllegalArgumentException {
-        if (visibile) {
+        if (visible) {
             visible(category);
         } else {
             hidden(category);
         }
     }
 
+    /** Logs an OPEN or CLOSE event on the category, depending on vis.
+     *  Equivalent to write(new LogMaker(category)
+                           .setType(vis == View.VISIBLE ?
+                                    MetricsEvent.TYPE_OPEN : MetricsEvent.TYPE_CLOSE)) */
     public void visibility(int category, int vis)
             throws IllegalArgumentException {
         visibility(category, vis == View.VISIBLE);
     }
 
+    /** Logs an ACTION event on the category.
+     * Equivalent to write(new LogMaker(category).setType(MetricsEvent.TYPE_ACTION)) */
     public void action(int category) {
-        EventLogTags.writeSysuiAction(category, "");
         saveLog(new LogMaker(category).setType(MetricsEvent.TYPE_ACTION));
     }
 
+    /** Logs an ACTION event on the category.
+     * Equivalent to write(new LogMaker(category).setType(MetricsEvent.TYPE_ACTION)
+                           .setSubtype(value) */
     public void action(int category, int value) {
-        EventLogTags.writeSysuiAction(category, Integer.toString(value));
         saveLog(new LogMaker(category).setType(MetricsEvent.TYPE_ACTION).setSubtype(value));
     }
 
+    /** Logs an ACTION event on the category.
+     * Equivalent to write(new LogMaker(category).setType(MetricsEvent.TYPE_ACTION)
+                           .setSubtype(value ? 1 : 0) */
     public void action(int category, boolean value) {
-        EventLogTags.writeSysuiAction(category, Boolean.toString(value));
         saveLog(new LogMaker(category).setType(MetricsEvent.TYPE_ACTION).setSubtype(value ? 1 : 0));
     }
 
+    /** Logs an ACTION event on the category.
+     * Equivalent to write(new LogMaker(category).setType(MetricsEvent.TYPE_ACTION)
+                           .setPackageName(value ? 1 : 0) */
     public void action(int category, String pkg) {
         if (Build.IS_DEBUGGABLE && category == VIEW_UNKNOWN) {
             throw new IllegalArgumentException("Must define metric category");
         }
-        EventLogTags.writeSysuiAction(category, pkg);
         saveLog(new LogMaker(category).setType(MetricsEvent.TYPE_ACTION).setPackageName(pkg));
     }
 
-    /** Add an integer value to the monotonically increasing counter with the given name. */
-    public void count(String name, int value) {
-        EventLogTags.writeSysuiCount(name, value);
-        saveLog(new LogMaker(MetricsEvent.RESERVED_FOR_LOGBUILDER_COUNTER)
-                    .setCounterName(name)
-                    .setCounterValue(value));
-    }
-
-    /** Increment the bucket with the integer label on the histogram with the given name. */
-    public void histogram(String name, int bucket) {
-        // see LogHistogram in system/core/libmetricslogger/metrics_logger.cpp
-        EventLogTags.writeSysuiHistogram(name, bucket);
-        saveLog(new LogMaker(MetricsEvent.RESERVED_FOR_LOGBUILDER_HISTOGRAM)
-                    .setCounterName(name)
-                    .setCounterBucket(bucket)
-                    .setCounterValue(1));
-    }
-
-    /** @deprecated use {@link #visible(int)} */
+    /** @deprecated because untestable; use {@link #visible(int)} */
     @Deprecated
     public static void visible(Context context, int category) throws IllegalArgumentException {
         getLogger().visible(category);
     }
 
-    /** @deprecated use {@link #hidden(int)} */
+    /** @deprecated because untestable; use {@link #hidden(int)} */
     @Deprecated
     public static void hidden(Context context, int category) throws IllegalArgumentException {
         getLogger().hidden(category);
     }
 
-    /** @deprecated use {@link #visibility(int, boolean)} */
+    /** @deprecated because untestable; use {@link #visibility(int, boolean)} */
     @Deprecated
     public static void visibility(Context context, int category, boolean visibile)
             throws IllegalArgumentException {
         getLogger().visibility(category, visibile);
     }
 
-    /** @deprecated use {@link #visibility(int, int)} */
+    /** @deprecated because untestable; use {@link #visibility(int, int)} */
     @Deprecated
     public static void visibility(Context context, int category, int vis)
             throws IllegalArgumentException {
         visibility(context, category, vis == View.VISIBLE);
     }
 
-    /** @deprecated use {@link #action(int)} */
+    /** @deprecated because untestable; use {@link #action(int)} */
     @Deprecated
     public static void action(Context context, int category) {
         getLogger().action(category);
     }
 
-    /** @deprecated use {@link #action(int, int)} */
+    /** @deprecated because untestable; use {@link #action(int, int)} */
     @Deprecated
     public static void action(Context context, int category, int value) {
         getLogger().action(category, value);
     }
 
-    /** @deprecated use {@link #action(int, boolean)} */
+    /** @deprecated because untestable; use {@link #action(int, boolean)} */
     @Deprecated
     public static void action(Context context, int category, boolean value) {
         getLogger().action(category, value);
     }
 
-    /** @deprecated use {@link #write(LogMaker)} */
+    /** @deprecated because untestable; use {@link #write(LogMaker)} */
     @Deprecated
     public static void action(LogMaker content) {
         getLogger().write(content);
     }
 
-    /** @deprecated use {@link #action(int, String)} */
+    /** @deprecated because untestable; use {@link #action(int, String)} */
     @Deprecated
     public static void action(Context context, int category, String pkg) {
         getLogger().action(category, pkg);
@@ -189,7 +213,7 @@
 
     /**
      * Add an integer value to the monotonically increasing counter with the given name.
-     * @deprecated use {@link #count(String, int)}
+     * @deprecated because untestable; use {@link #count(String, int)}
      */
     @Deprecated
     public static void count(Context context, String name, int value) {
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 17cc6af..c6afee2 100644
--- a/core/java/com/android/internal/os/BatteryStatsImpl.java
+++ b/core/java/com/android/internal/os/BatteryStatsImpl.java
@@ -87,6 +87,10 @@
 import com.android.internal.annotations.GuardedBy;
 import com.android.internal.annotations.VisibleForTesting;
 import com.android.internal.location.gnssmetrics.GnssMetrics;
+import com.android.internal.os.KernelCpuUidTimeReader.KernelCpuUidActiveTimeReader;
+import com.android.internal.os.KernelCpuUidTimeReader.KernelCpuUidClusterTimeReader;
+import com.android.internal.os.KernelCpuUidTimeReader.KernelCpuUidFreqTimeReader;
+import com.android.internal.os.KernelCpuUidTimeReader.KernelCpuUidUserSysTimeReader;
 import com.android.internal.util.ArrayUtils;
 import com.android.internal.util.FastPrintWriter;
 import com.android.internal.util.FastXmlSerializer;
@@ -187,18 +191,19 @@
     private final KernelWakelockStats mTmpWakelockStats = new KernelWakelockStats();
 
     @VisibleForTesting
-    protected KernelUidCpuTimeReader mKernelUidCpuTimeReader = new KernelUidCpuTimeReader();
+    protected KernelCpuUidUserSysTimeReader mCpuUidUserSysTimeReader =
+            new KernelCpuUidUserSysTimeReader(true);
     @VisibleForTesting
     protected KernelCpuSpeedReader[] mKernelCpuSpeedReaders;
     @VisibleForTesting
-    protected KernelUidCpuFreqTimeReader mKernelUidCpuFreqTimeReader =
-            new KernelUidCpuFreqTimeReader();
+    protected KernelCpuUidFreqTimeReader mCpuUidFreqTimeReader =
+            new KernelCpuUidFreqTimeReader(true);
     @VisibleForTesting
-    protected KernelUidCpuActiveTimeReader mKernelUidCpuActiveTimeReader =
-            new KernelUidCpuActiveTimeReader();
+    protected KernelCpuUidActiveTimeReader mCpuUidActiveTimeReader =
+            new KernelCpuUidActiveTimeReader(true);
     @VisibleForTesting
-    protected KernelUidCpuClusterTimeReader mKernelUidCpuClusterTimeReader =
-            new KernelUidCpuClusterTimeReader();
+    protected KernelCpuUidClusterTimeReader mCpuUidClusterTimeReader =
+            new KernelCpuUidClusterTimeReader(true);
     @VisibleForTesting
     protected KernelSingleUidTimeReader mKernelSingleUidTimeReader;
 
@@ -248,9 +253,9 @@
     /** Last time that RPM stats were updated by updateRpmStatsLocked. */
     private long mLastRpmStatsUpdateTimeMs = -RPM_STATS_UPDATE_FREQ_MS;
     /**
-     * Use a queue to delay removing UIDs from {@link KernelUidCpuTimeReader},
-     * {@link KernelUidCpuActiveTimeReader}, {@link KernelUidCpuClusterTimeReader},
-     * {@link KernelUidCpuFreqTimeReader} and from the Kernel.
+     * Use a queue to delay removing UIDs from {@link KernelCpuUidUserSysTimeReader},
+     * {@link KernelCpuUidActiveTimeReader}, {@link KernelCpuUidClusterTimeReader},
+     * {@link KernelCpuUidFreqTimeReader} and from the Kernel.
      *
      * Isolated and invalid UID info must be removed to conserve memory. However, STATSD and
      * Batterystats both need to access UID cpu time. To resolve this race condition, only
@@ -281,22 +286,22 @@
 
         void remove() {
             if (startUid == endUid) {
-                mKernelUidCpuTimeReader.removeUid(startUid);
-                mKernelUidCpuFreqTimeReader.removeUid(startUid);
+                mCpuUidUserSysTimeReader.removeUid(startUid);
+                mCpuUidFreqTimeReader.removeUid(startUid);
                 if (mConstants.TRACK_CPU_ACTIVE_CLUSTER_TIME) {
-                    mKernelUidCpuActiveTimeReader.removeUid(startUid);
-                    mKernelUidCpuClusterTimeReader.removeUid(startUid);
+                    mCpuUidActiveTimeReader.removeUid(startUid);
+                    mCpuUidClusterTimeReader.removeUid(startUid);
                 }
                 if (mKernelSingleUidTimeReader != null) {
                     mKernelSingleUidTimeReader.removeUid(startUid);
                 }
                 mNumUidsRemoved++;
             } else if (startUid < endUid) {
-                mKernelUidCpuFreqTimeReader.removeUidsInRange(startUid, endUid);
-                mKernelUidCpuTimeReader.removeUidsInRange(startUid, endUid);
+                mCpuUidFreqTimeReader.removeUidsInRange(startUid, endUid);
+                mCpuUidUserSysTimeReader.removeUidsInRange(startUid, endUid);
                 if (mConstants.TRACK_CPU_ACTIVE_CLUSTER_TIME) {
-                    mKernelUidCpuActiveTimeReader.removeUidsInRange(startUid, endUid);
-                    mKernelUidCpuClusterTimeReader.removeUidsInRange(startUid, endUid);
+                    mCpuUidActiveTimeReader.removeUidsInRange(startUid, endUid);
+                    mCpuUidClusterTimeReader.removeUidsInRange(startUid, endUid);
                 }
                 if (mKernelSingleUidTimeReader != null) {
                     mKernelSingleUidTimeReader.removeUidsInRange(startUid, endUid);
@@ -496,7 +501,7 @@
             }
 
             final SparseArray<long[]> allUidCpuFreqTimesMs =
-                    mKernelUidCpuFreqTimeReader.getAllUidCpuFreqTimeMs();
+                    mCpuUidFreqTimeReader.getAllUidCpuFreqTimeMs();
             // If the KernelSingleUidTimeReader has stale cpu times, then we shouldn't try to
             // compute deltas since it might result in mis-attributing cpu times to wrong states.
             if (mIsPerProcessStateCpuDataStale) {
@@ -553,16 +558,16 @@
                 return false;
             }
             if (mCpuFreqs == null) {
-                mCpuFreqs = mKernelUidCpuFreqTimeReader.readFreqs(mPowerProfile);
+                mCpuFreqs = mCpuUidFreqTimeReader.readFreqs(mPowerProfile);
             }
             if (mCpuFreqs != null) {
                 mKernelSingleUidTimeReader = new KernelSingleUidTimeReader(mCpuFreqs.length);
             } else {
-                mPerProcStateCpuTimesAvailable = mKernelUidCpuFreqTimeReader.allUidTimesAvailable();
+                mPerProcStateCpuTimesAvailable = mCpuUidFreqTimeReader.allUidTimesAvailable();
                 return false;
             }
         }
-        mPerProcStateCpuTimesAvailable = mKernelUidCpuFreqTimeReader.allUidTimesAvailable()
+        mPerProcStateCpuTimesAvailable = mCpuUidFreqTimeReader.allUidTimesAvailable()
                 && mKernelSingleUidTimeReader.singleUidCpuTimesAvailable();
         return true;
     }
@@ -11926,7 +11931,7 @@
         }
 
         if (mCpuFreqs == null) {
-            mCpuFreqs = mKernelUidCpuFreqTimeReader.readFreqs(mPowerProfile);
+            mCpuFreqs = mCpuUidFreqTimeReader.readFreqs(mPowerProfile);
         }
 
         // Calculate the wakelocks we have to distribute amongst. The system is excluded as it is
@@ -11952,12 +11957,12 @@
         // When the battery is not on, we don't attribute the cpu times to any timers but we still
         // need to take the snapshots.
         if (!onBattery) {
-            mKernelUidCpuTimeReader.readDelta(null);
-            mKernelUidCpuFreqTimeReader.readDelta(null);
+            mCpuUidUserSysTimeReader.readDelta(null);
+            mCpuUidFreqTimeReader.readDelta(null);
             mNumAllUidCpuTimeReads += 2;
             if (mConstants.TRACK_CPU_ACTIVE_CLUSTER_TIME) {
-                mKernelUidCpuActiveTimeReader.readDelta(null);
-                mKernelUidCpuClusterTimeReader.readDelta(null);
+                mCpuUidActiveTimeReader.readDelta(null);
+                mCpuUidClusterTimeReader.readDelta(null);
                 mNumAllUidCpuTimeReads += 2;
             }
             for (int cluster = mKernelCpuSpeedReaders.length - 1; cluster >= 0; --cluster) {
@@ -11967,7 +11972,7 @@
         }
 
         mUserInfoProvider.refreshUserIds();
-        final SparseLongArray updatedUids = mKernelUidCpuFreqTimeReader.perClusterTimesAvailable()
+        final SparseLongArray updatedUids = mCpuUidFreqTimeReader.perClusterTimesAvailable()
                 ? null : new SparseLongArray();
         readKernelUidCpuTimesLocked(partialTimersToConsider, updatedUids, onBattery);
         // updatedUids=null means /proc/uid_time_in_state provides snapshots of per-cluster cpu
@@ -12084,18 +12089,20 @@
         final int numWakelocks = partialTimers == null ? 0 : partialTimers.size();
         final long startTimeMs = mClocks.uptimeMillis();
 
-        mKernelUidCpuTimeReader.readDelta((uid, userTimeUs, systemTimeUs) -> {
+        mCpuUidUserSysTimeReader.readDelta((uid, timesUs) -> {
+            long userTimeUs = timesUs[0], systemTimeUs = timesUs[1];
+
             uid = mapUid(uid);
             if (Process.isIsolated(uid)) {
                 // This could happen if the isolated uid mapping was removed before that process
                 // was actually killed.
-                mKernelUidCpuTimeReader.removeUid(uid);
+                mCpuUidUserSysTimeReader.removeUid(uid);
                 Slog.d(TAG, "Got readings for an isolated uid with no mapping: " + uid);
                 return;
             }
             if (!mUserInfoProvider.exists(UserHandle.getUserId(uid))) {
                 Slog.d(TAG, "Got readings for an invalid user's uid " + uid);
-                mKernelUidCpuTimeReader.removeUid(uid);
+                mCpuUidUserSysTimeReader.removeUid(uid);
                 return;
             }
             final Uid u = getUidStatsLocked(uid);
@@ -12189,21 +12196,21 @@
     public void readKernelUidCpuFreqTimesLocked(@Nullable ArrayList<StopwatchTimer> partialTimers,
             boolean onBattery, boolean onBatteryScreenOff) {
         final boolean perClusterTimesAvailable =
-                mKernelUidCpuFreqTimeReader.perClusterTimesAvailable();
+                mCpuUidFreqTimeReader.perClusterTimesAvailable();
         final int numWakelocks = partialTimers == null ? 0 : partialTimers.size();
         final int numClusters = mPowerProfile.getNumCpuClusters();
         mWakeLockAllocationsUs = null;
         final long startTimeMs = mClocks.uptimeMillis();
-        mKernelUidCpuFreqTimeReader.readDelta((uid, cpuFreqTimeMs) -> {
+        mCpuUidFreqTimeReader.readDelta((uid, cpuFreqTimeMs) -> {
             uid = mapUid(uid);
             if (Process.isIsolated(uid)) {
-                mKernelUidCpuFreqTimeReader.removeUid(uid);
+                mCpuUidFreqTimeReader.removeUid(uid);
                 Slog.d(TAG, "Got freq readings for an isolated uid with no mapping: " + uid);
                 return;
             }
             if (!mUserInfoProvider.exists(UserHandle.getUserId(uid))) {
                 Slog.d(TAG, "Got freq readings for an invalid user's uid " + uid);
-                mKernelUidCpuFreqTimeReader.removeUid(uid);
+                mCpuUidFreqTimeReader.removeUid(uid);
                 return;
             }
             final Uid u = getUidStatsLocked(uid);
@@ -12307,16 +12314,16 @@
     @VisibleForTesting
     public void readKernelUidCpuActiveTimesLocked(boolean onBattery) {
         final long startTimeMs = mClocks.uptimeMillis();
-        mKernelUidCpuActiveTimeReader.readDelta((uid, cpuActiveTimesMs) -> {
+        mCpuUidActiveTimeReader.readDelta((uid, cpuActiveTimesMs) -> {
             uid = mapUid(uid);
             if (Process.isIsolated(uid)) {
-                mKernelUidCpuActiveTimeReader.removeUid(uid);
+                mCpuUidActiveTimeReader.removeUid(uid);
                 Slog.w(TAG, "Got active times for an isolated uid with no mapping: " + uid);
                 return;
             }
             if (!mUserInfoProvider.exists(UserHandle.getUserId(uid))) {
                 Slog.w(TAG, "Got active times for an invalid user's uid " + uid);
-                mKernelUidCpuActiveTimeReader.removeUid(uid);
+                mCpuUidActiveTimeReader.removeUid(uid);
                 return;
             }
             final Uid u = getUidStatsLocked(uid);
@@ -12336,16 +12343,16 @@
     @VisibleForTesting
     public void readKernelUidCpuClusterTimesLocked(boolean onBattery) {
         final long startTimeMs = mClocks.uptimeMillis();
-        mKernelUidCpuClusterTimeReader.readDelta((uid, cpuClusterTimesMs) -> {
+        mCpuUidClusterTimeReader.readDelta((uid, cpuClusterTimesMs) -> {
             uid = mapUid(uid);
             if (Process.isIsolated(uid)) {
-                mKernelUidCpuClusterTimeReader.removeUid(uid);
+                mCpuUidClusterTimeReader.removeUid(uid);
                 Slog.w(TAG, "Got cluster times for an isolated uid with no mapping: " + uid);
                 return;
             }
             if (!mUserInfoProvider.exists(UserHandle.getUserId(uid))) {
                 Slog.w(TAG, "Got cluster times for an invalid user's uid " + uid);
-                mKernelUidCpuClusterTimeReader.removeUid(uid);
+                mCpuUidClusterTimeReader.removeUid(uid);
                 return;
             }
             final Uid u = getUidStatsLocked(uid);
@@ -13344,7 +13351,7 @@
         private static final boolean DEFAULT_TRACK_CPU_TIMES_BY_PROC_STATE = false;
         private static final boolean DEFAULT_TRACK_CPU_ACTIVE_CLUSTER_TIME = true;
         private static final long DEFAULT_PROC_STATE_CPU_TIMES_READ_DELAY_MS = 5_000;
-        private static final long DEFAULT_KERNEL_UID_READERS_THROTTLE_TIME = 10_000;
+        private static final long DEFAULT_KERNEL_UID_READERS_THROTTLE_TIME = 1_000;
         private static final long DEFAULT_UID_REMOVE_DELAY_MS = 5L * 60L * 1000L;
         private static final long DEFAULT_EXTERNAL_STATS_COLLECTION_RATE_LIMIT_MS = 600_000;
         private static final long DEFAULT_BATTERY_LEVEL_COLLECTION_DELAY_MS = 300_000;
@@ -13357,7 +13364,9 @@
         public boolean TRACK_CPU_TIMES_BY_PROC_STATE = DEFAULT_TRACK_CPU_TIMES_BY_PROC_STATE;
         public boolean TRACK_CPU_ACTIVE_CLUSTER_TIME = DEFAULT_TRACK_CPU_ACTIVE_CLUSTER_TIME;
         public long PROC_STATE_CPU_TIMES_READ_DELAY_MS = DEFAULT_PROC_STATE_CPU_TIMES_READ_DELAY_MS;
-        public long KERNEL_UID_READERS_THROTTLE_TIME = DEFAULT_KERNEL_UID_READERS_THROTTLE_TIME;
+        /* Do not set default value for KERNEL_UID_READERS_THROTTLE_TIME. Need to trigger an
+         * update when startObserving. */
+        public long KERNEL_UID_READERS_THROTTLE_TIME;
         public long UID_REMOVE_DELAY_MS = DEFAULT_UID_REMOVE_DELAY_MS;
         public long EXTERNAL_STATS_COLLECTION_RATE_LIMIT_MS
                 = DEFAULT_EXTERNAL_STATS_COLLECTION_RATE_LIMIT_MS;
@@ -13386,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();
         }
 
@@ -13434,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) {
@@ -13464,11 +13493,11 @@
         private void updateKernelUidReadersThrottleTime(long oldTimeMs, long newTimeMs) {
             KERNEL_UID_READERS_THROTTLE_TIME = newTimeMs;
             if (oldTimeMs != newTimeMs) {
-                mKernelUidCpuTimeReader.setThrottleInterval(KERNEL_UID_READERS_THROTTLE_TIME);
-                mKernelUidCpuFreqTimeReader.setThrottleInterval(KERNEL_UID_READERS_THROTTLE_TIME);
-                mKernelUidCpuActiveTimeReader.setThrottleInterval(KERNEL_UID_READERS_THROTTLE_TIME);
-                mKernelUidCpuClusterTimeReader
-                        .setThrottleInterval(KERNEL_UID_READERS_THROTTLE_TIME);
+                mCpuUidUserSysTimeReader.setThrottle(KERNEL_UID_READERS_THROTTLE_TIME);
+                mCpuUidFreqTimeReader.setThrottle(KERNEL_UID_READERS_THROTTLE_TIME);
+                mCpuUidActiveTimeReader.setThrottle(KERNEL_UID_READERS_THROTTLE_TIME);
+                mCpuUidClusterTimeReader
+                        .setThrottle(KERNEL_UID_READERS_THROTTLE_TIME);
             }
         }
 
diff --git a/core/java/com/android/internal/os/BinderCallsStats.java b/core/java/com/android/internal/os/BinderCallsStats.java
index 051a96c..2c272de 100644
--- a/core/java/com/android/internal/os/BinderCallsStats.java
+++ b/core/java/com/android/internal/os/BinderCallsStats.java
@@ -49,10 +49,11 @@
  * 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;
+    private static final String DEBUG_ENTRY_PREFIX = "__DEBUG_";
 
     private static class OverflowBinder extends Binder {}
 
@@ -347,7 +348,7 @@
         callStat.callingUid = uid;
         callStat.recordedCallCount = 1;
         callStat.callCount = 1;
-        callStat.methodName = "__DEBUG_" + variableName;
+        callStat.methodName = DEBUG_ENTRY_PREFIX + variableName;
         callStat.latencyMicros = value;
         return callStat;
     }
@@ -398,6 +399,10 @@
         final List<ExportedCallStat> exportedCallStats = getExportedCallStats();
         exportedCallStats.sort(BinderCallsStats::compareByCpuDesc);
         for (ExportedCallStat e : exportedCallStats) {
+            if (e.methodName.startsWith(DEBUG_ENTRY_PREFIX)) {
+                // Do not dump debug entries.
+                continue;
+            }
             sb.setLength(0);
             sb.append("    ")
                     .append(packageMap.mapUid(e.callingUid))
diff --git a/core/java/com/android/internal/os/KernelCpuProcReader.java b/core/java/com/android/internal/os/KernelCpuProcReader.java
deleted file mode 100644
index c233ea8..0000000
--- a/core/java/com/android/internal/os/KernelCpuProcReader.java
+++ /dev/null
@@ -1,162 +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.internal.os;
-
-import android.os.StrictMode;
-import android.os.SystemClock;
-import android.util.Slog;
-
-import com.android.internal.annotations.VisibleForTesting;
-
-import java.io.FileNotFoundException;
-import java.io.IOException;
-import java.io.InputStream;
-import java.nio.ByteBuffer;
-import java.nio.ByteOrder;
-import java.nio.file.Files;
-import java.nio.file.NoSuchFileException;
-import java.nio.file.Path;
-import java.nio.file.Paths;
-import java.util.Arrays;
-
-/**
- * Reads cpu time proc files with throttling (adjustable interval).
- *
- * KernelCpuProcReader is implemented as singletons for built-in kernel proc files. Get___Instance()
- * method will return corresponding reader instance. In order to prevent frequent GC,
- * KernelCpuProcReader reuses a {@link ByteBuffer} to store data read from proc files.
- *
- * A KernelCpuProcReader instance keeps an error counter. When the number of read errors within that
- * instance accumulates to 5, this instance will reject all further read requests.
- *
- * Each KernelCpuProcReader instance also has a throttler. Throttle interval can be adjusted via
- * {@link #setThrottleInterval(long)} method. Default throttle interval is 3000ms. If current
- * timestamp based on {@link SystemClock#elapsedRealtime()} is less than throttle interval from
- * the last read timestamp, {@link #readBytes()} will return previous result.
- *
- * A KernelCpuProcReader instance is thread-unsafe. Caller needs to hold a lock on this object while
- * accessing its instance methods or digesting the return values.
- */
-public class KernelCpuProcReader {
-    private static final String TAG = "KernelCpuProcReader";
-    private static final int ERROR_THRESHOLD = 5;
-    // Throttle interval in milliseconds
-    private static final long DEFAULT_THROTTLE_INTERVAL = 3000L;
-    private static final int MAX_BUFFER_SIZE = 1024 * 1024;
-    private static final String PROC_UID_FREQ_TIME = "/proc/uid_cpupower/time_in_state";
-    private static final String PROC_UID_ACTIVE_TIME = "/proc/uid_cpupower/concurrent_active_time";
-    private static final String PROC_UID_CLUSTER_TIME = "/proc/uid_cpupower/concurrent_policy_time";
-
-    private static final KernelCpuProcReader mFreqTimeReader = new KernelCpuProcReader(
-            PROC_UID_FREQ_TIME);
-    private static final KernelCpuProcReader mActiveTimeReader = new KernelCpuProcReader(
-            PROC_UID_ACTIVE_TIME);
-    private static final KernelCpuProcReader mClusterTimeReader = new KernelCpuProcReader(
-            PROC_UID_CLUSTER_TIME);
-
-    public static KernelCpuProcReader getFreqTimeReaderInstance() {
-        return mFreqTimeReader;
-    }
-
-    public static KernelCpuProcReader getActiveTimeReaderInstance() {
-        return mActiveTimeReader;
-    }
-
-    public static KernelCpuProcReader getClusterTimeReaderInstance() {
-        return mClusterTimeReader;
-    }
-
-    private int mErrors;
-    private long mThrottleInterval = DEFAULT_THROTTLE_INTERVAL;
-    private long mLastReadTime = Long.MIN_VALUE;
-    private final Path mProc;
-    private byte[] mBuffer = new byte[8 * 1024];
-    private int mContentSize;
-
-    @VisibleForTesting
-    public KernelCpuProcReader(String procFile) {
-        mProc = Paths.get(procFile);
-    }
-
-    /**
-     * Reads all bytes from the corresponding proc file.
-     *
-     * If elapsed time since last call to this method is less than the throttle interval, it will
-     * return previous result. When IOException accumulates to 5, it will always return null. This
-     * method is thread-unsafe, so is the return value. Caller needs to hold a lock on this
-     * object while calling this method and digesting its return value.
-     *
-     * @return a {@link ByteBuffer} containing all bytes from the proc file.
-     */
-    public ByteBuffer readBytes() {
-        if (mErrors >= ERROR_THRESHOLD) {
-            return null;
-        }
-        if (SystemClock.elapsedRealtime() < mLastReadTime + mThrottleInterval) {
-            if (mContentSize > 0) {
-                return ByteBuffer.wrap(mBuffer, 0, mContentSize).asReadOnlyBuffer()
-                        .order(ByteOrder.nativeOrder());
-            }
-            return null;
-        }
-        mLastReadTime = SystemClock.elapsedRealtime();
-        mContentSize = 0;
-        final int oldMask = StrictMode.allowThreadDiskReadsMask();
-        try (InputStream in = Files.newInputStream(mProc)) {
-            int numBytes = 0;
-            int curr;
-            while ((curr = in.read(mBuffer, numBytes, mBuffer.length - numBytes)) >= 0) {
-                numBytes += curr;
-                if (numBytes == mBuffer.length) {
-                    // Hit the limit. Resize mBuffer.
-                    if (mBuffer.length == MAX_BUFFER_SIZE) {
-                        mErrors++;
-                        Slog.e(TAG, "Proc file is too large: " + mProc);
-                        return null;
-                    }
-                    mBuffer = Arrays.copyOf(mBuffer,
-                            Math.min(mBuffer.length << 1, MAX_BUFFER_SIZE));
-                }
-            }
-            mContentSize = numBytes;
-            return ByteBuffer.wrap(mBuffer, 0, mContentSize).asReadOnlyBuffer()
-                    .order(ByteOrder.nativeOrder());
-        } catch (NoSuchFileException | FileNotFoundException e) {
-            // Happens when the kernel does not provide this file. Not a big issue. Just log it.
-            mErrors++;
-            Slog.w(TAG, "File not exist: " + mProc);
-        } catch (IOException e) {
-            mErrors++;
-            Slog.e(TAG, "Error reading: " + mProc, e);
-        } finally {
-            StrictMode.setThreadPolicyMask(oldMask);
-        }
-        return null;
-    }
-
-    /**
-     * Sets the throttle interval. Set to 0 will disable throttling. Thread-unsafe, holding a lock
-     * on this object is recommended.
-     *
-     * @param throttleInterval throttle interval in milliseconds
-     */
-    public void setThrottleInterval(long throttleInterval) {
-        if (throttleInterval >= 0) {
-            mThrottleInterval = throttleInterval;
-        }
-    }
-}
diff --git a/core/java/com/android/internal/os/KernelCpuUidTimeReader.java b/core/java/com/android/internal/os/KernelCpuUidTimeReader.java
index 7021b57..e6d044f 100644
--- a/core/java/com/android/internal/os/KernelCpuUidTimeReader.java
+++ b/core/java/com/android/internal/os/KernelCpuUidTimeReader.java
@@ -177,6 +177,9 @@
      * The file contains a monotonically increasing count of time for a single boot. This class
      * maintains the previous results of a call to {@link #readDelta} in order to provide a proper
      * delta.
+     *
+     * The second parameter of the callback is a long[] with 2 elements, [user time in us, system
+     * time in us].
      */
     public static class KernelCpuUidUserSysTimeReader extends KernelCpuUidTimeReader<long[]> {
         private static final String REMOVE_UID_PROC_FILE = "/proc/uid_cputime/remove_uid_range";
diff --git a/core/java/com/android/internal/os/KernelSingleUidTimeReader.java b/core/java/com/android/internal/os/KernelSingleUidTimeReader.java
index ad62852..3c43a11 100644
--- a/core/java/com/android/internal/os/KernelSingleUidTimeReader.java
+++ b/core/java/com/android/internal/os/KernelSingleUidTimeReader.java
@@ -16,7 +16,6 @@
 package com.android.internal.os;
 
 import static com.android.internal.annotations.VisibleForTesting.Visibility.PACKAGE;
-import static com.android.internal.os.KernelUidCpuFreqTimeReader.UID_TIMES_PROC_FILE;
 
 import android.annotation.NonNull;
 import android.util.Slog;
@@ -34,11 +33,12 @@
 
 @VisibleForTesting(visibility = PACKAGE)
 public class KernelSingleUidTimeReader {
-    private final String TAG = KernelUidCpuFreqTimeReader.class.getName();
-    private final boolean DBG = false;
+    private static final String TAG = KernelSingleUidTimeReader.class.getName();
+    private static final boolean DBG = false;
 
-    private final String PROC_FILE_DIR = "/proc/uid/";
-    private final String PROC_FILE_NAME = "/time_in_state";
+    private static final String PROC_FILE_DIR = "/proc/uid/";
+    private static final String PROC_FILE_NAME = "/time_in_state";
+    private static final String UID_TIMES_PROC_FILE = "/proc/uid_time_in_state";
 
     @VisibleForTesting
     public static final int TOTAL_READ_ERROR_COUNT = 5;
diff --git a/core/java/com/android/internal/os/KernelUidCpuActiveTimeReader.java b/core/java/com/android/internal/os/KernelUidCpuActiveTimeReader.java
deleted file mode 100644
index bd8a67a..0000000
--- a/core/java/com/android/internal/os/KernelUidCpuActiveTimeReader.java
+++ /dev/null
@@ -1,179 +0,0 @@
-/*
- * Copyright (C) 2017 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.internal.os;
-
-import android.annotation.Nullable;
-import android.util.Slog;
-import android.util.SparseArray;
-
-import com.android.internal.annotations.VisibleForTesting;
-
-import java.nio.ByteBuffer;
-import java.nio.IntBuffer;
-import java.util.function.Consumer;
-
-/**
- * Reads binary proc file /proc/uid_cpupower/concurrent_active_time and reports CPU active time to
- * BatteryStats to compute {@link PowerProfile#POWER_CPU_ACTIVE}.
- *
- * concurrent_active_time is an array of u32's in the following format:
- * [n, uid0, time0a, time0b, ..., time0n,
- * uid1, time1a, time1b, ..., time1n,
- * uid2, time2a, time2b, ..., time2n, etc.]
- * where n is the total number of cpus (num_possible_cpus)
- * ...
- * timeXn means the CPU time that a UID X spent running concurrently with n other processes.
- * The file contains a monotonically increasing count of time for a single boot. This class
- * maintains the previous results of a call to {@link #readDelta} in order to provide a
- * proper delta.
- *
- * This class uses a throttler to reject any {@link #readDelta} call within
- * {@link #mThrottleInterval}. This is different from the throttler in {@link KernelCpuProcReader},
- * which has a shorter throttle interval and returns cached result from last read when the request
- * is throttled.
- *
- * This class is NOT thread-safe and NOT designed to be accessed by more than one caller since each
- * caller has its own view of delta.
- */
-public class KernelUidCpuActiveTimeReader extends
-        KernelUidCpuTimeReaderBase<KernelUidCpuActiveTimeReader.Callback> {
-    private static final String TAG = KernelUidCpuActiveTimeReader.class.getSimpleName();
-
-    private final KernelCpuProcReader mProcReader;
-    private SparseArray<Double> mLastUidCpuActiveTimeMs = new SparseArray<>();
-    private int mCores;
-
-    public interface Callback extends KernelUidCpuTimeReaderBase.Callback {
-        /**
-         * Notifies when new data is available.
-         *
-         * @param uid             uid int
-         * @param cpuActiveTimeMs cpu active time spent by this uid in milliseconds
-         */
-        void onUidCpuActiveTime(int uid, long cpuActiveTimeMs);
-    }
-
-    public KernelUidCpuActiveTimeReader() {
-        mProcReader = KernelCpuProcReader.getActiveTimeReaderInstance();
-    }
-
-    @VisibleForTesting
-    public KernelUidCpuActiveTimeReader(KernelCpuProcReader procReader) {
-        mProcReader = procReader;
-    }
-
-    @Override
-    protected void readDeltaImpl(@Nullable Callback callback) {
-        readImpl((buf) -> {
-            int uid = buf.get();
-            double activeTime = sumActiveTime(buf);
-            if (activeTime > 0) {
-                double delta = activeTime - mLastUidCpuActiveTimeMs.get(uid, 0.0);
-                if (delta > 0) {
-                    mLastUidCpuActiveTimeMs.put(uid, activeTime);
-                    if (callback != null) {
-                        callback.onUidCpuActiveTime(uid, (long) delta);
-                    }
-                } else if (delta < 0) {
-                    Slog.e(TAG, "Negative delta from active time proc: " + delta);
-                }
-            }
-        });
-    }
-
-    public void readAbsolute(Callback callback) {
-        readImpl((buf) -> {
-            int uid = buf.get();
-            double activeTime = sumActiveTime(buf);
-            if (activeTime > 0) {
-                callback.onUidCpuActiveTime(uid, (long) activeTime);
-            }
-        });
-    }
-
-    private double sumActiveTime(IntBuffer buffer) {
-        double sum = 0;
-        boolean corrupted = false;
-        for (int j = 1; j <= mCores; j++) {
-            int time = buffer.get();
-            if (time < 0) {
-                // Even if error happens, we still need to continue reading.
-                // Buffer cannot be skipped.
-                Slog.e(TAG, "Negative time from active time proc: " + time);
-                corrupted = true;
-            } else {
-                sum += (double) time * 10 / j; // Unit is 10ms.
-            }
-        }
-        return corrupted ? -1 : sum;
-    }
-
-    /**
-     * readImpl accepts a callback to process the uid entry. readDeltaImpl needs to store the last
-     * seen results while processing the buffer, while readAbsolute returns the absolute value read
-     * from the buffer without storing. So readImpl contains the common logic of the two, leaving
-     * the difference to a processUid function.
-     *
-     * @param processUid the callback function to process the uid entry in the buffer.
-     */
-    private void readImpl(Consumer<IntBuffer> processUid) {
-        synchronized (mProcReader) {
-            final ByteBuffer bytes = mProcReader.readBytes();
-            if (bytes == null || bytes.remaining() <= 4) {
-                // Error already logged in mProcReader.
-                return;
-            }
-            if ((bytes.remaining() & 3) != 0) {
-                Slog.wtf(TAG,
-                        "Cannot parse active time proc bytes to int: " + bytes.remaining());
-                return;
-            }
-            final IntBuffer buf = bytes.asIntBuffer();
-            final int cores = buf.get();
-            if (mCores != 0 && cores != mCores) {
-                Slog.wtf(TAG, "Cpu active time wrong # cores: " + cores);
-                return;
-            }
-            mCores = cores;
-            if (cores <= 0 || buf.remaining() % (cores + 1) != 0) {
-                Slog.wtf(TAG,
-                        "Cpu active time format error: " + buf.remaining() + " / " + (cores
-                                + 1));
-                return;
-            }
-            int numUids = buf.remaining() / (cores + 1);
-            for (int i = 0; i < numUids; i++) {
-                processUid.accept(buf);
-            }
-            if (DEBUG) {
-                Slog.d(TAG, "Read uids: " + numUids);
-            }
-        }
-    }
-
-    public void removeUid(int uid) {
-        mLastUidCpuActiveTimeMs.delete(uid);
-    }
-
-    public void removeUidsInRange(int startUid, int endUid) {
-        mLastUidCpuActiveTimeMs.put(startUid, null);
-        mLastUidCpuActiveTimeMs.put(endUid, null);
-        final int firstIndex = mLastUidCpuActiveTimeMs.indexOfKey(startUid);
-        final int lastIndex = mLastUidCpuActiveTimeMs.indexOfKey(endUid);
-        mLastUidCpuActiveTimeMs.removeAtRange(firstIndex, lastIndex - firstIndex + 1);
-    }
-}
diff --git a/core/java/com/android/internal/os/KernelUidCpuClusterTimeReader.java b/core/java/com/android/internal/os/KernelUidCpuClusterTimeReader.java
deleted file mode 100644
index 3cbfaea..0000000
--- a/core/java/com/android/internal/os/KernelUidCpuClusterTimeReader.java
+++ /dev/null
@@ -1,239 +0,0 @@
-/*
- * Copyright (C) 2017 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.internal.os;
-
-import android.annotation.Nullable;
-import android.util.Slog;
-import android.util.SparseArray;
-
-import com.android.internal.annotations.VisibleForTesting;
-
-import java.nio.ByteBuffer;
-import java.nio.IntBuffer;
-import java.util.function.Consumer;
-
-/**
- * Reads binary proc file /proc/uid_cpupower/concurrent_policy_time and reports CPU cluster times
- * to BatteryStats to compute cluster power. See
- * {@link PowerProfile#getAveragePowerForCpuCluster(int)}.
- *
- * concurrent_policy_time is an array of u32's in the following format:
- * [n, x0, ..., xn, uid0, time0a, time0b, ..., time0n,
- * uid1, time1a, time1b, ..., time1n,
- * uid2, time2a, time2b, ..., time2n, etc.]
- * where n is the number of policies
- * xi is the number cpus on a particular policy
- * Each uidX is followed by x0 time entries corresponding to the time UID X spent on cluster0
- * running concurrently with 0, 1, 2, ..., x0 - 1 other processes, then followed by x1, ..., xn
- * time entries.
- *
- * The file contains a monotonically increasing count of time for a single boot. This class
- * maintains the previous results of a call to {@link #readDelta} in order to provide a
- * proper delta.
- *
- * This class uses a throttler to reject any {@link #readDelta} call within
- * {@link #mThrottleInterval}. This is different from the throttler in {@link KernelCpuProcReader},
- * which has a shorter throttle interval and returns cached result from last read when the request
- * is throttled.
- *
- * This class is NOT thread-safe and NOT designed to be accessed by more than one caller since each
- * caller has its own view of delta.
- */
-public class KernelUidCpuClusterTimeReader extends
-        KernelUidCpuTimeReaderBase<KernelUidCpuClusterTimeReader.Callback> {
-    private static final String TAG = KernelUidCpuClusterTimeReader.class.getSimpleName();
-
-    private final KernelCpuProcReader mProcReader;
-    private SparseArray<double[]> mLastUidPolicyTimeMs = new SparseArray<>();
-
-    private int mNumClusters = -1;
-    private int mNumCores;
-    private int[] mNumCoresOnCluster;
-
-    private double[] mCurTime; // Reuse to avoid GC.
-    private long[] mDeltaTime; // Reuse to avoid GC.
-    private long[] mCurTimeRounded; // Reuse to avoid GC.
-
-    public interface Callback extends KernelUidCpuTimeReaderBase.Callback {
-        /**
-         * Notifies when new data is available.
-         *
-         * @param uid              uid int
-         * @param cpuClusterTimeMs an array of times spent by this uid on corresponding clusters.
-         *                         The array index is the cluster index.
-         */
-        void onUidCpuPolicyTime(int uid, long[] cpuClusterTimeMs);
-    }
-
-    public KernelUidCpuClusterTimeReader() {
-        mProcReader = KernelCpuProcReader.getClusterTimeReaderInstance();
-    }
-
-    @VisibleForTesting
-    public KernelUidCpuClusterTimeReader(KernelCpuProcReader procReader) {
-        mProcReader = procReader;
-    }
-
-    @Override
-    protected void readDeltaImpl(@Nullable Callback cb) {
-        readImpl((buf) -> {
-            int uid = buf.get();
-            double[] lastTimes = mLastUidPolicyTimeMs.get(uid);
-            if (lastTimes == null) {
-                lastTimes = new double[mNumClusters];
-                mLastUidPolicyTimeMs.put(uid, lastTimes);
-            }
-            if (!sumClusterTime(buf, mCurTime)) {
-                return;
-            }
-            boolean valid = true;
-            boolean notify = false;
-            for (int i = 0; i < mNumClusters; i++) {
-                mDeltaTime[i] = (long) (mCurTime[i] - lastTimes[i]);
-                if (mDeltaTime[i] < 0) {
-                    Slog.e(TAG, "Negative delta from cluster time proc: " + mDeltaTime[i]);
-                    valid = false;
-                }
-                notify |= mDeltaTime[i] > 0;
-            }
-            if (notify && valid) {
-                System.arraycopy(mCurTime, 0, lastTimes, 0, mNumClusters);
-                if (cb != null) {
-                    cb.onUidCpuPolicyTime(uid, mDeltaTime);
-                }
-            }
-        });
-    }
-
-    public void readAbsolute(Callback callback) {
-        readImpl((buf) -> {
-            int uid = buf.get();
-            if (sumClusterTime(buf, mCurTime)) {
-                for (int i = 0; i < mNumClusters; i++) {
-                    mCurTimeRounded[i] = (long) mCurTime[i];
-                }
-                callback.onUidCpuPolicyTime(uid, mCurTimeRounded);
-            }
-        });
-    }
-
-    private boolean sumClusterTime(IntBuffer buffer, double[] clusterTime) {
-        boolean valid = true;
-        for (int i = 0; i < mNumClusters; i++) {
-            clusterTime[i] = 0;
-            for (int j = 1; j <= mNumCoresOnCluster[i]; j++) {
-                int time = buffer.get();
-                if (time < 0) {
-                    Slog.e(TAG, "Negative time from cluster time proc: " + time);
-                    valid = false;
-                }
-                clusterTime[i] += (double) time * 10 / j; // Unit is 10ms.
-            }
-        }
-        return valid;
-    }
-
-    /**
-     * readImpl accepts a callback to process the uid entry. readDeltaImpl needs to store the last
-     * seen results while processing the buffer, while readAbsolute returns the absolute value read
-     * from the buffer without storing. So readImpl contains the common logic of the two, leaving
-     * the difference to a processUid function.
-     *
-     * @param processUid the callback function to process the uid entry in the buffer.
-     */
-    private void readImpl(Consumer<IntBuffer> processUid) {
-        synchronized (mProcReader) {
-            ByteBuffer bytes = mProcReader.readBytes();
-            if (bytes == null || bytes.remaining() <= 4) {
-                // Error already logged in mProcReader.
-                return;
-            }
-            if ((bytes.remaining() & 3) != 0) {
-                Slog.wtf(TAG,
-                        "Cannot parse cluster time proc bytes to int: " + bytes.remaining());
-                return;
-            }
-            IntBuffer buf = bytes.asIntBuffer();
-            final int numClusters = buf.get();
-            if (numClusters <= 0) {
-                Slog.wtf(TAG, "Cluster time format error: " + numClusters);
-                return;
-            }
-            if (mNumClusters == -1) {
-                mNumClusters = numClusters;
-            }
-            if (buf.remaining() < numClusters) {
-                Slog.wtf(TAG, "Too few data left in the buffer: " + buf.remaining());
-                return;
-            }
-            if (mNumCores <= 0) {
-                if (!readCoreInfo(buf, numClusters)) {
-                    return;
-                }
-            } else {
-                buf.position(buf.position() + numClusters);
-            }
-
-            if (buf.remaining() % (mNumCores + 1) != 0) {
-                Slog.wtf(TAG,
-                        "Cluster time format error: " + buf.remaining() + " / " + (mNumCores
-                                + 1));
-                return;
-            }
-            int numUids = buf.remaining() / (mNumCores + 1);
-
-            for (int i = 0; i < numUids; i++) {
-                processUid.accept(buf);
-            }
-            if (DEBUG) {
-                Slog.d(TAG, "Read uids: " + numUids);
-            }
-        }
-    }
-
-    // Returns if it has read valid info.
-    private boolean readCoreInfo(IntBuffer buf, int numClusters) {
-        int numCores = 0;
-        int[] numCoresOnCluster = new int[numClusters];
-        for (int i = 0; i < numClusters; i++) {
-            numCoresOnCluster[i] = buf.get();
-            numCores += numCoresOnCluster[i];
-        }
-        if (numCores <= 0) {
-            Slog.e(TAG, "Invalid # cores from cluster time proc file: " + numCores);
-            return false;
-        }
-        mNumCores = numCores;
-        mNumCoresOnCluster = numCoresOnCluster;
-        mCurTime = new double[numClusters];
-        mDeltaTime = new long[numClusters];
-        mCurTimeRounded = new long[numClusters];
-        return true;
-    }
-
-    public void removeUid(int uid) {
-        mLastUidPolicyTimeMs.delete(uid);
-    }
-
-    public void removeUidsInRange(int startUid, int endUid) {
-        mLastUidPolicyTimeMs.put(startUid, null);
-        mLastUidPolicyTimeMs.put(endUid, null);
-        final int firstIndex = mLastUidPolicyTimeMs.indexOfKey(startUid);
-        final int lastIndex = mLastUidPolicyTimeMs.indexOfKey(endUid);
-        mLastUidPolicyTimeMs.removeAtRange(firstIndex, lastIndex - firstIndex + 1);
-    }
-}
diff --git a/core/java/com/android/internal/os/KernelUidCpuFreqTimeReader.java b/core/java/com/android/internal/os/KernelUidCpuFreqTimeReader.java
deleted file mode 100644
index 5b46d0f..0000000
--- a/core/java/com/android/internal/os/KernelUidCpuFreqTimeReader.java
+++ /dev/null
@@ -1,296 +0,0 @@
-/*
- * Copyright (C) 2017 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.internal.os;
-
-import static com.android.internal.util.Preconditions.checkNotNull;
-
-import android.annotation.NonNull;
-import android.annotation.Nullable;
-import android.os.StrictMode;
-import android.util.IntArray;
-import android.util.Slog;
-import android.util.SparseArray;
-
-import com.android.internal.annotations.VisibleForTesting;
-
-import java.io.BufferedReader;
-import java.io.FileReader;
-import java.io.IOException;
-import java.nio.ByteBuffer;
-import java.nio.IntBuffer;
-import java.util.function.Consumer;
-
-/**
- * Reads /proc/uid_time_in_state which has the format:
- *
- * uid: [freq1] [freq2] [freq3] ...
- * [uid1]: [time in freq1] [time in freq2] [time in freq3] ...
- * [uid2]: [time in freq1] [time in freq2] [time in freq3] ...
- * ...
- *
- * Binary variation reads /proc/uid_cpupower/time_in_state in the following format:
- * [n, uid0, time0a, time0b, ..., time0n,
- * uid1, time1a, time1b, ..., time1n,
- * uid2, time2a, time2b, ..., time2n, etc.]
- * where n is the total number of frequencies.
- *
- * This provides the times a UID's processes spent executing at each different cpu frequency.
- * The file contains a monotonically increasing count of time for a single boot. This class
- * maintains the previous results of a call to {@link #readDelta} in order to provide a proper
- * delta.
- *
- * This class uses a throttler to reject any {@link #readDelta} call within
- * {@link #mThrottleInterval}. This is different from the throttler in {@link KernelCpuProcReader},
- * which has a shorter throttle interval and returns cached result from last read when the request
- * is throttled.
- *
- * This class is NOT thread-safe and NOT designed to be accessed by more than one caller since each
- * caller has its own view of delta.
- */
-public class KernelUidCpuFreqTimeReader extends
-        KernelUidCpuTimeReaderBase<KernelUidCpuFreqTimeReader.Callback> {
-    private static final String TAG = KernelUidCpuFreqTimeReader.class.getSimpleName();
-    static final String UID_TIMES_PROC_FILE = "/proc/uid_time_in_state";
-
-    public interface Callback extends KernelUidCpuTimeReaderBase.Callback {
-        void onUidCpuFreqTime(int uid, long[] cpuFreqTimeMs);
-    }
-
-    private long[] mCpuFreqs;
-    private long[] mCurTimes; // Reuse to prevent GC.
-    private long[] mDeltaTimes; // Reuse to prevent GC.
-    private int mCpuFreqsCount;
-    private final KernelCpuProcReader mProcReader;
-
-    private SparseArray<long[]> mLastUidCpuFreqTimeMs = new SparseArray<>();
-
-    // We check the existence of proc file a few times (just in case it is not ready yet when we
-    // start reading) and if it is not available, we simply ignore further read requests.
-    private static final int TOTAL_READ_ERROR_COUNT = 5;
-    private int mReadErrorCounter;
-    private boolean mPerClusterTimesAvailable;
-    private boolean mAllUidTimesAvailable = true;
-
-    public KernelUidCpuFreqTimeReader() {
-        mProcReader = KernelCpuProcReader.getFreqTimeReaderInstance();
-    }
-
-    @VisibleForTesting
-    public KernelUidCpuFreqTimeReader(KernelCpuProcReader procReader) {
-        mProcReader = procReader;
-    }
-
-    public boolean perClusterTimesAvailable() {
-        return mPerClusterTimesAvailable;
-    }
-
-    public boolean allUidTimesAvailable() {
-        return mAllUidTimesAvailable;
-    }
-
-    public SparseArray<long[]> getAllUidCpuFreqTimeMs() {
-        return mLastUidCpuFreqTimeMs;
-    }
-
-    public long[] readFreqs(@NonNull PowerProfile powerProfile) {
-        checkNotNull(powerProfile);
-        if (mCpuFreqs != null) {
-            // No need to read cpu freqs more than once.
-            return mCpuFreqs;
-        }
-        if (!mAllUidTimesAvailable) {
-            return null;
-        }
-        final int oldMask = StrictMode.allowThreadDiskReadsMask();
-        try (BufferedReader reader = new BufferedReader(new FileReader(UID_TIMES_PROC_FILE))) {
-            return readFreqs(reader, powerProfile);
-        } catch (IOException e) {
-            if (++mReadErrorCounter >= TOTAL_READ_ERROR_COUNT) {
-                mAllUidTimesAvailable = false;
-            }
-            Slog.e(TAG, "Failed to read " + UID_TIMES_PROC_FILE + ": " + e);
-            return null;
-        } finally {
-            StrictMode.setThreadPolicyMask(oldMask);
-        }
-    }
-
-    @VisibleForTesting
-    public long[] readFreqs(BufferedReader reader, PowerProfile powerProfile)
-            throws IOException {
-        final String line = reader.readLine();
-        if (line == null) {
-            return null;
-        }
-        final String[] freqStr = line.split(" ");
-        // First item would be "uid: " which needs to be ignored.
-        mCpuFreqsCount = freqStr.length - 1;
-        mCpuFreqs = new long[mCpuFreqsCount];
-        mCurTimes = new long[mCpuFreqsCount];
-        mDeltaTimes = new long[mCpuFreqsCount];
-        for (int i = 0; i < mCpuFreqsCount; ++i) {
-            mCpuFreqs[i] = Long.parseLong(freqStr[i + 1], 10);
-        }
-
-        // Check if the freqs in the proc file correspond to per-cluster freqs.
-        final IntArray numClusterFreqs = extractClusterInfoFromProcFileFreqs();
-        final int numClusters = powerProfile.getNumCpuClusters();
-        if (numClusterFreqs.size() == numClusters) {
-            mPerClusterTimesAvailable = true;
-            for (int i = 0; i < numClusters; ++i) {
-                if (numClusterFreqs.get(i) != powerProfile.getNumSpeedStepsInCpuCluster(i)) {
-                    mPerClusterTimesAvailable = false;
-                    break;
-                }
-            }
-        } else {
-            mPerClusterTimesAvailable = false;
-        }
-        Slog.i(TAG, "mPerClusterTimesAvailable=" + mPerClusterTimesAvailable);
-        return mCpuFreqs;
-    }
-
-    @Override
-    @VisibleForTesting
-    public void readDeltaImpl(@Nullable Callback callback) {
-        if (mCpuFreqs == null) {
-            return;
-        }
-        readImpl((buf) -> {
-            int uid = buf.get();
-            long[] lastTimes = mLastUidCpuFreqTimeMs.get(uid);
-            if (lastTimes == null) {
-                lastTimes = new long[mCpuFreqsCount];
-                mLastUidCpuFreqTimeMs.put(uid, lastTimes);
-            }
-            if (!getFreqTimeForUid(buf, mCurTimes)) {
-                return;
-            }
-            boolean notify = false;
-            boolean valid = true;
-            for (int i = 0; i < mCpuFreqsCount; i++) {
-                mDeltaTimes[i] = mCurTimes[i] - lastTimes[i];
-                if (mDeltaTimes[i] < 0) {
-                    Slog.e(TAG, "Negative delta from freq time proc: " + mDeltaTimes[i]);
-                    valid = false;
-                }
-                notify |= mDeltaTimes[i] > 0;
-            }
-            if (notify && valid) {
-                System.arraycopy(mCurTimes, 0, lastTimes, 0, mCpuFreqsCount);
-                if (callback != null) {
-                    callback.onUidCpuFreqTime(uid, mDeltaTimes);
-                }
-            }
-        });
-    }
-
-    public void readAbsolute(Callback callback) {
-        readImpl((buf) -> {
-            int uid = buf.get();
-            if (getFreqTimeForUid(buf, mCurTimes)) {
-                callback.onUidCpuFreqTime(uid, mCurTimes);
-            }
-        });
-    }
-
-    private boolean getFreqTimeForUid(IntBuffer buffer, long[] freqTime) {
-        boolean valid = true;
-        for (int i = 0; i < mCpuFreqsCount; i++) {
-            freqTime[i] = (long) buffer.get() * 10; // Unit is 10ms.
-            if (freqTime[i] < 0) {
-                Slog.e(TAG, "Negative time from freq time proc: " + freqTime[i]);
-                valid = false;
-            }
-        }
-        return valid;
-    }
-
-    /**
-     * readImpl accepts a callback to process the uid entry. readDeltaImpl needs to store the last
-     * seen results while processing the buffer, while readAbsolute returns the absolute value read
-     * from the buffer without storing. So readImpl contains the common logic of the two, leaving
-     * the difference to a processUid function.
-     *
-     * @param processUid the callback function to process the uid entry in the buffer.
-     */
-    private void readImpl(Consumer<IntBuffer> processUid) {
-        synchronized (mProcReader) {
-            ByteBuffer bytes = mProcReader.readBytes();
-            if (bytes == null || bytes.remaining() <= 4) {
-                // Error already logged in mProcReader.
-                return;
-            }
-            if ((bytes.remaining() & 3) != 0) {
-                Slog.wtf(TAG, "Cannot parse freq time proc bytes to int: " + bytes.remaining());
-                return;
-            }
-            IntBuffer buf = bytes.asIntBuffer();
-            final int freqs = buf.get();
-            if (freqs != mCpuFreqsCount) {
-                Slog.wtf(TAG, "Cpu freqs expect " + mCpuFreqsCount + " , got " + freqs);
-                return;
-            }
-            if (buf.remaining() % (freqs + 1) != 0) {
-                Slog.wtf(TAG, "Freq time format error: " + buf.remaining() + " / " + (freqs + 1));
-                return;
-            }
-            int numUids = buf.remaining() / (freqs + 1);
-            for (int i = 0; i < numUids; i++) {
-                processUid.accept(buf);
-            }
-            if (DEBUG) {
-                Slog.d(TAG, "Read uids: #" + numUids);
-            }
-        }
-    }
-
-    public void removeUid(int uid) {
-        mLastUidCpuFreqTimeMs.delete(uid);
-    }
-
-    public void removeUidsInRange(int startUid, int endUid) {
-        mLastUidCpuFreqTimeMs.put(startUid, null);
-        mLastUidCpuFreqTimeMs.put(endUid, null);
-        final int firstIndex = mLastUidCpuFreqTimeMs.indexOfKey(startUid);
-        final int lastIndex = mLastUidCpuFreqTimeMs.indexOfKey(endUid);
-        mLastUidCpuFreqTimeMs.removeAtRange(firstIndex, lastIndex - firstIndex + 1);
-    }
-
-    /**
-     * Extracts no. of cpu clusters and no. of freqs in each of these clusters from the freqs
-     * read from the proc file.
-     *
-     * We need to assume that freqs in each cluster are strictly increasing.
-     * For e.g. if the freqs read from proc file are: 12, 34, 15, 45, 12, 15, 52. Then it means
-     * there are 3 clusters: (12, 34), (15, 45), (12, 15, 52)
-     *
-     * @return an IntArray filled with no. of freqs in each cluster.
-     */
-    private IntArray extractClusterInfoFromProcFileFreqs() {
-        final IntArray numClusterFreqs = new IntArray();
-        int freqsFound = 0;
-        for (int i = 0; i < mCpuFreqsCount; ++i) {
-            freqsFound++;
-            if (i + 1 == mCpuFreqsCount || mCpuFreqs[i + 1] <= mCpuFreqs[i]) {
-                numClusterFreqs.add(freqsFound);
-                freqsFound = 0;
-            }
-        }
-        return numClusterFreqs;
-    }
-}
diff --git a/core/java/com/android/internal/os/KernelUidCpuTimeReader.java b/core/java/com/android/internal/os/KernelUidCpuTimeReader.java
deleted file mode 100644
index 97b7211..0000000
--- a/core/java/com/android/internal/os/KernelUidCpuTimeReader.java
+++ /dev/null
@@ -1,213 +0,0 @@
-/*
- * Copyright (C) 2015 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.internal.os;
-
-import android.annotation.Nullable;
-import android.os.StrictMode;
-import android.os.SystemClock;
-import android.text.TextUtils;
-import android.util.Slog;
-import android.util.SparseLongArray;
-import android.util.TimeUtils;
-
-import java.io.BufferedReader;
-import java.io.FileReader;
-import java.io.FileWriter;
-import java.io.IOException;
-
-/**
- * Reads /proc/uid_cputime/show_uid_stat which has the line format:
- *
- * uid: user_time_micro_seconds system_time_micro_seconds power_in_milli-amp-micro_seconds
- *
- * This provides the time a UID's processes spent executing in user-space and kernel-space.
- * The file contains a monotonically increasing count of time for a single boot. This class
- * maintains the previous results of a call to {@link #readDelta} in order to provide a proper
- * delta.
- */
-public class KernelUidCpuTimeReader extends
-        KernelUidCpuTimeReaderBase<KernelUidCpuTimeReader.Callback> {
-    private static final String TAG = KernelUidCpuTimeReader.class.getSimpleName();
-    private static final String sProcFile = "/proc/uid_cputime/show_uid_stat";
-    private static final String sRemoveUidProcFile = "/proc/uid_cputime/remove_uid_range";
-
-    /**
-     * Callback interface for processing each line of the proc file.
-     */
-    public interface Callback extends KernelUidCpuTimeReaderBase.Callback {
-        /**
-         * @param uid          UID of the app
-         * @param userTimeUs   time spent executing in user space in microseconds
-         * @param systemTimeUs time spent executing in kernel space in microseconds
-         */
-        void onUidCpuTime(int uid, long userTimeUs, long systemTimeUs);
-    }
-
-    private SparseLongArray mLastUserTimeUs = new SparseLongArray();
-    private SparseLongArray mLastSystemTimeUs = new SparseLongArray();
-    private long mLastTimeReadUs = 0;
-
-    /**
-     * Reads the proc file, calling into the callback with a delta of time for each UID.
-     *
-     * @param callback The callback to invoke for each line of the proc file. If null,
-     *                 the data is consumed and subsequent calls to readDelta will provide
-     *                 a fresh delta.
-     */
-    @Override
-    protected void readDeltaImpl(@Nullable Callback callback) {
-        final int oldMask = StrictMode.allowThreadDiskReadsMask();
-        long nowUs = SystemClock.elapsedRealtime() * 1000;
-        try (BufferedReader reader = new BufferedReader(new FileReader(sProcFile))) {
-            TextUtils.SimpleStringSplitter splitter = new TextUtils.SimpleStringSplitter(' ');
-            String line;
-            while ((line = reader.readLine()) != null) {
-                splitter.setString(line);
-                final String uidStr = splitter.next();
-                final int uid = Integer.parseInt(uidStr.substring(0, uidStr.length() - 1), 10);
-                final long userTimeUs = Long.parseLong(splitter.next(), 10);
-                final long systemTimeUs = Long.parseLong(splitter.next(), 10);
-
-                boolean notifyCallback = false;
-                long userTimeDeltaUs = userTimeUs;
-                long systemTimeDeltaUs = systemTimeUs;
-                // Only report if there is a callback and if this is not the first read.
-                if (callback != null && mLastTimeReadUs != 0) {
-                    int index = mLastUserTimeUs.indexOfKey(uid);
-                    if (index >= 0) {
-                        userTimeDeltaUs -= mLastUserTimeUs.valueAt(index);
-                        systemTimeDeltaUs -= mLastSystemTimeUs.valueAt(index);
-
-                        final long timeDiffUs = nowUs - mLastTimeReadUs;
-                        if (userTimeDeltaUs < 0 || systemTimeDeltaUs < 0) {
-                            StringBuilder sb = new StringBuilder("Malformed cpu data for UID=");
-                            sb.append(uid).append("!\n");
-                            sb.append("Time between reads: ");
-                            TimeUtils.formatDuration(timeDiffUs / 1000, sb);
-                            sb.append("\n");
-                            sb.append("Previous times: u=");
-                            TimeUtils.formatDuration(mLastUserTimeUs.valueAt(index) / 1000, sb);
-                            sb.append(" s=");
-                            TimeUtils.formatDuration(mLastSystemTimeUs.valueAt(index) / 1000, sb);
-
-                            sb.append("\nCurrent times: u=");
-                            TimeUtils.formatDuration(userTimeUs / 1000, sb);
-                            sb.append(" s=");
-                            TimeUtils.formatDuration(systemTimeUs / 1000, sb);
-                            sb.append("\nDelta: u=");
-                            TimeUtils.formatDuration(userTimeDeltaUs / 1000, sb);
-                            sb.append(" s=");
-                            TimeUtils.formatDuration(systemTimeDeltaUs / 1000, sb);
-                            Slog.e(TAG, sb.toString());
-
-                            userTimeDeltaUs = 0;
-                            systemTimeDeltaUs = 0;
-                        }
-                    }
-
-                    notifyCallback = (userTimeDeltaUs != 0 || systemTimeDeltaUs != 0);
-                }
-                mLastUserTimeUs.put(uid, userTimeUs);
-                mLastSystemTimeUs.put(uid, systemTimeUs);
-                if (notifyCallback) {
-                    callback.onUidCpuTime(uid, userTimeDeltaUs, systemTimeDeltaUs);
-                }
-            }
-        } catch (IOException e) {
-            Slog.e(TAG, "Failed to read uid_cputime: " + e.getMessage());
-        } finally {
-            StrictMode.setThreadPolicyMask(oldMask);
-        }
-        mLastTimeReadUs = nowUs;
-    }
-
-    /**
-     * Reads the proc file, calling into the callback with raw absolute value of time for each UID.
-     * @param callback The callback to invoke for each line of the proc file.
-     */
-    public void readAbsolute(Callback callback) {
-        final int oldMask = StrictMode.allowThreadDiskReadsMask();
-        try (BufferedReader reader = new BufferedReader(new FileReader(sProcFile))) {
-            TextUtils.SimpleStringSplitter splitter = new TextUtils.SimpleStringSplitter(' ');
-            String line;
-            while ((line = reader.readLine()) != null) {
-                splitter.setString(line);
-                final String uidStr = splitter.next();
-                final int uid = Integer.parseInt(uidStr.substring(0, uidStr.length() - 1), 10);
-                final long userTimeUs = Long.parseLong(splitter.next(), 10);
-                final long systemTimeUs = Long.parseLong(splitter.next(), 10);
-                callback.onUidCpuTime(uid, userTimeUs, systemTimeUs);
-            }
-        } catch (IOException e) {
-            Slog.e(TAG, "Failed to read uid_cputime: " + e.getMessage());
-        } finally {
-            StrictMode.setThreadPolicyMask(oldMask);
-        }
-    }
-
-    /**
-     * Removes the UID from the kernel module and from internal accounting data. Only
-     * {@link BatteryStatsImpl} and its child processes should call this, as the change on Kernel is
-     * visible system wide.
-     *
-     * @param uid The UID to remove.
-     */
-    public void removeUid(int uid) {
-        final int index = mLastSystemTimeUs.indexOfKey(uid);
-        if (index >= 0) {
-            mLastSystemTimeUs.removeAt(index);
-            mLastUserTimeUs.removeAt(index);
-        }
-        removeUidsFromKernelModule(uid, uid);
-    }
-
-    /**
-     * Removes UIDs in a given range from the kernel module and internal accounting data. Only
-     * {@link BatteryStatsImpl} and its child processes should call this, as the change on Kernel is
-     * visible system wide.
-     *
-     * @param startUid the first uid to remove
-     * @param endUid   the last uid to remove
-     */
-    public void removeUidsInRange(int startUid, int endUid) {
-        if (endUid < startUid) {
-            return;
-        }
-        mLastSystemTimeUs.put(startUid, 0);
-        mLastUserTimeUs.put(startUid, 0);
-        mLastSystemTimeUs.put(endUid, 0);
-        mLastUserTimeUs.put(endUid, 0);
-        final int startIndex = mLastSystemTimeUs.indexOfKey(startUid);
-        final int endIndex = mLastSystemTimeUs.indexOfKey(endUid);
-        mLastSystemTimeUs.removeAtRange(startIndex, endIndex - startIndex + 1);
-        mLastUserTimeUs.removeAtRange(startIndex, endIndex - startIndex + 1);
-        removeUidsFromKernelModule(startUid, endUid);
-    }
-
-    private void removeUidsFromKernelModule(int startUid, int endUid) {
-        Slog.d(TAG, "Removing uids " + startUid + "-" + endUid);
-        final int oldMask = StrictMode.allowThreadDiskWritesMask();
-        try (FileWriter writer = new FileWriter(sRemoveUidProcFile)) {
-            writer.write(startUid + "-" + endUid);
-            writer.flush();
-        } catch (IOException e) {
-            Slog.e(TAG, "failed to remove uids " + startUid + " - " + endUid
-                    + " from uid_cputime module", e);
-        } finally {
-            StrictMode.setThreadPolicyMask(oldMask);
-        }
-    }
-}
diff --git a/core/java/com/android/internal/os/KernelUidCpuTimeReaderBase.java b/core/java/com/android/internal/os/KernelUidCpuTimeReaderBase.java
deleted file mode 100644
index 11e50e1..0000000
--- a/core/java/com/android/internal/os/KernelUidCpuTimeReaderBase.java
+++ /dev/null
@@ -1,60 +0,0 @@
-/*
- * Copyright (C) 2017 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.internal.os;
-
-import android.annotation.Nullable;
-import android.os.SystemClock;
-import android.util.Slog;
-
-/**
- * The base class of all KernelUidCpuTimeReaders.
- *
- * This class is NOT designed to be thread-safe or accessed by more than one caller (due to
- * the nature of {@link #readDelta(Callback)}).
- */
-public abstract class KernelUidCpuTimeReaderBase<T extends KernelUidCpuTimeReaderBase.Callback> {
-    protected static final boolean DEBUG = false;
-    // Throttle interval in milliseconds
-    private static final long DEFAULT_THROTTLE_INTERVAL = 10_000L;
-
-    private final String TAG = this.getClass().getSimpleName();
-    private long mLastTimeReadMs = Long.MIN_VALUE;
-    private long mThrottleInterval = DEFAULT_THROTTLE_INTERVAL;
-
-    // A generic Callback interface (used by readDelta) to be extended by subclasses.
-    public interface Callback {
-    }
-
-    public void readDelta(@Nullable T cb) {
-        if (SystemClock.elapsedRealtime() < mLastTimeReadMs + mThrottleInterval) {
-            if (DEBUG) {
-                Slog.d(TAG, "Throttle");
-            }
-            return;
-        }
-        readDeltaImpl(cb);
-        mLastTimeReadMs = SystemClock.elapsedRealtime();
-    }
-
-    protected abstract void readDeltaImpl(@Nullable T cb);
-
-    public void setThrottleInterval(long throttleInterval) {
-        if (throttleInterval >= 0) {
-            mThrottleInterval = throttleInterval;
-        }
-    }
-}
diff --git a/core/java/com/android/internal/os/LooperStats.java b/core/java/com/android/internal/os/LooperStats.java
index 9a7fb9f..0f0eedd 100644
--- a/core/java/com/android/internal/os/LooperStats.java
+++ b/core/java/com/android/internal/os/LooperStats.java
@@ -37,6 +37,7 @@
  * @hide Only for use within the system server.
  */
 public class LooperStats implements Looper.Observer {
+    public static final String DEBUG_ENTRY_PREFIX = "__DEBUG_";
     private static final int SESSION_POOL_SIZE = 50;
 
     @GuardedBy("mLock")
@@ -165,7 +166,7 @@
     }
 
     private ExportedEntry createDebugEntry(String variableName, long value) {
-        final Entry entry = new Entry("__DEBUG_" + variableName);
+        final Entry entry = new Entry(DEBUG_ENTRY_PREFIX + variableName);
         entry.messageCount = 1;
         entry.recordedMessageCount = 1;
         entry.totalLatencyMicro = value;
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 705bae4..069413f 100644
--- a/core/java/com/android/internal/os/Zygote.java
+++ b/core/java/com/android/internal/os/Zygote.java
@@ -16,13 +16,33 @@
 
 package com.android.internal.os;
 
+import static android.system.OsConstants.O_CLOEXEC;
+
+import static com.android.internal.os.ZygoteConnectionConstants.MAX_ZYGOTE_ARGC;
+
+import android.net.Credentials;
+import android.net.LocalServerSocket;
+import android.net.LocalSocket;
+import android.os.FactoryTest;
 import android.os.IVold;
+import android.os.Process;
+import android.os.SystemProperties;
 import android.os.Trace;
 import android.system.ErrnoException;
 import android.system.Os;
+import android.util.Log;
 
 import dalvik.system.ZygoteHooks;
 
+import libcore.io.IoUtils;
+
+import java.io.BufferedReader;
+import java.io.ByteArrayOutputStream;
+import java.io.DataOutputStream;
+import java.io.FileDescriptor;
+import java.io.IOException;
+import java.io.InputStreamReader;
+
 /** @hide */
 public final class Zygote {
     /*
@@ -94,6 +114,24 @@
     /** Read-write external storage should be mounted instead of package sandbox */
     public static final int MOUNT_EXTERNAL_FULL = IVold.REMOUNT_MODE_FULL;
 
+    /** 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.
+     *
+     * 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;
+
+    /**
+     * File descriptor used for communication between the signal handler and the ZygoteServer poll
+     * loop.
+     * */
+    protected static FileDescriptor sBlastulaPoolEventFD;
+
     private static final ZygoteHooks VM_HOOKS = new ZygoteHooks();
 
     /**
@@ -123,10 +161,52 @@
      */
     public static final String CHILD_ZYGOTE_UID_RANGE_END = "--uid-range-end=";
 
+    /** Prefix prepended to socket names created by init */
+    private static final String ANDROID_SOCKET_PREFIX = "ANDROID_SOCKET_";
+
+    /**
+     * The maximum value that the sBlastulaPoolMax variable may take.  This value
+     * is a mirror of BLASTULA_POOL_MAX_LIMIT found in com_android_internal_os_Zygote.cpp.
+     */
+    static final int BLASTULA_POOL_MAX_LIMIT = 10;
+
+    /**
+     * The minimum value that the sBlastulaPoolMin variable may take.
+     */
+    static final int BLASTULA_POOL_MIN_LIMIT = 1;
+
+    /**
+     * The runtime-adjustable maximum Blastula pool size.
+     */
+    static int sBlastulaPoolMax = BLASTULA_POOL_MAX_LIMIT;
+
+    /**
+     * The runtime-adjustable minimum Blastula pool size.
+     */
+    static int sBlastulaPoolMin = BLASTULA_POOL_MIN_LIMIT;
+
+    /**
+     * The runtime-adjustable value used to determine when to re-fill the
+     * blastula pool.  The pool will be re-filled when
+     * (sBlastulaPoolMax - gBlastulaPoolCount) >= sBlastulaPoolRefillThreshold.
+     */
+    // 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() */
+    protected static final int[][] INT_ARRAY_2D = new int[0][0];
+
     private Zygote() {}
 
     /** Called for some security initialization before any fork. */
-    native static void nativeSecurityInit();
+    static native void nativeSecurityInit();
 
     /**
      * Forks a new VM instance.  The current VM must have been started
@@ -165,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);
+                uid, gid, gids, runtimeFlags, rlimits, mountExternal, seInfo, niceName, fdsToClose,
+                fdsToIgnore, startChildZygote, instructionSet, appDataDir, packageName,
+                packagesForUID, visibleVolIDs);
         // Enable tracing as soon as possible for the child process.
         if (pid == 0) {
             Trace.setTracingEnabled(true, runtimeFlags);
@@ -184,15 +264,65 @@
         return pid;
     }
 
-    native private static 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);
+    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);
+
+    /**
+     * Specialize a Blastula instance.  The current VM must have been started
+     * with the -Xzygote flag.
+     *
+     * @param uid  The UNIX uid that the new process should setuid() to before spawning any threads
+     * @param gid  The UNIX gid that the new process should setgid() to before spawning any threads
+     * @param gids null-ok;  A list of UNIX gids that the new process should
+     * setgroups() to before spawning any threads
+     * @param runtimeFlags  Bit flags that enable ART features
+     * @param rlimits null-ok  An array of rlimit tuples, with the second
+     * dimension having a length of 3 and representing
+     * (resource, rlim_cur, rlim_max). These are set via the posix
+     * setrlimit(2) call.
+     * @param seInfo null-ok  A string specifying SELinux information for
+     * the new process.
+     * @param niceName null-ok  A string specifying the process name.
+     * @param startChildZygote  If true, the new child process will itself be a
+     * new zygote process.
+     * @param instructionSet null-ok  The instruction set to use.
+     * @param appDataDir null-ok  The data directory of the app.
+     */
+    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) {
+
+        nativeSpecializeBlastula(uid, gid, gids, runtimeFlags, rlimits, mountExternal, seInfo,
+                                 niceName, startChildZygote, instructionSet, appDataDir,
+                                 packageName, packagesForUID, visibleVolIDs);
+
+        // Enable tracing as soon as possible for the child process.
+        Trace.setTracingEnabled(true, runtimeFlags);
+
+        // Note that this event ends at the end of handleChildProc.
+        Trace.traceBegin(Trace.TRACE_TAG_ACTIVITY_MANAGER, "PostFork");
+
+        /*
+         * This is called here (instead of after the fork but before the specialize) to maintain
+         * consistancy with the code paths for forkAndSpecialize.
+         *
+         * TODO (chriswailes): Look into moving this to immediately after the fork.
+         */
+        VM_HOOKS.postForkCommon();
+    }
+
+    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);
 
     /**
      * Called to do any initialization before starting an application.
      */
-    native static void nativePreApplicationInit();
+    static native void nativePreApplicationInit();
 
     /**
      * Special method to start the system server process. In addition to the
@@ -223,7 +353,8 @@
         // Resets nice priority for zygote process.
         resetNicePriority();
         int pid = nativeForkSystemServer(
-                uid, gid, gids, runtimeFlags, rlimits, permittedCapabilities, effectiveCapabilities);
+                uid, gid, gids, runtimeFlags, rlimits,
+                permittedCapabilities, effectiveCapabilities);
         // Enable tracing as soon as we enter the system_server.
         if (pid == 0) {
             Trace.setTracingEnabled(true, runtimeFlags);
@@ -232,13 +363,13 @@
         return pid;
     }
 
-    native private static int nativeForkSystemServer(int uid, int gid, int[] gids, int runtimeFlags,
+    private static native int nativeForkSystemServer(int uid, int gid, int[] gids, int runtimeFlags,
             int[][] rlimits, long permittedCapabilities, long effectiveCapabilities);
 
     /**
      * Lets children of the zygote inherit open file descriptors to this path.
      */
-    native protected static void nativeAllowFileAcrossFork(String path);
+    protected static native void nativeAllowFileAcrossFork(String path);
 
     /**
      * Installs a seccomp filter that limits setresuid()/setresgid() to the passed-in range
@@ -251,7 +382,485 @@
      * Zygote unmount storage space on initializing.
      * This method is called once.
      */
-    native protected static void nativeUnmountStorageOnInit();
+    protected static native void nativeUnmountStorageOnInit();
+
+    /**
+     * Get socket file descriptors (opened by init) from the environment and
+     * store them for access from native code later.
+     *
+     * @param isPrimary  True if this is the zygote process, false if it is zygote_secondary
+     */
+    public static void getSocketFDs(boolean isPrimary) {
+        nativeGetSocketFDs(isPrimary);
+    }
+
+    protected static native void nativeGetSocketFDs(boolean isPrimary);
+
+    /**
+     * Initialize the blastula pool and fill it with the desired number of
+     * processes.
+     */
+    protected static Runnable initBlastulaPool() {
+        if (BLASTULA_POOL_ENABLED) {
+            sBlastulaPoolEventFD = getBlastulaPoolEventFD();
+
+            return fillBlastulaPool(null);
+        } else {
+            return null;
+        }
+    }
+
+    /**
+     * Checks to see if the current policy says that pool should be refilled, and spawns new
+     * blastulas if necessary.
+     *
+     * NOTE: This function doesn't need to be guarded with BLASTULA_POOL_ENABLED because it is
+     *       only called from contexts that are only valid if the pool is enabled.
+     *
+     * @param sessionSocketRawFDs  Anonymous session sockets that are currently open
+     * @return In the Zygote process this function will always return null; in blastula processes
+     *         this function will return a Runnable object representing the new application that is
+     *         passed up from blastulaMain.
+     */
+    protected static Runnable fillBlastulaPool(int[] sessionSocketRawFDs) {
+        Trace.traceBegin(Trace.TRACE_TAG_ACTIVITY_MANAGER, "Zygote:FillBlastulaPool");
+
+        int blastulaPoolCount = getBlastulaPoolCount();
+
+        int numBlastulasToSpawn = sBlastulaPoolMax - blastulaPoolCount;
+
+        if (blastulaPoolCount < sBlastulaPoolMin
+                || numBlastulasToSpawn >= sBlastulaPoolRefillThreshold) {
+
+            // Disable some VM functionality and reset some system values
+            // before forking.
+            VM_HOOKS.preFork();
+            resetNicePriority();
+
+            while (blastulaPoolCount++ < sBlastulaPoolMax) {
+                Runnable caller = forkBlastula(sessionSocketRawFDs);
+
+                if (caller != null) {
+                    return caller;
+                }
+            }
+
+            // Re-enable runtime services for the Zygote.  Blastula services
+            // are re-enabled in specializeBlastula.
+            VM_HOOKS.postForkCommon();
+
+            Log.i("zygote", "Filled the blastula pool. New blastulas: " + numBlastulasToSpawn);
+        }
+
+        Trace.traceEnd(Trace.TRACE_TAG_ACTIVITY_MANAGER);
+
+        return null;
+    }
+
+    /**
+     * @return Number of blastulas currently in the pool
+     */
+    private static int getBlastulaPoolCount() {
+        return nativeGetBlastulaPoolCount();
+    }
+
+    private static native int nativeGetBlastulaPoolCount();
+
+    /**
+     * @return The event FD used for communication between the signal handler and the ZygoteServer
+     *         poll loop
+     */
+    private static FileDescriptor getBlastulaPoolEventFD() {
+        FileDescriptor fd = new FileDescriptor();
+        fd.setInt$(nativeGetBlastulaPoolEventFD());
+
+        return fd;
+    }
+
+    private static native int nativeGetBlastulaPoolEventFD();
+
+    /**
+     * Fork a new blastula process from the zygote
+     *
+     * @param sessionSocketRawFDs  Anonymous session sockets that are currently open
+     * @return In the Zygote process this function will always return null; in blastula processes
+     *         this function will return a Runnable object representing the new application that is
+     *         passed up from blastulaMain.
+     */
+    private static Runnable forkBlastula(int[] sessionSocketRawFDs) {
+        FileDescriptor[] pipeFDs = null;
+
+        try {
+            pipeFDs = Os.pipe2(O_CLOEXEC);
+        } catch (ErrnoException errnoEx) {
+            throw new IllegalStateException("Unable to create blastula pipe.", errnoEx);
+        }
+
+        int pid =
+                nativeForkBlastula(pipeFDs[0].getInt$(), pipeFDs[1].getInt$(), sessionSocketRawFDs);
+
+        if (pid == 0) {
+            IoUtils.closeQuietly(pipeFDs[0]);
+            return blastulaMain(pipeFDs[1]);
+        } else {
+            // The read-end of the pipe will be closed by the native code.
+            // See removeBlastulaTableEntry();
+            IoUtils.closeQuietly(pipeFDs[1]);
+            return null;
+        }
+    }
+
+    private static native int nativeForkBlastula(int readPipeFD,
+                                                 int writePipeFD,
+                                                 int[] sessionSocketRawFDs);
+
+    /**
+     * This function is used by blastulas to wait for specialization requests from the system
+     * server.
+     *
+     * @param writePipe  The write end of the reporting pipe used to communicate with the poll loop
+     *                   of the ZygoteServer.
+     * @return A runnable oject representing the new application.
+     */
+    static Runnable blastulaMain(FileDescriptor writePipe) {
+        final int pid = Process.myPid();
+
+        LocalSocket sessionSocket = null;
+        DataOutputStream blastulaOutputStream = null;
+        Credentials peerCredentials = null;
+        String[] argStrings = null;
+
+        while (true) {
+            try {
+                sessionSocket = sBlastulaPoolSocket.accept();
+
+                BufferedReader blastulaReader =
+                        new BufferedReader(new InputStreamReader(sessionSocket.getInputStream()));
+                blastulaOutputStream =
+                        new DataOutputStream(sessionSocket.getOutputStream());
+
+                peerCredentials = sessionSocket.getPeerCredentials();
+
+                argStrings = readArgumentList(blastulaReader);
+
+                if (argStrings != null) {
+                    break;
+                } else {
+                    Log.e("Blastula", "Truncated command received.");
+                    IoUtils.closeQuietly(sessionSocket);
+                }
+            } catch (IOException ioEx) {
+                Log.e("Blastula", "Failed to read command: " + ioEx.getMessage());
+                IoUtils.closeQuietly(sessionSocket);
+            }
+        }
+
+        ZygoteArguments args = new ZygoteArguments(argStrings);
+
+        // TODO (chriswailes): Should this only be run for debug builds?
+        validateBlastulaCommand(args);
+
+        applyUidSecurityPolicy(args, peerCredentials);
+        applyDebuggerSystemProperty(args);
+
+        int[][] rlimits = null;
+
+        if (args.mRLimits != null) {
+            rlimits = args.mRLimits.toArray(INT_ARRAY_2D);
+        }
+
+        // This must happen before the SELinux policy for this process is
+        // changed when specializing.
+        try {
+            // Used by ZygoteProcess.zygoteSendArgsAndGetResult to fill in a
+            // Process.ProcessStartResult object.
+            blastulaOutputStream.writeInt(pid);
+        } catch (IOException ioEx) {
+            Log.e("Blastula", "Failed to write response to session socket: " + ioEx.getMessage());
+            System.exit(-1);
+        } finally {
+            IoUtils.closeQuietly(sessionSocket);
+            IoUtils.closeQuietly(sBlastulaPoolSocket);
+        }
+
+        try {
+            ByteArrayOutputStream buffer =
+                    new ByteArrayOutputStream(Zygote.BLASTULA_MANAGEMENT_MESSAGE_BYTES);
+            DataOutputStream outputStream = new DataOutputStream(buffer);
+
+            // This is written as a long so that the blastula reporting pipe and blastula pool
+            // event FD handlers in ZygoteServer.runSelectLoop can be unified.  These two cases
+            // should both send/receive 8 bytes.
+            outputStream.writeLong(pid);
+            outputStream.flush();
+
+            Os.write(writePipe, buffer.toByteArray(), 0, buffer.size());
+        } catch (Exception ex) {
+            Log.e("Blastula",
+                    String.format("Failed to write PID (%d) to pipe (%d): %s",
+                            pid, writePipe.getInt$(), ex.getMessage()));
+            System.exit(-1);
+        } finally {
+            IoUtils.closeQuietly(writePipe);
+        }
+
+        specializeBlastula(args.mUid, args.mGid, args.mGids,
+                           args.mRuntimeFlags, rlimits, args.mMountExternal,
+                           args.mSeInfo, args.mNiceName, args.mStartChildZygote,
+                           args.mInstructionSet, args.mAppDataDir, args.mPackageName,
+                           args.mPackagesForUid, args.mVisibleVolIds);
+
+        if (args.mNiceName != null) {
+            Process.setArgV0(args.mNiceName);
+        }
+
+        // End of the postFork event.
+        Trace.traceEnd(Trace.TRACE_TAG_ACTIVITY_MANAGER);
+
+        return ZygoteInit.zygoteInit(args.mTargetSdkVersion,
+                                     args.mRemainingArgs,
+                                     null /* classLoader */);
+    }
+
+    private static final String BLASTULA_ERROR_PREFIX = "Invalid command to blastula: ";
+
+    /**
+     * Checks a set of zygote arguments to see if they can be handled by a blastula.  Throws an
+     * exception if an invalid arugment is encountered.
+     * @param args  The arguments to test
+     */
+    static void validateBlastulaCommand(ZygoteArguments args) {
+        if (args.mAbiListQuery) {
+            throw new IllegalArgumentException(BLASTULA_ERROR_PREFIX + "--query-abi-list");
+        } else if (args.mPidQuery) {
+            throw new IllegalArgumentException(BLASTULA_ERROR_PREFIX + "--get-pid");
+        } else if (args.mPreloadDefault) {
+            throw new IllegalArgumentException(BLASTULA_ERROR_PREFIX + "--preload-default");
+        } else if (args.mPreloadPackage != null) {
+            throw new IllegalArgumentException(BLASTULA_ERROR_PREFIX + "--preload-package");
+        } else if (args.mPreloadApp != null) {
+            throw new IllegalArgumentException(BLASTULA_ERROR_PREFIX + "--preload-app");
+        } else if (args.mStartChildZygote) {
+            throw new IllegalArgumentException(BLASTULA_ERROR_PREFIX + "--start-child-zygote");
+        } else if (args.mApiBlacklistExemptions != null) {
+            throw new IllegalArgumentException(
+                BLASTULA_ERROR_PREFIX + "--set-api-blacklist-exemptions");
+        } else if (args.mHiddenApiAccessLogSampleRate != -1) {
+            throw new IllegalArgumentException(
+                BLASTULA_ERROR_PREFIX + "--hidden-api-log-sampling-rate=");
+        } else if (args.mInvokeWith != null) {
+            throw new IllegalArgumentException(BLASTULA_ERROR_PREFIX + "--invoke-with");
+        } else if (args.mPermittedCapabilities != 0 || args.mEffectiveCapabilities != 0) {
+            throw new ZygoteSecurityException("Client may not specify capabilities: "
+                + "permitted=0x" + Long.toHexString(args.mPermittedCapabilities)
+                + ", effective=0x" + Long.toHexString(args.mEffectiveCapabilities));
+        }
+    }
+
+    /**
+     * @return  Raw file descriptors for the read-end of blastula reporting pipes.
+     */
+    protected static int[] getBlastulaPipeFDs() {
+        return nativeGetBlastulaPipeFDs();
+    }
+
+    private static native int[] nativeGetBlastulaPipeFDs();
+
+    /**
+     * Remove the blastula table entry for the provided process ID.
+     *
+     * @param blastulaPID  Process ID of the entry to remove
+     * @return True if the entry was removed; false if it doesn't exist
+     */
+    protected static boolean removeBlastulaTableEntry(int blastulaPID) {
+        return nativeRemoveBlastulaTableEntry(blastulaPID);
+    }
+
+    private static native boolean nativeRemoveBlastulaTableEntry(int blastulaPID);
+
+    /**
+     * uid 1000 (Process.SYSTEM_UID) may specify any uid &gt; 1000 in normal
+     * operation. It may also specify any gid and setgroups() list it chooses.
+     * In factory test mode, it may specify any UID.
+     *
+     * @param args non-null; zygote spawner arguments
+     * @param peer non-null; peer credentials
+     * @throws ZygoteSecurityException
+     */
+    protected static void applyUidSecurityPolicy(ZygoteArguments args, Credentials peer)
+            throws ZygoteSecurityException {
+
+        if (peer.getUid() == Process.SYSTEM_UID) {
+            /* In normal operation, SYSTEM_UID can only specify a restricted
+             * set of UIDs. In factory test mode, SYSTEM_UID may specify any uid.
+             */
+            boolean uidRestricted = FactoryTest.getMode() == FactoryTest.FACTORY_TEST_OFF;
+
+            if (uidRestricted && args.mUidSpecified && (args.mUid < Process.SYSTEM_UID)) {
+                throw new ZygoteSecurityException(
+                        "System UID may not launch process with UID < "
+                        + Process.SYSTEM_UID);
+            }
+        }
+
+        // If not otherwise specified, uid and gid are inherited from peer
+        if (!args.mUidSpecified) {
+            args.mUid = peer.getUid();
+            args.mUidSpecified = true;
+        }
+        if (!args.mGidSpecified) {
+            args.mGid = peer.getGid();
+            args.mGidSpecified = true;
+        }
+    }
+
+    /**
+     * Applies debugger system properties to the zygote arguments.
+     *
+     * If "ro.debuggable" is "1", all apps are debuggable. Otherwise,
+     * the debugger state is specified via the "--enable-jdwp" flag
+     * in the spawn request.
+     *
+     * @param args non-null; zygote spawner args
+     */
+    protected static void applyDebuggerSystemProperty(ZygoteArguments args) {
+        if (RoSystemProperties.DEBUGGABLE) {
+            args.mRuntimeFlags |= Zygote.DEBUG_ENABLE_JDWP;
+        }
+    }
+
+    /**
+     * Applies zygote security policy.
+     * Based on the credentials of the process issuing a zygote command:
+     * <ol>
+     * <li> uid 0 (root) may specify --invoke-with to launch Zygote with a
+     * wrapper command.
+     * <li> Any other uid may not specify any invoke-with argument.
+     * </ul>
+     *
+     * @param args non-null; zygote spawner arguments
+     * @param peer non-null; peer credentials
+     * @throws ZygoteSecurityException
+     */
+    protected static void applyInvokeWithSecurityPolicy(ZygoteArguments args, Credentials peer)
+            throws ZygoteSecurityException {
+        int peerUid = peer.getUid();
+
+        if (args.mInvokeWith != null && peerUid != 0
+                && (args.mRuntimeFlags & Zygote.DEBUG_ENABLE_JDWP) == 0) {
+            throw new ZygoteSecurityException("Peer is permitted to specify an"
+                + "explicit invoke-with wrapper command only for debuggable"
+                + "applications.");
+        }
+    }
+
+    /**
+     * Applies invoke-with system properties to the zygote arguments.
+     *
+     * @param args non-null; zygote args
+     */
+    protected static void applyInvokeWithSystemProperty(ZygoteArguments args) {
+        if (args.mInvokeWith == null && args.mNiceName != null) {
+            String property = "wrap." + args.mNiceName;
+            args.mInvokeWith = SystemProperties.get(property);
+            if (args.mInvokeWith != null && args.mInvokeWith.length() == 0) {
+                args.mInvokeWith = null;
+            }
+        }
+    }
+
+    /**
+     * Reads an argument list from the provided socket
+     * @return Argument list or null if EOF is reached
+     * @throws IOException passed straight through
+     */
+    static String[] readArgumentList(BufferedReader socketReader) throws IOException {
+
+        /**
+         * See android.os.Process.zygoteSendArgsAndGetPid()
+         * 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.
+         */
+
+        int argc;
+
+        try {
+            String argc_string = socketReader.readLine();
+
+            if (argc_string == null) {
+                // EOF reached.
+                return null;
+            }
+            argc = Integer.parseInt(argc_string);
+
+        } catch (NumberFormatException ex) {
+            Log.e("Zygote", "Invalid Zygote wire format: non-int at argc");
+            throw new IOException("Invalid wire format");
+        }
+
+        // See bug 1092107: large argc can be used for a DOS attack
+        if (argc > MAX_ZYGOTE_ARGC) {
+            throw new IOException("Max arg count exceeded");
+        }
+
+        String[] args = new String[argc];
+        for (int arg_index = 0; arg_index < argc; arg_index++) {
+            args[arg_index] = socketReader.readLine();
+            if (args[arg_index] == null) {
+                // We got an unexpected EOF.
+                throw new IOException("Truncated request");
+            }
+        }
+
+        return args;
+    }
+
+    /**
+     * Creates a managed object representing the Blastula pool 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
+     */
+    static void createBlastulaSocket(String socketName) {
+        if (BLASTULA_POOL_ENABLED && sBlastulaPoolSocket == null) {
+            sBlastulaPoolSocket = createManagedSocketFromInitSocket(socketName);
+        }
+    }
+
+    /**
+     * Creates a managed LocalServerSocket object using a file descriptor
+     * created by an init.rc script.  The init scripts that specify the
+     * sockets name can be found in system/core/rootdir.  The socket is bound
+     * to the file system in the /dev/sockets/ directory, and the file
+     * descriptor is shared via the ANDROID_SOCKET_<socketName> environment
+     * variable.
+     */
+    static LocalServerSocket createManagedSocketFromInitSocket(String socketName) {
+        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("Socket unset or invalid: " + fullSocketName, ex);
+        }
+
+        try {
+            FileDescriptor fd = new FileDescriptor();
+            fd.setInt$(fileDesc);
+            return new LocalServerSocket(fd);
+        } catch (IOException ex) {
+            throw new RuntimeException(
+                "Error building socket from file descriptor: " + fileDesc, ex);
+        }
+    }
 
     private static void callPostForkSystemServerHooks() {
         // SystemServer specific post fork hooks run before child post fork hooks.
diff --git a/core/java/com/android/internal/os/ZygoteArguments.java b/core/java/com/android/internal/os/ZygoteArguments.java
new file mode 100644
index 0000000..24a08ca
--- /dev/null
+++ b/core/java/com/android/internal/os/ZygoteArguments.java
@@ -0,0 +1,430 @@
+/*
+ * Copyright (C) 2007 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.internal.os;
+
+import java.util.ArrayList;
+import java.util.Arrays;
+
+/**
+ * Handles argument parsing for args related to the zygote spawner.
+ *
+ * Current recognized args:
+ * <ul>
+ *   <li> --setuid=<i>uid of child process, defaults to 0</i>
+ *   <li> --setgid=<i>gid of child process, defaults to 0</i>
+ *   <li> --setgroups=<i>comma-separated list of supplimentary gid's</i>
+ *   <li> --capabilities=<i>a pair of comma-separated integer strings
+ * indicating Linux capabilities(2) set for child. The first string
+ * represents the <code>permitted</code> set, and the second the
+ * <code>effective</code> set. Precede each with 0 or
+ * 0x for octal or hexidecimal value. If unspecified, both default to 0.
+ * This parameter is only applied if the uid of the new process will
+ * be non-0. </i>
+ *   <li> --rlimit=r,c,m<i>tuple of values for setrlimit() call.
+ *    <code>r</code> is the resource, <code>c</code> and <code>m</code>
+ *    are the settings for current and max value.</i>
+ *   <li> --instruction-set=<i>instruction-set-string</i> which instruction set to use/emulate.
+ *   <li> --nice-name=<i>nice name to appear in ps</i>
+ *   <li> --package-name=<i>package name this process belongs to</i>
+ *   <li> --runtime-args indicates that the remaining arg list should
+ * be handed off to com.android.internal.os.RuntimeInit, rather than
+ * processed directly.
+ * Android runtime startup (eg, Binder initialization) is also eschewed.
+ *   <li> [--] &lt;args for RuntimeInit &gt;
+ * </ul>
+ */
+class ZygoteArguments {
+
+    /**
+     * from --setuid
+     */
+    int mUid = 0;
+    boolean mUidSpecified;
+
+    /**
+     * from --setgid
+     */
+    int mGid = 0;
+    boolean mGidSpecified;
+
+    /**
+     * from --setgroups
+     */
+    int[] mGids;
+
+    /**
+     * From --runtime-flags.
+     */
+    int mRuntimeFlags;
+
+    /**
+     * From --mount-external
+     */
+    int mMountExternal = Zygote.MOUNT_EXTERNAL_NONE;
+
+    /**
+     * from --target-sdk-version.
+     */
+    int mTargetSdkVersion;
+    boolean mTargetSdkVersionSpecified;
+
+    /**
+     * from --nice-name
+     */
+    String mNiceName;
+
+    /**
+     * from --capabilities
+     */
+    boolean mCapabilitiesSpecified;
+    long mPermittedCapabilities;
+    long mEffectiveCapabilities;
+
+    /**
+     * from --seinfo
+     */
+    boolean mSeInfoSpecified;
+    String mSeInfo;
+
+    /**
+     * from all --rlimit=r,c,m
+     */
+    ArrayList<int[]> mRLimits;
+
+    /**
+     * from --invoke-with
+     */
+    String mInvokeWith;
+
+    /** from --package-name */
+    String mPackageName;
+
+    /** from --packages-for-uid */
+    String[] mPackagesForUid;
+
+    /** from --visible-vols */
+    String[] mVisibleVolIds;
+
+    /**
+     * Any args after and including the first non-option arg (or after a '--')
+     */
+    String[] mRemainingArgs;
+
+    /**
+     * Whether the current arguments constitute an ABI list query.
+     */
+    boolean mAbiListQuery;
+
+    /**
+     * The instruction set to use, or null when not important.
+     */
+    String mInstructionSet;
+
+    /**
+     * The app data directory. May be null, e.g., for the system server. Note that this might not be
+     * reliable in the case of process-sharing apps.
+     */
+    String mAppDataDir;
+
+    /**
+     * The APK path of the package to preload, when using --preload-package.
+     */
+    String mPreloadPackage;
+
+    /**
+     * A Base64 string representing a serialize ApplicationInfo Parcel,
+     when using --preload-app.
+     */
+    String mPreloadApp;
+
+    /**
+     * The native library path of the package to preload, when using --preload-package.
+     */
+    String mPreloadPackageLibs;
+
+    /**
+     * The filename of the native library to preload, when using --preload-package.
+     */
+    String mPreloadPackageLibFileName;
+
+    /**
+     * The cache key under which to enter the preloaded package into the classloader cache, when
+     * using --preload-package.
+     */
+    String mPreloadPackageCacheKey;
+
+    /**
+     * Whether this is a request to start preloading the default resources and classes. This
+     * argument only makes sense when the zygote is in lazy preload mode (i.e, when it's started
+     * with --enable-lazy-preload).
+     */
+    boolean mPreloadDefault;
+
+    /**
+     * Whether this is a request to start a zygote process as a child of this zygote. Set with
+     * --start-child-zygote. The remaining arguments must include the CHILD_ZYGOTE_SOCKET_NAME_ARG
+     * flag to indicate the abstract socket name that should be used for communication.
+     */
+    boolean mStartChildZygote;
+
+    /**
+     * Whether the current arguments constitute a request for the zygote's PID.
+     */
+    boolean mPidQuery;
+
+    /**
+     * Exemptions from API blacklisting. These are sent to the pre-forked zygote at boot time, or
+     * when they change, via --set-api-blacklist-exemptions.
+     */
+    String[] mApiBlacklistExemptions;
+
+    /**
+     * Sampling rate for logging hidden API accesses to the event log. This is sent to the
+     * pre-forked zygote at boot time, or when it changes, via --hidden-api-log-sampling-rate.
+     */
+    int mHiddenApiAccessLogSampleRate = -1;
+
+    /**
+     * Constructs instance and parses args
+     *
+     * @param args zygote command-line args
+     */
+    ZygoteArguments(String[] args) throws IllegalArgumentException {
+        parseArgs(args);
+    }
+
+    /**
+     * Parses the commandline arguments intended for the Zygote spawner (such as "--setuid=" and
+     * "--setgid=") and creates an array containing the remaining args.
+     *
+     * Per security review bug #1112214, duplicate args are disallowed in critical cases to make
+     * injection harder.
+     */
+    private void parseArgs(String[] args)
+            throws IllegalArgumentException {
+        int curArg = 0;
+
+        boolean seenRuntimeArgs = false;
+
+        boolean expectRuntimeArgs = true;
+        for ( /* curArg */ ; curArg < args.length; curArg++) {
+            String arg = args[curArg];
+
+            if (arg.equals("--")) {
+                curArg++;
+                break;
+            } else if (arg.startsWith("--setuid=")) {
+                if (mUidSpecified) {
+                    throw new IllegalArgumentException(
+                        "Duplicate arg specified");
+                }
+                mUidSpecified = true;
+                mUid = Integer.parseInt(
+                    arg.substring(arg.indexOf('=') + 1));
+            } else if (arg.startsWith("--setgid=")) {
+                if (mGidSpecified) {
+                    throw new IllegalArgumentException(
+                        "Duplicate arg specified");
+                }
+                mGidSpecified = true;
+                mGid = Integer.parseInt(
+                    arg.substring(arg.indexOf('=') + 1));
+            } else if (arg.startsWith("--target-sdk-version=")) {
+                if (mTargetSdkVersionSpecified) {
+                    throw new IllegalArgumentException(
+                        "Duplicate target-sdk-version specified");
+                }
+                mTargetSdkVersionSpecified = true;
+                mTargetSdkVersion = Integer.parseInt(
+                    arg.substring(arg.indexOf('=') + 1));
+            } else if (arg.equals("--runtime-args")) {
+                seenRuntimeArgs = true;
+            } else if (arg.startsWith("--runtime-flags=")) {
+                mRuntimeFlags = Integer.parseInt(
+                    arg.substring(arg.indexOf('=') + 1));
+            } else if (arg.startsWith("--seinfo=")) {
+                if (mSeInfoSpecified) {
+                    throw new IllegalArgumentException(
+                        "Duplicate arg specified");
+                }
+                mSeInfoSpecified = true;
+                mSeInfo = arg.substring(arg.indexOf('=') + 1);
+            } else if (arg.startsWith("--capabilities=")) {
+                if (mCapabilitiesSpecified) {
+                    throw new IllegalArgumentException(
+                        "Duplicate arg specified");
+                }
+                mCapabilitiesSpecified = true;
+                String capString = arg.substring(arg.indexOf('=') + 1);
+
+                String[] capStrings = capString.split(",", 2);
+
+                if (capStrings.length == 1) {
+                    mEffectiveCapabilities = Long.decode(capStrings[0]);
+                    mPermittedCapabilities = mEffectiveCapabilities;
+                } else {
+                    mPermittedCapabilities = Long.decode(capStrings[0]);
+                    mEffectiveCapabilities = Long.decode(capStrings[1]);
+                }
+            } else if (arg.startsWith("--rlimit=")) {
+                // Duplicate --rlimit arguments are specifically allowed.
+                String[] limitStrings = arg.substring(arg.indexOf('=') + 1).split(",");
+
+                if (limitStrings.length != 3) {
+                    throw new IllegalArgumentException(
+                        "--rlimit= should have 3 comma-delimited ints");
+                }
+                int[] rlimitTuple = new int[limitStrings.length];
+
+                for (int i = 0; i < limitStrings.length; i++) {
+                    rlimitTuple[i] = Integer.parseInt(limitStrings[i]);
+                }
+
+                if (mRLimits == null) {
+                    mRLimits = new ArrayList();
+                }
+
+                mRLimits.add(rlimitTuple);
+            } else if (arg.startsWith("--setgroups=")) {
+                if (mGids != null) {
+                    throw new IllegalArgumentException(
+                        "Duplicate arg specified");
+                }
+
+                String[] params = arg.substring(arg.indexOf('=') + 1).split(",");
+
+                mGids = new int[params.length];
+
+                for (int i = params.length - 1; i >= 0; i--) {
+                    mGids[i] = Integer.parseInt(params[i]);
+                }
+            } else if (arg.equals("--invoke-with")) {
+                if (mInvokeWith != null) {
+                    throw new IllegalArgumentException(
+                        "Duplicate arg specified");
+                }
+                try {
+                    mInvokeWith = args[++curArg];
+                } catch (IndexOutOfBoundsException ex) {
+                    throw new IllegalArgumentException(
+                        "--invoke-with requires argument");
+                }
+            } else if (arg.startsWith("--nice-name=")) {
+                if (mNiceName != null) {
+                    throw new IllegalArgumentException(
+                        "Duplicate arg specified");
+                }
+                mNiceName = arg.substring(arg.indexOf('=') + 1);
+            } else if (arg.equals("--mount-external-default")) {
+                mMountExternal = Zygote.MOUNT_EXTERNAL_DEFAULT;
+            } else if (arg.equals("--mount-external-read")) {
+                mMountExternal = Zygote.MOUNT_EXTERNAL_READ;
+            } else if (arg.equals("--mount-external-write")) {
+                mMountExternal = Zygote.MOUNT_EXTERNAL_WRITE;
+            } else if (arg.equals("--mount-external-full")) {
+                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")) {
+                mPidQuery = true;
+            } else if (arg.startsWith("--instruction-set=")) {
+                mInstructionSet = arg.substring(arg.indexOf('=') + 1);
+            } else if (arg.startsWith("--app-data-dir=")) {
+                mAppDataDir = arg.substring(arg.indexOf('=') + 1);
+            } else if (arg.equals("--preload-app")) {
+                mPreloadApp = args[++curArg];
+            } else if (arg.equals("--preload-package")) {
+                mPreloadPackage = args[++curArg];
+                mPreloadPackageLibs = args[++curArg];
+                mPreloadPackageLibFileName = args[++curArg];
+                mPreloadPackageCacheKey = args[++curArg];
+            } else if (arg.equals("--preload-default")) {
+                mPreloadDefault = true;
+                expectRuntimeArgs = false;
+            } else if (arg.equals("--start-child-zygote")) {
+                mStartChildZygote = true;
+            } else if (arg.equals("--set-api-blacklist-exemptions")) {
+                // consume all remaining args; this is a stand-alone command, never included
+                // with the regular fork command.
+                mApiBlacklistExemptions = Arrays.copyOfRange(args, curArg + 1, args.length);
+                curArg = args.length;
+                expectRuntimeArgs = false;
+            } else if (arg.startsWith("--hidden-api-log-sampling-rate=")) {
+                String rateStr = arg.substring(arg.indexOf('=') + 1);
+                try {
+                    mHiddenApiAccessLogSampleRate = Integer.parseInt(rateStr);
+                } catch (NumberFormatException nfe) {
+                    throw new IllegalArgumentException(
+                        "Invalid log sampling rate: " + rateStr, nfe);
+                }
+                expectRuntimeArgs = false;
+            } else if (arg.startsWith("--package-name=")) {
+                if (mPackageName != null) {
+                    throw new IllegalArgumentException("Duplicate arg specified");
+                }
+                mPackageName = arg.substring(arg.indexOf('=') + 1);
+            } else if (arg.startsWith("--packages-for-uid=")) {
+                mPackagesForUid = arg.substring(arg.indexOf('=') + 1).split(",");
+            } else if (arg.startsWith("--visible-vols=")) {
+                mVisibleVolIds = arg.substring(arg.indexOf('=') + 1).split(",");
+            } else {
+                break;
+            }
+        }
+
+        if (mAbiListQuery || mPidQuery) {
+            if (args.length - curArg > 0) {
+                throw new IllegalArgumentException("Unexpected arguments after --query-abi-list.");
+            }
+        } else if (mPreloadPackage != null) {
+            if (args.length - curArg > 0) {
+                throw new IllegalArgumentException(
+                    "Unexpected arguments after --preload-package.");
+            }
+        } else if (mPreloadApp != null) {
+            if (args.length - curArg > 0) {
+                throw new IllegalArgumentException(
+                    "Unexpected arguments after --preload-app.");
+            }
+        } else if (expectRuntimeArgs) {
+            if (!seenRuntimeArgs) {
+                throw new IllegalArgumentException("Unexpected argument : " + args[curArg]);
+            }
+
+            mRemainingArgs = new String[args.length - curArg];
+            System.arraycopy(args, curArg, mRemainingArgs, 0, mRemainingArgs.length);
+        }
+
+        if (mStartChildZygote) {
+            boolean seenChildSocketArg = false;
+            for (String arg : mRemainingArgs) {
+                if (arg.startsWith(Zygote.CHILD_ZYGOTE_SOCKET_NAME_ARG)) {
+                    seenChildSocketArg = true;
+                    break;
+                }
+            }
+            if (!seenChildSocketArg) {
+                throw new IllegalArgumentException("--start-child-zygote specified "
+                        + "without " + Zygote.CHILD_ZYGOTE_SOCKET_NAME_ARG);
+            }
+        }
+    }
+}
diff --git a/core/java/com/android/internal/os/ZygoteConnection.java b/core/java/com/android/internal/os/ZygoteConnection.java
index ced798c..ffbe8eb 100644
--- a/core/java/com/android/internal/os/ZygoteConnection.java
+++ b/core/java/com/android/internal/os/ZygoteConnection.java
@@ -24,16 +24,13 @@
 import static android.system.OsConstants.STDOUT_FILENO;
 
 import static com.android.internal.os.ZygoteConnectionConstants.CONNECTION_TIMEOUT_MILLIS;
-import static com.android.internal.os.ZygoteConnectionConstants.MAX_ZYGOTE_ARGC;
 import static com.android.internal.os.ZygoteConnectionConstants.WRAPPED_PID_TIMEOUT_MILLIS;
 
 import android.content.pm.ApplicationInfo;
 import android.net.Credentials;
 import android.net.LocalSocket;
-import android.os.FactoryTest;
 import android.os.Parcel;
 import android.os.Process;
-import android.os.SystemProperties;
 import android.os.Trace;
 import android.system.ErrnoException;
 import android.system.Os;
@@ -52,8 +49,6 @@
 import java.io.IOException;
 import java.io.InputStreamReader;
 import java.nio.charset.StandardCharsets;
-import java.util.ArrayList;
-import java.util.Arrays;
 import java.util.Base64;
 
 /**
@@ -62,9 +57,6 @@
 class ZygoteConnection {
     private static final String TAG = "Zygote";
 
-    /** a prototype instance for a future List.toArray() */
-    private static final int[][] intArray2d = new int[0][0];
-
     /**
      * The command socket.
      *
@@ -113,7 +105,7 @@
      *
      * @return null-ok; file descriptor
      */
-    FileDescriptor getFileDesciptor() {
+    FileDescriptor getFileDescriptor() {
         return mSocket.getFileDescriptor();
     }
 
@@ -127,11 +119,13 @@
      */
     Runnable processOneCommand(ZygoteServer zygoteServer) {
         String args[];
-        Arguments parsedArgs = null;
+        ZygoteArguments parsedArgs = null;
         FileDescriptor[] descriptors;
 
         try {
-            args = readArgumentList();
+            args = Zygote.readArgumentList(mSocketReader);
+
+            // TODO (chriswailes): Remove this and add an assert.
             descriptors = mSocket.getAncillaryFileDescriptors();
         } catch (IOException ex) {
             throw new IllegalStateException("IOException on command socket", ex);
@@ -148,26 +142,26 @@
         FileDescriptor childPipeFd = null;
         FileDescriptor serverPipeFd = null;
 
-        parsedArgs = new Arguments(args);
+        parsedArgs = new ZygoteArguments(args);
 
-        if (parsedArgs.abiListQuery) {
+        if (parsedArgs.mAbiListQuery) {
             handleAbiListQuery();
             return null;
         }
 
-        if (parsedArgs.pidQuery) {
+        if (parsedArgs.mPidQuery) {
             handlePidQuery();
             return null;
         }
 
-        if (parsedArgs.preloadDefault) {
+        if (parsedArgs.mPreloadDefault) {
             handlePreload();
             return null;
         }
 
-        if (parsedArgs.preloadPackage != null) {
-            handlePreloadPackage(parsedArgs.preloadPackage, parsedArgs.preloadPackageLibs,
-                    parsedArgs.preloadPackageLibFileName, parsedArgs.preloadPackageCacheKey);
+        if (parsedArgs.mPreloadPackage != null) {
+            handlePreloadPackage(parsedArgs.mPreloadPackage, parsedArgs.mPreloadPackageLibs,
+                    parsedArgs.mPreloadPackageLibFileName, parsedArgs.mPreloadPackageCacheKey);
             return null;
         }
 
@@ -186,37 +180,37 @@
             return null;
         }
 
-        if (parsedArgs.apiBlacklistExemptions != null) {
-            handleApiBlacklistExemptions(parsedArgs.apiBlacklistExemptions);
+        if (parsedArgs.mApiBlacklistExemptions != null) {
+            handleApiBlacklistExemptions(parsedArgs.mApiBlacklistExemptions);
             return null;
         }
 
-        if (parsedArgs.hiddenApiAccessLogSampleRate != -1) {
-            handleHiddenApiAccessLogSampleRate(parsedArgs.hiddenApiAccessLogSampleRate);
+        if (parsedArgs.mHiddenApiAccessLogSampleRate != -1) {
+            handleHiddenApiAccessLogSampleRate(parsedArgs.mHiddenApiAccessLogSampleRate);
             return null;
         }
 
-        if (parsedArgs.permittedCapabilities != 0 || parsedArgs.effectiveCapabilities != 0) {
-            throw new ZygoteSecurityException("Client may not specify capabilities: " +
-                    "permitted=0x" + Long.toHexString(parsedArgs.permittedCapabilities) +
-                    ", effective=0x" + Long.toHexString(parsedArgs.effectiveCapabilities));
+        if (parsedArgs.mPermittedCapabilities != 0 || parsedArgs.mEffectiveCapabilities != 0) {
+            throw new ZygoteSecurityException("Client may not specify capabilities: "
+                    + "permitted=0x" + Long.toHexString(parsedArgs.mPermittedCapabilities)
+                    + ", effective=0x" + Long.toHexString(parsedArgs.mEffectiveCapabilities));
         }
 
-        applyUidSecurityPolicy(parsedArgs, peer);
-        applyInvokeWithSecurityPolicy(parsedArgs, peer);
+        Zygote.applyUidSecurityPolicy(parsedArgs, peer);
+        Zygote.applyInvokeWithSecurityPolicy(parsedArgs, peer);
 
-        applyDebuggerSystemProperty(parsedArgs);
-        applyInvokeWithSystemProperty(parsedArgs);
+        Zygote.applyDebuggerSystemProperty(parsedArgs);
+        Zygote.applyInvokeWithSystemProperty(parsedArgs);
 
         int[][] rlimits = null;
 
-        if (parsedArgs.rlimits != null) {
-            rlimits = parsedArgs.rlimits.toArray(intArray2d);
+        if (parsedArgs.mRLimits != null) {
+            rlimits = parsedArgs.mRLimits.toArray(Zygote.INT_ARRAY_2D);
         }
 
         int[] fdsToIgnore = null;
 
-        if (parsedArgs.invokeWith != null) {
+        if (parsedArgs.mInvokeWith != null) {
             try {
                 FileDescriptor[] pipeFds = Os.pipe2(O_CLOEXEC);
                 childPipeFd = pipeFds[1];
@@ -248,7 +242,7 @@
             fdsToClose[0] = fd.getInt$();
         }
 
-        fd = zygoteServer.getServerSocketFileDescriptor();
+        fd = zygoteServer.getZygoteSocketFileDescriptor();
 
         if (fd != null) {
             fdsToClose[1] = fd.getInt$();
@@ -256,11 +250,11 @@
 
         fd = null;
 
-        pid = Zygote.forkAndSpecialize(parsedArgs.uid, parsedArgs.gid, parsedArgs.gids,
-                parsedArgs.runtimeFlags, rlimits, parsedArgs.mountExternal, parsedArgs.seInfo,
-                parsedArgs.niceName, fdsToClose, fdsToIgnore, parsedArgs.startChildZygote,
-                parsedArgs.instructionSet, parsedArgs.appDataDir, parsedArgs.packageName,
-                parsedArgs.packagesForUid, parsedArgs.visibleVolIds);
+        pid = Zygote.forkAndSpecialize(parsedArgs.mUid, parsedArgs.mGid, parsedArgs.mGids,
+                parsedArgs.mRuntimeFlags, rlimits, parsedArgs.mMountExternal, parsedArgs.mSeInfo,
+                parsedArgs.mNiceName, fdsToClose, fdsToIgnore, parsedArgs.mStartChildZygote,
+                parsedArgs.mInstructionSet, parsedArgs.mAppDataDir, parsedArgs.mPackageName,
+                parsedArgs.mPackagesForUid, parsedArgs.mVisibleVolIds);
 
         try {
             if (pid == 0) {
@@ -272,7 +266,7 @@
                 serverPipeFd = null;
 
                 return handleChildProc(parsedArgs, descriptors, childPipeFd,
-                        parsedArgs.startChildZygote);
+                        parsedArgs.mStartChildZygote);
             } else {
                 // In the parent. A pid < 0 indicates a failure and will be handled in
                 // handleParentProc.
@@ -387,541 +381,6 @@
     }
 
     /**
-     * Handles argument parsing for args related to the zygote spawner.
-     *
-     * Current recognized args:
-     * <ul>
-     *   <li> --setuid=<i>uid of child process, defaults to 0</i>
-     *   <li> --setgid=<i>gid of child process, defaults to 0</i>
-     *   <li> --setgroups=<i>comma-separated list of supplimentary gid's</i>
-     *   <li> --capabilities=<i>a pair of comma-separated integer strings
-     * indicating Linux capabilities(2) set for child. The first string
-     * represents the <code>permitted</code> set, and the second the
-     * <code>effective</code> set. Precede each with 0 or
-     * 0x for octal or hexidecimal value. If unspecified, both default to 0.
-     * This parameter is only applied if the uid of the new process will
-     * be non-0. </i>
-     *   <li> --rlimit=r,c,m<i>tuple of values for setrlimit() call.
-     *    <code>r</code> is the resource, <code>c</code> and <code>m</code>
-     *    are the settings for current and max value.</i>
-     *   <li> --instruction-set=<i>instruction-set-string</i> which instruction set to use/emulate.
-     *   <li> --nice-name=<i>nice name to appear in ps</i>
-     *   <li> --package-name=<i>package name this process belongs to</i>
-     *   <li> --runtime-args indicates that the remaining arg list should
-     * be handed off to com.android.internal.os.RuntimeInit, rather than
-     * processed directly.
-     * Android runtime startup (eg, Binder initialization) is also eschewed.
-     *   <li> [--] &lt;args for RuntimeInit &gt;
-     * </ul>
-     */
-    static class Arguments {
-        /** from --setuid */
-        int uid = 0;
-        boolean uidSpecified;
-
-        /** from --setgid */
-        int gid = 0;
-        boolean gidSpecified;
-
-        /** from --setgroups */
-        int[] gids;
-
-        /**
-         * From --runtime-flags.
-         */
-        int runtimeFlags;
-
-        /** From --mount-external */
-        int mountExternal = Zygote.MOUNT_EXTERNAL_NONE;
-
-        /** from --target-sdk-version. */
-        int targetSdkVersion;
-        boolean targetSdkVersionSpecified;
-
-        /** from --nice-name */
-        String niceName;
-
-        /** from --capabilities */
-        boolean capabilitiesSpecified;
-        long permittedCapabilities;
-        long effectiveCapabilities;
-
-        /** from --seinfo */
-        boolean seInfoSpecified;
-        String seInfo;
-
-        /** from all --rlimit=r,c,m */
-        ArrayList<int[]> rlimits;
-
-        /** from --invoke-with */
-        String invokeWith;
-
-        /** from --package-name */
-        String packageName;
-
-        /** from --packages-for-uid */
-        String[] packagesForUid;
-
-        /** from --visible-vols */
-        String[] visibleVolIds;
-
-        /**
-         * Any args after and including the first non-option arg
-         * (or after a '--')
-         */
-        String remainingArgs[];
-
-        /**
-         * Whether the current arguments constitute an ABI list query.
-         */
-        boolean abiListQuery;
-
-        /**
-         * The instruction set to use, or null when not important.
-         */
-        String instructionSet;
-
-        /**
-         * The app data directory. May be null, e.g., for the system server. Note that this might
-         * not be reliable in the case of process-sharing apps.
-         */
-        String appDataDir;
-
-        /**
-         * The APK path of the package to preload, when using --preload-package.
-         */
-        String preloadPackage;
-
-        /**
-         * A Base64 string representing a serialize ApplicationInfo Parcel,
-           when using --preload-app.
-          */
-        String mPreloadApp;
-
-        /**
-         * The native library path of the package to preload, when using --preload-package.
-         */
-        String preloadPackageLibs;
-
-        /**
-         * The filename of the native library to preload, when using --preload-package.
-         */
-        String preloadPackageLibFileName;
-
-        /**
-         * The cache key under which to enter the preloaded package into the classloader cache,
-         * when using --preload-package.
-         */
-        String preloadPackageCacheKey;
-
-        /**
-         * Whether this is a request to start preloading the default resources and classes.
-         * This argument only makes sense when the zygote is in lazy preload mode (i.e, when
-         * it's started with --enable-lazy-preload).
-         */
-        boolean preloadDefault;
-
-        /**
-         * Whether this is a request to start a zygote process as a child of this zygote.
-         * Set with --start-child-zygote. The remaining arguments must include the
-         * CHILD_ZYGOTE_SOCKET_NAME_ARG flag to indicate the abstract socket name that
-         * should be used for communication.
-         */
-        boolean startChildZygote;
-
-        /**
-         * Whether the current arguments constitute a request for the zygote's PID.
-         */
-        boolean pidQuery;
-
-        /**
-         * Exemptions from API blacklisting. These are sent to the pre-forked zygote at boot time,
-         * or when they change, via --set-api-blacklist-exemptions.
-         */
-        String[] apiBlacklistExemptions;
-
-        /**
-         * Sampling rate for logging hidden API accesses to the event log. This is sent to the
-         * pre-forked zygote at boot time, or when it changes, via --hidden-api-log-sampling-rate.
-         */
-        int hiddenApiAccessLogSampleRate = -1;
-
-        /**
-         * Constructs instance and parses args
-         * @param args zygote command-line args
-         * @throws IllegalArgumentException
-         */
-        Arguments(String args[]) throws IllegalArgumentException {
-            parseArgs(args);
-        }
-
-        /**
-         * Parses the commandline arguments intended for the Zygote spawner
-         * (such as "--setuid=" and "--setgid=") and creates an array
-         * containing the remaining args.
-         *
-         * Per security review bug #1112214, duplicate args are disallowed in
-         * critical cases to make injection harder.
-         */
-        private void parseArgs(String args[])
-                throws IllegalArgumentException {
-            int curArg = 0;
-
-            boolean seenRuntimeArgs = false;
-
-            boolean expectRuntimeArgs = true;
-            for ( /* curArg */ ; curArg < args.length; curArg++) {
-                String arg = args[curArg];
-
-                if (arg.equals("--")) {
-                    curArg++;
-                    break;
-                } else if (arg.startsWith("--setuid=")) {
-                    if (uidSpecified) {
-                        throw new IllegalArgumentException(
-                                "Duplicate arg specified");
-                    }
-                    uidSpecified = true;
-                    uid = Integer.parseInt(
-                            arg.substring(arg.indexOf('=') + 1));
-                } else if (arg.startsWith("--setgid=")) {
-                    if (gidSpecified) {
-                        throw new IllegalArgumentException(
-                                "Duplicate arg specified");
-                    }
-                    gidSpecified = true;
-                    gid = Integer.parseInt(
-                            arg.substring(arg.indexOf('=') + 1));
-                } else if (arg.startsWith("--target-sdk-version=")) {
-                    if (targetSdkVersionSpecified) {
-                        throw new IllegalArgumentException(
-                                "Duplicate target-sdk-version specified");
-                    }
-                    targetSdkVersionSpecified = true;
-                    targetSdkVersion = Integer.parseInt(
-                            arg.substring(arg.indexOf('=') + 1));
-                } else if (arg.equals("--runtime-args")) {
-                    seenRuntimeArgs = true;
-                } else if (arg.startsWith("--runtime-flags=")) {
-                    runtimeFlags = Integer.parseInt(
-                            arg.substring(arg.indexOf('=') + 1));
-                } else if (arg.startsWith("--seinfo=")) {
-                    if (seInfoSpecified) {
-                        throw new IllegalArgumentException(
-                                "Duplicate arg specified");
-                    }
-                    seInfoSpecified = true;
-                    seInfo = arg.substring(arg.indexOf('=') + 1);
-                } else if (arg.startsWith("--capabilities=")) {
-                    if (capabilitiesSpecified) {
-                        throw new IllegalArgumentException(
-                                "Duplicate arg specified");
-                    }
-                    capabilitiesSpecified = true;
-                    String capString = arg.substring(arg.indexOf('=')+1);
-
-                    String[] capStrings = capString.split(",", 2);
-
-                    if (capStrings.length == 1) {
-                        effectiveCapabilities = Long.decode(capStrings[0]);
-                        permittedCapabilities = effectiveCapabilities;
-                    } else {
-                        permittedCapabilities = Long.decode(capStrings[0]);
-                        effectiveCapabilities = Long.decode(capStrings[1]);
-                    }
-                } else if (arg.startsWith("--rlimit=")) {
-                    // Duplicate --rlimit arguments are specifically allowed.
-                    String[] limitStrings
-                            = arg.substring(arg.indexOf('=')+1).split(",");
-
-                    if (limitStrings.length != 3) {
-                        throw new IllegalArgumentException(
-                                "--rlimit= should have 3 comma-delimited ints");
-                    }
-                    int[] rlimitTuple = new int[limitStrings.length];
-
-                    for(int i=0; i < limitStrings.length; i++) {
-                        rlimitTuple[i] = Integer.parseInt(limitStrings[i]);
-                    }
-
-                    if (rlimits == null) {
-                        rlimits = new ArrayList();
-                    }
-
-                    rlimits.add(rlimitTuple);
-                } else if (arg.startsWith("--setgroups=")) {
-                    if (gids != null) {
-                        throw new IllegalArgumentException(
-                                "Duplicate arg specified");
-                    }
-
-                    String[] params
-                            = arg.substring(arg.indexOf('=') + 1).split(",");
-
-                    gids = new int[params.length];
-
-                    for (int i = params.length - 1; i >= 0 ; i--) {
-                        gids[i] = Integer.parseInt(params[i]);
-                    }
-                } else if (arg.equals("--invoke-with")) {
-                    if (invokeWith != null) {
-                        throw new IllegalArgumentException(
-                                "Duplicate arg specified");
-                    }
-                    try {
-                        invokeWith = args[++curArg];
-                    } catch (IndexOutOfBoundsException ex) {
-                        throw new IllegalArgumentException(
-                                "--invoke-with requires argument");
-                    }
-                } else if (arg.startsWith("--nice-name=")) {
-                    if (niceName != null) {
-                        throw new IllegalArgumentException(
-                                "Duplicate arg specified");
-                    }
-                    niceName = arg.substring(arg.indexOf('=') + 1);
-                } else if (arg.equals("--mount-external-default")) {
-                    mountExternal = Zygote.MOUNT_EXTERNAL_DEFAULT;
-                } else if (arg.equals("--mount-external-read")) {
-                    mountExternal = Zygote.MOUNT_EXTERNAL_READ;
-                } else if (arg.equals("--mount-external-write")) {
-                    mountExternal = Zygote.MOUNT_EXTERNAL_WRITE;
-                } else if (arg.equals("--mount-external-full")) {
-                    mountExternal = Zygote.MOUNT_EXTERNAL_FULL;
-                } else if (arg.equals("--mount-external-installer")) {
-                    mountExternal = Zygote.MOUNT_EXTERNAL_INSTALLER;
-                } else if (arg.equals("--mount-external-legacy")) {
-                    mountExternal = Zygote.MOUNT_EXTERNAL_LEGACY;
-                } else if (arg.equals("--query-abi-list")) {
-                    abiListQuery = true;
-                } else if (arg.equals("--get-pid")) {
-                    pidQuery = true;
-                } else if (arg.startsWith("--instruction-set=")) {
-                    instructionSet = arg.substring(arg.indexOf('=') + 1);
-                } else if (arg.startsWith("--app-data-dir=")) {
-                    appDataDir = arg.substring(arg.indexOf('=') + 1);
-                } else if (arg.equals("--preload-app")) {
-                    mPreloadApp = args[++curArg];
-                } else if (arg.equals("--preload-package")) {
-                    preloadPackage = args[++curArg];
-                    preloadPackageLibs = args[++curArg];
-                    preloadPackageLibFileName = args[++curArg];
-                    preloadPackageCacheKey = args[++curArg];
-                } else if (arg.equals("--preload-default")) {
-                    preloadDefault = true;
-                    expectRuntimeArgs = false;
-                } else if (arg.equals("--start-child-zygote")) {
-                    startChildZygote = true;
-                } else if (arg.equals("--set-api-blacklist-exemptions")) {
-                    // consume all remaining args; this is a stand-alone command, never included
-                    // with the regular fork command.
-                    apiBlacklistExemptions = Arrays.copyOfRange(args, curArg + 1, args.length);
-                    curArg = args.length;
-                    expectRuntimeArgs = false;
-                } else if (arg.startsWith("--hidden-api-log-sampling-rate=")) {
-                    String rateStr = arg.substring(arg.indexOf('=') + 1);
-                    try {
-                        hiddenApiAccessLogSampleRate = Integer.parseInt(rateStr);
-                    } catch (NumberFormatException nfe) {
-                        throw new IllegalArgumentException(
-                                "Invalid log sampling rate: " + rateStr, nfe);
-                    }
-                    expectRuntimeArgs = false;
-                } else if (arg.startsWith("--package-name=")) {
-                    if (packageName != null) {
-                        throw new IllegalArgumentException("Duplicate arg specified");
-                    }
-                    packageName = arg.substring(arg.indexOf('=') + 1);
-                } else if (arg.startsWith("--packages-for-uid=")) {
-                    packagesForUid = arg.substring(arg.indexOf('=') + 1).split(",");
-                } else if (arg.startsWith("--visible-vols=")) {
-                    visibleVolIds = arg.substring(arg.indexOf('=') + 1).split(",");
-                } else {
-                    break;
-                }
-            }
-
-            if (abiListQuery || pidQuery) {
-                if (args.length - curArg > 0) {
-                    throw new IllegalArgumentException("Unexpected arguments after --query-abi-list.");
-                }
-            } else if (preloadPackage != null) {
-                if (args.length - curArg > 0) {
-                    throw new IllegalArgumentException(
-                            "Unexpected arguments after --preload-package.");
-                }
-            } else if (mPreloadApp != null) {
-                if (args.length - curArg > 0) {
-                    throw new IllegalArgumentException(
-                            "Unexpected arguments after --preload-app.");
-                }
-            } else if (expectRuntimeArgs) {
-                if (!seenRuntimeArgs) {
-                    throw new IllegalArgumentException("Unexpected argument : " + args[curArg]);
-                }
-
-                remainingArgs = new String[args.length - curArg];
-                System.arraycopy(args, curArg, remainingArgs, 0, remainingArgs.length);
-            }
-
-            if (startChildZygote) {
-                boolean seenChildSocketArg = false;
-                for (String arg : remainingArgs) {
-                    if (arg.startsWith(Zygote.CHILD_ZYGOTE_SOCKET_NAME_ARG)) {
-                        seenChildSocketArg = true;
-                        break;
-                    }
-                }
-                if (!seenChildSocketArg) {
-                    throw new IllegalArgumentException("--start-child-zygote specified " +
-                            "without " + Zygote.CHILD_ZYGOTE_SOCKET_NAME_ARG);
-                }
-            }
-        }
-    }
-
-    /**
-     * Reads an argument list from the command socket/
-     * @return Argument list or null if EOF is reached
-     * @throws IOException passed straight through
-     */
-    private String[] readArgumentList()
-            throws IOException {
-
-        /**
-         * See android.os.Process.zygoteSendArgsAndGetPid()
-         * 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.
-         */
-
-        int argc;
-
-        try {
-            String s = mSocketReader.readLine();
-
-            if (s == null) {
-                // EOF reached.
-                return null;
-            }
-            argc = Integer.parseInt(s);
-        } catch (NumberFormatException ex) {
-            Log.e(TAG, "invalid Zygote wire format: non-int at argc");
-            throw new IOException("invalid wire format");
-        }
-
-        // See bug 1092107: large argc can be used for a DOS attack
-        if (argc > MAX_ZYGOTE_ARGC) {
-            throw new IOException("max arg count exceeded");
-        }
-
-        String[] result = new String[argc];
-        for (int i = 0; i < argc; i++) {
-            result[i] = mSocketReader.readLine();
-            if (result[i] == null) {
-                // We got an unexpected EOF.
-                throw new IOException("truncated request");
-            }
-        }
-
-        return result;
-    }
-
-    /**
-     * uid 1000 (Process.SYSTEM_UID) may specify any uid &gt; 1000 in normal
-     * operation. It may also specify any gid and setgroups() list it chooses.
-     * In factory test mode, it may specify any UID.
-     *
-     * @param args non-null; zygote spawner arguments
-     * @param peer non-null; peer credentials
-     * @throws ZygoteSecurityException
-     */
-    private static void applyUidSecurityPolicy(Arguments args, Credentials peer)
-            throws ZygoteSecurityException {
-
-        if (peer.getUid() == Process.SYSTEM_UID) {
-            /* In normal operation, SYSTEM_UID can only specify a restricted
-             * set of UIDs. In factory test mode, SYSTEM_UID may specify any uid.
-             */
-            boolean uidRestricted = FactoryTest.getMode() == FactoryTest.FACTORY_TEST_OFF;
-
-            if (uidRestricted && args.uidSpecified && (args.uid < Process.SYSTEM_UID)) {
-                throw new ZygoteSecurityException(
-                        "System UID may not launch process with UID < "
-                        + Process.SYSTEM_UID);
-            }
-        }
-
-        // If not otherwise specified, uid and gid are inherited from peer
-        if (!args.uidSpecified) {
-            args.uid = peer.getUid();
-            args.uidSpecified = true;
-        }
-        if (!args.gidSpecified) {
-            args.gid = peer.getGid();
-            args.gidSpecified = true;
-        }
-    }
-
-    /**
-     * Applies debugger system properties to the zygote arguments.
-     *
-     * If "ro.debuggable" is "1", all apps are debuggable. Otherwise,
-     * the debugger state is specified via the "--enable-jdwp" flag
-     * in the spawn request.
-     *
-     * @param args non-null; zygote spawner args
-     */
-    public static void applyDebuggerSystemProperty(Arguments args) {
-        if (RoSystemProperties.DEBUGGABLE) {
-            args.runtimeFlags |= Zygote.DEBUG_ENABLE_JDWP;
-        }
-    }
-
-    /**
-     * Applies zygote security policy.
-     * Based on the credentials of the process issuing a zygote command:
-     * <ol>
-     * <li> uid 0 (root) may specify --invoke-with to launch Zygote with a
-     * wrapper command.
-     * <li> Any other uid may not specify any invoke-with argument.
-     * </ul>
-     *
-     * @param args non-null; zygote spawner arguments
-     * @param peer non-null; peer credentials
-     * @throws ZygoteSecurityException
-     */
-    private static void applyInvokeWithSecurityPolicy(Arguments args, Credentials peer)
-            throws ZygoteSecurityException {
-        int peerUid = peer.getUid();
-
-        if (args.invokeWith != null && peerUid != 0 &&
-            (args.runtimeFlags & Zygote.DEBUG_ENABLE_JDWP) == 0) {
-            throw new ZygoteSecurityException("Peer is permitted to specify an"
-                    + "explicit invoke-with wrapper command only for debuggable"
-                    + "applications.");
-        }
-    }
-
-    /**
-     * Applies invoke-with system properties to the zygote arguments.
-     *
-     * @param args non-null; zygote args
-     */
-    public static void applyInvokeWithSystemProperty(Arguments args) {
-        if (args.invokeWith == null && args.niceName != null) {
-            String property = "wrap." + args.niceName;
-            args.invokeWith = SystemProperties.get(property);
-            if (args.invokeWith != null && args.invokeWith.length() == 0) {
-                args.invokeWith = null;
-            }
-        }
-    }
-
-    /**
      * Handles post-fork setup of child proc, closing sockets as appropriate,
      * reopen stdio as appropriate, and ultimately throwing MethodAndArgsCaller
      * if successful or returning if failed.
@@ -931,7 +390,7 @@
      * @param pipeFd null-ok; pipe for communication back to Zygote.
      * @param isZygote whether this new child process is itself a new Zygote.
      */
-    private Runnable handleChildProc(Arguments parsedArgs, FileDescriptor[] descriptors,
+    private Runnable handleChildProc(ZygoteArguments parsedArgs, FileDescriptor[] descriptors,
             FileDescriptor pipeFd, boolean isZygote) {
         /**
          * By the time we get here, the native code has closed the two actual Zygote
@@ -954,27 +413,27 @@
             }
         }
 
-        if (parsedArgs.niceName != null) {
-            Process.setArgV0(parsedArgs.niceName);
+        if (parsedArgs.mNiceName != null) {
+            Process.setArgV0(parsedArgs.mNiceName);
         }
 
         // End of the postFork event.
         Trace.traceEnd(Trace.TRACE_TAG_ACTIVITY_MANAGER);
-        if (parsedArgs.invokeWith != null) {
-            WrapperInit.execApplication(parsedArgs.invokeWith,
-                    parsedArgs.niceName, parsedArgs.targetSdkVersion,
+        if (parsedArgs.mInvokeWith != null) {
+            WrapperInit.execApplication(parsedArgs.mInvokeWith,
+                    parsedArgs.mNiceName, parsedArgs.mTargetSdkVersion,
                     VMRuntime.getCurrentInstructionSet(),
-                    pipeFd, parsedArgs.remainingArgs);
+                    pipeFd, parsedArgs.mRemainingArgs);
 
             // Should not get here.
             throw new IllegalStateException("WrapperInit.execApplication unexpectedly returned");
         } else {
             if (!isZygote) {
-                return ZygoteInit.zygoteInit(parsedArgs.targetSdkVersion, parsedArgs.remainingArgs,
-                        null /* classLoader */);
+                return ZygoteInit.zygoteInit(parsedArgs.mTargetSdkVersion,
+                        parsedArgs.mRemainingArgs, null /* classLoader */);
             } else {
-                return ZygoteInit.childZygoteInit(parsedArgs.targetSdkVersion,
-                        parsedArgs.remainingArgs, null /* classLoader */);
+                return ZygoteInit.childZygoteInit(parsedArgs.mTargetSdkVersion,
+                        parsedArgs.mRemainingArgs, null /* classLoader */);
             }
         }
     }
diff --git a/core/java/com/android/internal/os/ZygoteInit.java b/core/java/com/android/internal/os/ZygoteInit.java
index c2c6ae6..e3e55ed 100644
--- a/core/java/com/android/internal/os/ZygoteInit.java
+++ b/core/java/com/android/internal/os/ZygoteInit.java
@@ -21,7 +21,6 @@
 
 import android.content.res.Resources;
 import android.content.res.TypedArray;
-import android.opengl.EGL14;
 import android.os.Build;
 import android.os.Environment;
 import android.os.IInstalld;
@@ -71,16 +70,16 @@
 /**
  * Startup class for the zygote process.
  *
- * Pre-initializes some classes, and then waits for commands on a UNIX domain
- * socket. Based on these commands, forks off child processes that inherit
- * the initial state of the VM.
+ * Pre-initializes some classes, and then waits for commands on a UNIX domain socket. Based on these
+ * commands, forks off child processes that inherit the initial state of the VM.
  *
- * Please see {@link ZygoteConnection.Arguments} for documentation on the
- * client protocol.
+ * Please see {@link ZygoteArguments} for documentation on the client protocol.
  *
  * @hide
  */
 public class ZygoteInit {
+
+    // TODO (chriswailes): Change this so it is set with Zygote or ZygoteSecondary as appropriate
     private static final String TAG = "Zygote";
 
     private static final String PROPERTY_DISABLE_OPENGL_PRELOADING = "ro.zygote.disable_gl_preload";
@@ -89,11 +88,15 @@
     private static final int LOG_BOOT_PROGRESS_PRELOAD_START = 3020;
     private static final int LOG_BOOT_PROGRESS_PRELOAD_END = 3030;
 
-    /** when preloading, GC after allocating this many bytes */
+    /**
+     * when preloading, GC after allocating this many bytes
+     */
     private static final int PRELOAD_GC_THRESHOLD = 50000;
 
     private static final String ABI_LIST_ARG = "--abi-list=";
 
+    // TODO (chriswailes): Re-name this --zygote-socket-name= and then add a
+    // --blastula-socket-name parameter.
     private static final String SOCKET_NAME_ARG = "--socket-name=";
 
     /**
@@ -106,7 +109,9 @@
      */
     private static final String PRELOADED_CLASSES = "/system/etc/preloaded-classes";
 
-    /** Controls whether we should preload resources during zygote init. */
+    /**
+     * Controls whether we should preload resources during zygote init.
+     */
     public static final boolean PRELOAD_RESOURCES = true;
 
     private static final int UNPRIVILEGED_UID = 9999;
@@ -173,6 +178,7 @@
     }
 
     native private static void nativePreloadAppProcessHALs();
+
     native private static void nativePreloadOpenGL();
 
     private static void preloadOpenGL() {
@@ -191,8 +197,8 @@
     /**
      * Register AndroidKeyStoreProvider and warm up the providers that are already registered.
      *
-     * By doing it here we avoid that each app does it when requesting a service from the
-     * provider for the first time.
+     * By doing it here we avoid that each app does it when requesting a service from the provider
+     * for the first time.
      */
     private static void warmUpJcaProviders() {
         long startTime = SystemClock.uptimeMillis();
@@ -218,11 +224,10 @@
     }
 
     /**
-     * Performs Zygote process initialization. Loads and initializes
-     * commonly used classes.
+     * Performs Zygote process initialization. Loads and initializes commonly used classes.
      *
-     * Most classes only cause a few hundred bytes to be allocated, but
-     * a few will allocate a dozen Kbytes (in one case, 500+K).
+     * Most classes only cause a few hundred bytes to be allocated, but a few will allocate a dozen
+     * Kbytes (in one case, 500+K).
      */
     private static void preloadClasses() {
         final VMRuntime runtime = VMRuntime.getRuntime();
@@ -263,8 +268,8 @@
         runtime.setTargetHeapUtilization(0.8f);
 
         try {
-            BufferedReader br
-                = new BufferedReader(new InputStreamReader(is), 256);
+            BufferedReader br =
+                    new BufferedReader(new InputStreamReader(is), Zygote.SOCKET_BUFFER_SIZE);
 
             int count = 0;
             String line;
@@ -305,7 +310,7 @@
             }
 
             Log.i(TAG, "...preloaded " + count + " classes in "
-                    + (SystemClock.uptimeMillis()-startTime) + "ms.");
+                    + (SystemClock.uptimeMillis() - startTime) + "ms.");
         } catch (IOException e) {
             Log.e(TAG, "Error reading " + PRELOADED_CLASSES + ".", e);
         } finally {
@@ -331,11 +336,10 @@
     }
 
     /**
-     * Load in commonly used resources, so they can be shared across
-     * processes.
+     * Load in commonly used resources, so they can be shared across processes.
      *
-     * These tend to be a few Kbytes, but are frequently in the 20-40K
-     * range, and occasionally even larger.
+     * These tend to be a few Kbytes, but are frequently in the 20-40K range, and occasionally even
+     * larger.
      */
     private static void preloadResources() {
         final VMRuntime runtime = VMRuntime.getRuntime();
@@ -352,7 +356,7 @@
                 int N = preloadDrawables(ar);
                 ar.recycle();
                 Log.i(TAG, "...preloaded " + N + " resources in "
-                        + (SystemClock.uptimeMillis()-startTime) + "ms.");
+                        + (SystemClock.uptimeMillis() - startTime) + "ms.");
 
                 startTime = SystemClock.uptimeMillis();
                 ar = mResources.obtainTypedArray(
@@ -360,7 +364,7 @@
                 N = preloadColorStateLists(ar);
                 ar.recycle();
                 Log.i(TAG, "...preloaded " + N + " resources in "
-                        + (SystemClock.uptimeMillis()-startTime) + "ms.");
+                        + (SystemClock.uptimeMillis() - startTime) + "ms.");
 
                 if (mResources.getBoolean(
                         com.android.internal.R.bool.config_freeformWindowManagement)) {
@@ -381,7 +385,7 @@
 
     private static int preloadColorStateLists(TypedArray ar) {
         int N = ar.length();
-        for (int i=0; i<N; i++) {
+        for (int i = 0; i < N; i++) {
             int id = ar.getResourceId(i, 0);
             if (false) {
                 Log.v(TAG, "Preloading resource #" + Integer.toHexString(id));
@@ -390,8 +394,8 @@
                 if (mResources.getColorStateList(id, null) == null) {
                     throw new IllegalArgumentException(
                             "Unable to find preloaded color resource #0x"
-                            + Integer.toHexString(id)
-                            + " (" + ar.getString(i) + ")");
+                                    + Integer.toHexString(id)
+                                    + " (" + ar.getString(i) + ")");
                 }
             }
         }
@@ -401,7 +405,7 @@
 
     private static int preloadDrawables(TypedArray ar) {
         int N = ar.length();
-        for (int i=0; i<N; i++) {
+        for (int i = 0; i < N; i++) {
             int id = ar.getResourceId(i, 0);
             if (false) {
                 Log.v(TAG, "Preloading resource #" + Integer.toHexString(id));
@@ -410,8 +414,8 @@
                 if (mResources.getDrawable(id, null) == null) {
                     throw new IllegalArgumentException(
                             "Unable to find preloaded drawable resource #0x"
-                            + Integer.toHexString(id)
-                            + " (" + ar.getString(i) + ")");
+                                    + Integer.toHexString(id)
+                                    + " (" + ar.getString(i) + ")");
                 }
             }
         }
@@ -419,9 +423,8 @@
     }
 
     /**
-     * Runs several special GCs to try to clean up a few generations of
-     * softly- and final-reachable objects, along with any other garbage.
-     * This is only useful just before a fork().
+     * Runs several special GCs to try to clean up a few generations of softly- and final-reachable
+     * objects, along with any other garbage. This is only useful just before a fork().
      */
     private static void gcAndFinalize() {
         ZygoteHooks.gcAndFinalize();
@@ -430,12 +433,12 @@
     /**
      * Finish remaining work for the newly forked system server process.
      */
-    private static Runnable handleSystemServerProcess(ZygoteConnection.Arguments parsedArgs) {
+    private static Runnable handleSystemServerProcess(ZygoteArguments parsedArgs) {
         // set umask to 0077 so new files and directories will default to owner-only permissions.
         Os.umask(S_IRWXG | S_IRWXO);
 
-        if (parsedArgs.niceName != null) {
-            Process.setArgV0(parsedArgs.niceName);
+        if (parsedArgs.mNiceName != null) {
+            Process.setArgV0(parsedArgs.mNiceName);
         }
 
         final String systemServerClasspath = Os.getenv("SYSTEMSERVERCLASSPATH");
@@ -454,8 +457,8 @@
             }
         }
 
-        if (parsedArgs.invokeWith != null) {
-            String[] args = parsedArgs.remainingArgs;
+        if (parsedArgs.mInvokeWith != null) {
+            String[] args = parsedArgs.mRemainingArgs;
             // If we have a non-null system server class path, we'll have to duplicate the
             // existing arguments and append the classpath to it. ART will handle the classpath
             // correctly when we exec a new process.
@@ -467,15 +470,15 @@
                 args = amendedArgs;
             }
 
-            WrapperInit.execApplication(parsedArgs.invokeWith,
-                    parsedArgs.niceName, parsedArgs.targetSdkVersion,
+            WrapperInit.execApplication(parsedArgs.mInvokeWith,
+                    parsedArgs.mNiceName, parsedArgs.mTargetSdkVersion,
                     VMRuntime.getCurrentInstructionSet(), null, args);
 
             throw new IllegalStateException("Unexpected return from WrapperInit.execApplication");
         } else {
             ClassLoader cl = null;
             if (systemServerClasspath != null) {
-                cl = createPathClassLoader(systemServerClasspath, parsedArgs.targetSdkVersion);
+                cl = createPathClassLoader(systemServerClasspath, parsedArgs.mTargetSdkVersion);
 
                 Thread.currentThread().setContextClassLoader(cl);
             }
@@ -483,16 +486,17 @@
             /*
              * Pass the remaining arguments to SystemServer.
              */
-            return ZygoteInit.zygoteInit(parsedArgs.targetSdkVersion, parsedArgs.remainingArgs, cl);
+            return ZygoteInit.zygoteInit(parsedArgs.mTargetSdkVersion,
+                    parsedArgs.mRemainingArgs, cl);
         }
 
         /* should never reach here */
     }
 
     /**
-     * Note that preparing the profiles for system server does not require special
-     * selinux permissions. From the installer perspective the system server is a regular package
-     * which can capture profile information.
+     * Note that preparing the profiles for system server does not require special selinux
+     * permissions. From the installer perspective the system server is a regular package which can
+     * capture profile information.
      */
     private static void prepareSystemServerProfile(String systemServerClasspath)
             throws RemoteException {
@@ -544,8 +548,8 @@
     }
 
     /**
-     * Performs dex-opt on the elements of {@code classPath}, if needed. We
-     * choose the instruction set of the current runtime.
+     * Performs dex-opt on the elements of {@code classPath}, if needed. We choose the instruction
+     * set of the current runtime.
      */
     private static void performSystemServerDexOpt(String classPath) {
         final String[] classPathElements = classPath.split(":");
@@ -563,8 +567,9 @@
             int dexoptNeeded;
             try {
                 dexoptNeeded = DexFile.getDexOptNeeded(
-                    classPathElement, instructionSet, systemServerFilter,
-                    null /* classLoaderContext */, false /* newProfile */, false /* downgrade */);
+                        classPathElement, instructionSet, systemServerFilter,
+                        null /* classLoaderContext */, false /* newProfile */,
+                        false /* downgrade */);
             } catch (FileNotFoundException ignored) {
                 // Do not add to the classpath.
                 Log.w(TAG, "Missing classpath element for system server: " + classPathElement);
@@ -607,8 +612,8 @@
     }
 
     /**
-     * Encodes the system server class loader context in a format that is accepted by dexopt.
-     * This assumes the system server is always loaded with a {@link dalvik.system.PathClassLoader}.
+     * Encodes the system server class loader context in a format that is accepted by dexopt. This
+     * assumes the system server is always loaded with a {@link dalvik.system.PathClassLoader}.
      *
      * Note that ideally we would use the {@code DexoptUtils} to compute this. However we have no
      * dependency here on the server so we hard code the logic again.
@@ -619,10 +624,11 @@
 
     /**
      * Encodes the class path in a format accepted by dexopt.
-     * @param classPath the old class path (may be empty).
-     * @param newElement the new class path elements
-     * @return the class path encoding resulted from appending {@code newElement} to
-     * {@code classPath}.
+     *
+     * @param classPath  The old class path (may be empty).
+     * @param newElement  The new class path elements
+     * @return The class path encoding resulted from appending {@code newElement} to {@code
+     * classPath}.
      */
     private static String encodeSystemServerClassPath(String classPath, String newElement) {
         return (classPath == null || classPath.isEmpty())
@@ -633,25 +639,25 @@
     /**
      * Prepare the arguments and forks for the system server process.
      *
-     * Returns an {@code Runnable} that provides an entrypoint into system_server code in the
-     * child process, and {@code null} in the parent.
+     * @return A {@code Runnable} that provides an entrypoint into system_server code in the child
+     * process; {@code null} in the parent.
      */
     private static Runnable forkSystemServer(String abiList, String socketName,
             ZygoteServer zygoteServer) {
         long capabilities = posixCapabilitiesAsBits(
-            OsConstants.CAP_IPC_LOCK,
-            OsConstants.CAP_KILL,
-            OsConstants.CAP_NET_ADMIN,
-            OsConstants.CAP_NET_BIND_SERVICE,
-            OsConstants.CAP_NET_BROADCAST,
-            OsConstants.CAP_NET_RAW,
-            OsConstants.CAP_SYS_MODULE,
-            OsConstants.CAP_SYS_NICE,
-            OsConstants.CAP_SYS_PTRACE,
-            OsConstants.CAP_SYS_TIME,
-            OsConstants.CAP_SYS_TTY_CONFIG,
-            OsConstants.CAP_WAKE_ALARM,
-            OsConstants.CAP_BLOCK_SUSPEND
+                OsConstants.CAP_IPC_LOCK,
+                OsConstants.CAP_KILL,
+                OsConstants.CAP_NET_ADMIN,
+                OsConstants.CAP_NET_BIND_SERVICE,
+                OsConstants.CAP_NET_BROADCAST,
+                OsConstants.CAP_NET_RAW,
+                OsConstants.CAP_SYS_MODULE,
+                OsConstants.CAP_SYS_NICE,
+                OsConstants.CAP_SYS_PTRACE,
+                OsConstants.CAP_SYS_TIME,
+                OsConstants.CAP_SYS_TTY_CONFIG,
+                OsConstants.CAP_WAKE_ALARM,
+                OsConstants.CAP_BLOCK_SUSPEND
         );
         /* Containers run without some capabilities, so drop any caps that are not available. */
         StructCapUserHeader header = new StructCapUserHeader(
@@ -666,38 +672,39 @@
 
         /* Hardcoded command line to start the system server */
         String args[] = {
-            "--setuid=1000",
-            "--setgid=1000",
-            "--setgroups=1001,1002,1003,1004,1005,1006,1007,1008,1009,1010,1018,1021,1023,1024,1032,1065,3001,3002,3003,3006,3007,3009,3010",
-            "--capabilities=" + capabilities + "," + capabilities,
-            "--nice-name=system_server",
-            "--runtime-args",
-            "--target-sdk-version=" + VMRuntime.SDK_VERSION_CUR_DEVELOPMENT,
-            "com.android.server.SystemServer",
+                "--setuid=1000",
+                "--setgid=1000",
+                "--setgroups=1001,1002,1003,1004,1005,1006,1007,1008,1009,1010,1018,1021,1023,"
+                        + "1024,1032,1065,3001,3002,3003,3006,3007,3009,3010",
+                "--capabilities=" + capabilities + "," + capabilities,
+                "--nice-name=system_server",
+                "--runtime-args",
+                "--target-sdk-version=" + VMRuntime.SDK_VERSION_CUR_DEVELOPMENT,
+                "com.android.server.SystemServer",
         };
-        ZygoteConnection.Arguments parsedArgs = null;
+        ZygoteArguments parsedArgs = null;
 
         int pid;
 
         try {
-            parsedArgs = new ZygoteConnection.Arguments(args);
-            ZygoteConnection.applyDebuggerSystemProperty(parsedArgs);
-            ZygoteConnection.applyInvokeWithSystemProperty(parsedArgs);
+            parsedArgs = new ZygoteArguments(args);
+            Zygote.applyDebuggerSystemProperty(parsedArgs);
+            Zygote.applyInvokeWithSystemProperty(parsedArgs);
 
             boolean profileSystemServer = SystemProperties.getBoolean(
                     "dalvik.vm.profilesystemserver", false);
             if (profileSystemServer) {
-                parsedArgs.runtimeFlags |= Zygote.PROFILE_SYSTEM_SERVER;
+                parsedArgs.mRuntimeFlags |= Zygote.PROFILE_SYSTEM_SERVER;
             }
 
             /* Request to fork the system server process */
             pid = Zygote.forkSystemServer(
-                    parsedArgs.uid, parsedArgs.gid,
-                    parsedArgs.gids,
-                    parsedArgs.runtimeFlags,
+                    parsedArgs.mUid, parsedArgs.mGid,
+                    parsedArgs.mGids,
+                    parsedArgs.mRuntimeFlags,
                     null,
-                    parsedArgs.permittedCapabilities,
-                    parsedArgs.effectiveCapabilities);
+                    parsedArgs.mPermittedCapabilities,
+                    parsedArgs.mEffectiveCapabilities);
         } catch (IllegalArgumentException ex) {
             throw new RuntimeException(ex);
         }
@@ -743,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"))) {
@@ -779,16 +786,26 @@
                 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) {
                 bootTimingsTraceLog.traceBegin("ZygotePreload");
                 EventLog.writeEvent(LOG_BOOT_PROGRESS_PRELOAD_START,
-                    SystemClock.uptimeMillis());
+                        SystemClock.uptimeMillis());
                 preload(bootTimingsTraceLog);
                 EventLog.writeEvent(LOG_BOOT_PROGRESS_PRELOAD_END,
-                    SystemClock.uptimeMillis());
+                        SystemClock.uptimeMillis());
                 bootTimingsTraceLog.traceEnd(); // ZygotePreload
             } else {
                 Zygote.resetNicePriority();
@@ -822,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;
@@ -844,17 +868,16 @@
     /**
      * Return {@code true} if this device configuration has another zygote.
      *
-     * We determine this by comparing the device ABI list with this zygotes
-     * list. If this zygote supports all ABIs this device supports, there won't
-     * be another zygote.
+     * We determine this by comparing the device ABI list with this zygotes list. If this zygote
+     * supports all ABIs this device supports, there won't be another zygote.
      */
     private static boolean hasSecondZygote(String abiList) {
         return !SystemProperties.get("ro.product.cpu.abilist").equals(abiList);
     }
 
     private static void waitForSecondaryZygote(String socketName) {
-        String otherZygoteName = Process.ZYGOTE_SOCKET.equals(socketName) ?
-                Process.SECONDARY_ZYGOTE_SOCKET : Process.ZYGOTE_SOCKET;
+        String otherZygoteName = ZygoteProcess.ZYGOTE_SOCKET_NAME.equals(socketName)
+                ? ZygoteProcess.ZYGOTE_SECONDARY_SOCKET_NAME : ZygoteProcess.ZYGOTE_SOCKET_NAME;
         ZygoteProcess.waitForConnectionToZygote(otherZygoteName);
     }
 
@@ -869,9 +892,8 @@
     }
 
     /**
-     * The main function called when started through the zygote process. This
-     * could be unified with main(), if the native code in nativeFinishInit()
-     * were rationalized with Zygote startup.<p>
+     * The main function called when started through the zygote process. This could be unified with
+     * main(), if the native code in nativeFinishInit() were rationalized with Zygote startup.<p>
      *
      * Current recognized args:
      * <ul>
@@ -881,7 +903,8 @@
      * @param targetSdkVersion target SDK version
      * @param argv arg strings
      */
-    public static final Runnable zygoteInit(int targetSdkVersion, String[] argv, ClassLoader classLoader) {
+    public static final Runnable zygoteInit(int targetSdkVersion, String[] argv,
+            ClassLoader classLoader) {
         if (RuntimeInit.DEBUG) {
             Slog.d(RuntimeInit.TAG, "RuntimeInit: Starting application from zygote");
         }
@@ -895,9 +918,9 @@
     }
 
     /**
-     * The main function called when starting a child zygote process. This is used as an
-     * alternative to zygoteInit(), which skips calling into initialization routines that
-     * start the Binder threadpool.
+     * The main function called when starting a child zygote process. This is used as an alternative
+     * to zygoteInit(), which skips calling into initialization routines that start the Binder
+     * threadpool.
      */
     static final Runnable childZygoteInit(
             int targetSdkVersion, String[] argv, ClassLoader classLoader) {
diff --git a/core/java/com/android/internal/os/ZygoteServer.java b/core/java/com/android/internal/os/ZygoteServer.java
index fecf9b9..a78c095 100644
--- a/core/java/com/android/internal/os/ZygoteServer.java
+++ b/core/java/com/android/internal/os/ZygoteServer.java
@@ -20,14 +20,16 @@
 
 import android.net.LocalServerSocket;
 import android.net.LocalSocket;
-import android.system.Os;
 import android.system.ErrnoException;
+import android.system.Os;
 import android.system.StructPollfd;
 import android.util.Log;
-
 import android.util.Slog;
-import java.io.IOException;
+
+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.
@@ -63,39 +64,24 @@
      */
     private boolean mIsForkChild;
 
-    ZygoteServer() {
-    }
+    ZygoteServer() { }
 
     void setForkChild() {
         mIsForkChild = true;
     }
 
     /**
-     * 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;
         }
     }
 
@@ -104,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(
@@ -121,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);
@@ -139,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);
                 }
@@ -152,7 +138,7 @@
             Log.e(TAG, "Zygote:  error closing descriptor", ex);
         }
 
-        mServerSocket = null;
+        mZygoteSocket = null;
     }
 
     /**
@@ -161,8 +147,8 @@
      * closure after a child process is forked off.
      */
 
-    FileDescriptor getServerSocketFileDescriptor() {
-        return mServerSocket.getFileDescriptor();
+    FileDescriptor getZygoteSocketFileDescriptor() {
+        return mZygoteSocket.getFileDescriptor();
     }
 
     /**
@@ -171,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.getFileDesciptor());
-                } 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) {
@@ -218,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) {
@@ -235,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()
@@ -255,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/IStatusBarService.aidl b/core/java/com/android/internal/statusbar/IStatusBarService.aidl
index 197e873..d61f10e 100644
--- a/core/java/com/android/internal/statusbar/IStatusBarService.aidl
+++ b/core/java/com/android/internal/statusbar/IStatusBarService.aidl
@@ -65,7 +65,7 @@
             int dismissalSurface, int dismissalSentiment, in NotificationVisibility nv);
     void onNotificationVisibilityChanged( in NotificationVisibility[] newlyVisibleKeys,
             in NotificationVisibility[] noLongerVisibleKeys);
-    void onNotificationExpansionChanged(in String key, in boolean userAction, in boolean expanded);
+    void onNotificationExpansionChanged(in String key, in boolean userAction, in boolean expanded, in int notificationLocation);
     void onNotificationDirectReplied(String key);
     void onNotificationSmartSuggestionsAdded(String key, int smartReplyCount, int smartActionCount,
             boolean generatedByAsssistant);
diff --git a/core/java/com/android/internal/statusbar/NotificationVisibility.java b/core/java/com/android/internal/statusbar/NotificationVisibility.java
index a7203e7..24bb789 100644
--- a/core/java/com/android/internal/statusbar/NotificationVisibility.java
+++ b/core/java/com/android/internal/statusbar/NotificationVisibility.java
@@ -21,6 +21,8 @@
 import android.os.Parcelable;
 import android.util.Log;
 
+import com.android.internal.logging.nano.MetricsProto.MetricsEvent;
+
 import java.util.ArrayDeque;
 import java.util.Collection;
 
@@ -33,18 +35,53 @@
     public int rank;
     public int count;
     public boolean visible = true;
+    /** The visible location of the notification, could be e.g. notification shade or HUN. */
+    public NotificationLocation location;
     /*package*/ int id;
 
+    /**
+     * The UI location of the notification.
+     *
+     * There is a one-to-one mapping between this enum and
+     * MetricsProto.MetricsEvent.NotificationLocation.
+     */
+    public enum NotificationLocation {
+        LOCATION_UNKNOWN(MetricsEvent.LOCATION_UNKNOWN),
+        LOCATION_FIRST_HEADS_UP(MetricsEvent.LOCATION_FIRST_HEADS_UP), // visible heads-up
+        LOCATION_HIDDEN_TOP(MetricsEvent.LOCATION_HIDDEN_TOP), // hidden/scrolled away on the top
+        LOCATION_MAIN_AREA(MetricsEvent.LOCATION_MAIN_AREA), // visible in the shade
+        // in the bottom stack, and peeking
+        LOCATION_BOTTOM_STACK_PEEKING(MetricsEvent.LOCATION_BOTTOM_STACK_PEEKING),
+        // in the bottom stack, and hidden
+        LOCATION_BOTTOM_STACK_HIDDEN(MetricsEvent.LOCATION_BOTTOM_STACK_HIDDEN),
+        LOCATION_GONE(MetricsEvent.LOCATION_GONE); // the view isn't laid out at all
+
+        private final int mMetricsEventNotificationLocation;
+
+        NotificationLocation(int metricsEventNotificationLocation) {
+            mMetricsEventNotificationLocation = metricsEventNotificationLocation;
+        }
+
+        /**
+         * Returns the field from MetricsEvent.NotificationLocation that corresponds to this object.
+         */
+        public int toMetricsEventEnum() {
+            return mMetricsEventNotificationLocation;
+        }
+    }
+
     private NotificationVisibility() {
         id = sNexrId++;
     }
 
-    private NotificationVisibility(String key, int rank, int count, boolean visibile) {
+    private NotificationVisibility(String key, int rank, int count, boolean visible,
+            NotificationLocation location) {
         this();
         this.key = key;
         this.rank = rank;
         this.count = count;
-        this.visible = visibile;
+        this.visible = visible;
+        this.location = location;
     }
 
     @Override
@@ -54,12 +91,13 @@
                 + " rank=" + rank
                 + " count=" + count
                 + (visible?" visible":"")
+                + " location=" + location.name()
                 + " )";
     }
 
     @Override
     public NotificationVisibility clone() {
-        return obtain(this.key, this.rank, this.count, this.visible);
+        return obtain(this.key, this.rank, this.count, this.visible, this.location);
     }
 
     @Override
@@ -89,6 +127,7 @@
         out.writeInt(this.rank);
         out.writeInt(this.count);
         out.writeInt(this.visible ? 1 : 0);
+        out.writeString(this.location.name());
     }
 
     private void readFromParcel(Parcel in) {
@@ -96,18 +135,28 @@
         this.rank = in.readInt();
         this.count = in.readInt();
         this.visible = in.readInt() != 0;
+        this.location = NotificationLocation.valueOf(in.readString());
     }
 
     /**
-     * Return a new NotificationVisibility instance from the global pool. Allows us to
-     * avoid allocating new objects in many cases.
+     * Create a new NotificationVisibility object.
      */
     public static NotificationVisibility obtain(String key, int rank, int count, boolean visible) {
+        return obtain(key, rank, count, visible,
+                NotificationVisibility.NotificationLocation.LOCATION_UNKNOWN);
+    }
+
+    /**
+     * Create a new NotificationVisibility object.
+     */
+    public static NotificationVisibility obtain(String key, int rank, int count, boolean visible,
+            NotificationLocation location) {
         NotificationVisibility vo = obtain();
         vo.key = key;
         vo.rank = rank;
         vo.count = count;
         vo.visible = visible;
+        vo.location = location;
         return vo;
     }
 
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/CollectionUtils.java b/core/java/com/android/internal/util/CollectionUtils.java
index 605df04..f91b837 100644
--- a/core/java/com/android/internal/util/CollectionUtils.java
+++ b/core/java/com/android/internal/util/CollectionUtils.java
@@ -327,10 +327,6 @@
         }
     }
 
-    public static @NonNull <T> List<T> defeatNullable(@Nullable List<T> val) {
-        return (val != null) ? val : Collections.emptyList();
-    }
-
     /**
      * @return the first element if not empty/null, null otherwise
      */
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/IInputMethodManager.aidl b/core/java/com/android/internal/view/IInputMethodManager.aidl
index 356d178..0752efe 100644
--- a/core/java/com/android/internal/view/IInputMethodManager.aidl
+++ b/core/java/com/android/internal/view/IInputMethodManager.aidl
@@ -35,7 +35,6 @@
 
     // TODO: Use ParceledListSlice instead
     List<InputMethodInfo> getInputMethodList();
-    List<InputMethodInfo> getVrInputMethodList();
     // TODO: Use ParceledListSlice instead
     List<InputMethodInfo> getEnabledInputMethodList();
     List<InputMethodSubtype> getEnabledInputMethodSubtypeList(in String imiId,
@@ -65,7 +64,6 @@
     void showInputMethodAndSubtypeEnablerFromClient(in IInputMethodClient client, String topId);
     boolean isInputMethodPickerShownForTest();
     InputMethodSubtype getCurrentInputMethodSubtype();
-    boolean setCurrentInputMethodSubtype(in InputMethodSubtype subtype);
     void setAdditionalInputMethodSubtypes(String id, in InputMethodSubtype[] subtypes);
     // This is kept due to @UnsupportedAppUsage.
     // TODO(Bug 113914148): Consider removing this.
diff --git a/core/java/com/android/internal/widget/LockPatternUtils.java b/core/java/com/android/internal/widget/LockPatternUtils.java
index d5dc703..8d3c482 100644
--- a/core/java/com/android/internal/widget/LockPatternUtils.java
+++ b/core/java/com/android/internal/widget/LockPatternUtils.java
@@ -34,6 +34,7 @@
 import android.content.ComponentName;
 import android.content.ContentResolver;
 import android.content.Context;
+import android.content.pm.PackageManager;
 import android.content.pm.UserInfo;
 import android.os.AsyncTask;
 import android.os.Handler;
@@ -176,6 +177,7 @@
     private UserManager mUserManager;
     private final Handler mHandler;
     private final SparseLongArray mLockoutDeadlines = new SparseLongArray();
+    private Boolean mHasSecureLockScreen;
 
     /**
      * Use {@link TrustManager#isTrustUsuallyManaged(int)}.
@@ -706,6 +708,10 @@
      * @param userId the user whose pattern is to be saved.
      */
     public void saveLockPattern(List<LockPatternView.Cell> pattern, String savedPattern, int userId) {
+        if (!hasSecureLockScreen()) {
+            throw new UnsupportedOperationException(
+                    "This operation requires the lock screen feature.");
+        }
         if (pattern == null || pattern.size() < MIN_LOCK_PATTERN_SIZE) {
             throw new IllegalArgumentException("pattern must not be null and at least "
                     + MIN_LOCK_PATTERN_SIZE + " dots long.");
@@ -801,6 +807,10 @@
 
     /** Update the encryption password if it is enabled **/
     private void updateEncryptionPassword(final int type, final String password) {
+        if (!hasSecureLockScreen()) {
+            throw new UnsupportedOperationException(
+                    "This operation requires the lock screen feature.");
+        }
         if (!isDeviceEncryptionEnabled()) {
             return;
         }
@@ -835,6 +845,10 @@
      */
     public void saveLockPassword(String password, String savedPassword, int requestedQuality,
             int userHandle) {
+        if (!hasSecureLockScreen()) {
+            throw new UnsupportedOperationException(
+                    "This operation requires the lock screen feature.");
+        }
         if (password == null || password.length() < MIN_LOCK_PASSWORD_SIZE) {
             throw new IllegalArgumentException("password must not be null and at least "
                     + "of length " + MIN_LOCK_PASSWORD_SIZE);
@@ -1621,6 +1635,10 @@
      */
     public boolean setLockCredentialWithToken(String credential, int type, int requestedQuality,
             long tokenHandle, byte[] token, int userId) {
+        if (!hasSecureLockScreen()) {
+            throw new UnsupportedOperationException(
+                    "This operation requires the lock screen feature.");
+        }
         LockSettingsInternal localService = getLockSettingsInternal();
         if (type != CREDENTIAL_TYPE_NONE) {
             if (TextUtils.isEmpty(credential) || credential.length() < MIN_LOCK_PASSWORD_SIZE) {
@@ -1854,6 +1872,17 @@
         return getLong(SYNTHETIC_PASSWORD_ENABLED_KEY, 0, UserHandle.USER_SYSTEM) != 0;
     }
 
+    /**
+     * Return true if the device supports the lock screen feature, false otherwise.
+     */
+    public boolean hasSecureLockScreen() {
+        if (mHasSecureLockScreen == null) {
+            mHasSecureLockScreen = Boolean.valueOf(mContext.getPackageManager()
+                    .hasSystemFeature(PackageManager.FEATURE_SECURE_LOCK_SCREEN));
+        }
+        return mHasSecureLockScreen.booleanValue();
+    }
+
     public static boolean userOwnsFrpCredential(Context context, UserInfo info) {
         return info != null && info.isPrimary() && info.isAdmin() && frpCredentialEnabled(context);
     }
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 c5dfe6c..be12700 100644
--- a/core/jni/Android.bp
+++ b/core/jni/Android.bp
@@ -186,6 +186,7 @@
         "android_hardware_UsbDevice.cpp",
         "android_hardware_UsbDeviceConnection.cpp",
         "android_hardware_UsbRequest.cpp",
+        "android_hardware_location_ActivityRecognitionHardware.cpp",
         "android_util_FileObserver.cpp",
         "android/opengl/poly_clip.cpp", // TODO: .arm
         "android/opengl/util.cpp",
diff --git a/core/jni/AndroidRuntime.cpp b/core/jni/AndroidRuntime.cpp
index 5befab9..f458299 100644
--- a/core/jni/AndroidRuntime.cpp
+++ b/core/jni/AndroidRuntime.cpp
@@ -101,6 +101,7 @@
 extern int register_android_hardware_UsbDevice(JNIEnv *env);
 extern int register_android_hardware_UsbDeviceConnection(JNIEnv *env);
 extern int register_android_hardware_UsbRequest(JNIEnv *env);
+extern int register_android_hardware_location_ActivityRecognitionHardware(JNIEnv* env);
 
 extern int register_android_media_AudioEffectDescriptor(JNIEnv *env);
 extern int register_android_media_AudioRecord(JNIEnv *env);
@@ -151,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);
@@ -1369,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),
@@ -1460,6 +1465,7 @@
     REG_JNI(register_android_hardware_UsbDevice),
     REG_JNI(register_android_hardware_UsbDeviceConnection),
     REG_JNI(register_android_hardware_UsbRequest),
+    REG_JNI(register_android_hardware_location_ActivityRecognitionHardware),
     REG_JNI(register_android_media_AudioEffectDescriptor),
     REG_JNI(register_android_media_AudioSystem),
     REG_JNI(register_android_media_AudioRecord),
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..acb34ba 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,31 +659,13 @@
         *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) {
@@ -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/graphics/Picture.cpp b/core/jni/android/graphics/Picture.cpp
index fd1d87f..d29857d 100644
--- a/core/jni/android/graphics/Picture.cpp
+++ b/core/jni/android/graphics/Picture.cpp
@@ -37,6 +37,12 @@
     }
 }
 
+Picture::Picture(sk_sp<SkPicture>&& src) {
+    mPicture = std::move(src);
+    mWidth = 0;
+    mHeight = 0;
+}
+
 Canvas* Picture::beginRecording(int width, int height) {
     mPicture.reset(NULL);
     mRecorder.reset(new SkPictureRecorder);
diff --git a/core/jni/android/graphics/Picture.h b/core/jni/android/graphics/Picture.h
index 3068631..536f651 100644
--- a/core/jni/android/graphics/Picture.h
+++ b/core/jni/android/graphics/Picture.h
@@ -37,6 +37,7 @@
 class Picture {
 public:
     explicit Picture(const Picture* src = NULL);
+    explicit Picture(sk_sp<SkPicture>&& src);
 
     Canvas* beginRecording(int width, int height);
 
diff --git a/core/jni/android_app_ActivityThread.cpp b/core/jni/android_app_ActivityThread.cpp
index d56e4c5..93f2525 100644
--- a/core/jni/android_app_ActivityThread.cpp
+++ b/core/jni/android_app_ActivityThread.cpp
@@ -24,6 +24,8 @@
 #include "core_jni_helpers.h"
 #include <unistd.h>
 
+#include <bionic_malloc.h>
+
 namespace android {
 
 static void android_app_ActivityThread_purgePendingResources(JNIEnv* env, jobject clazz) {
@@ -38,13 +40,18 @@
     minikin::Layout::dumpMinikinStats(fd);
 }
 
+static void android_app_ActivityThread_initZygoteChildHeapProfiling(JNIEnv* env, jobject clazz) {
+    android_mallopt(M_INIT_ZYGOTE_CHILD_PROFILING, nullptr, 0);
+}
 
 static JNINativeMethod gActivityThreadMethods[] = {
     // ------------ Regular JNI ------------------
     { "nPurgePendingResources",        "()V",
       (void*) android_app_ActivityThread_purgePendingResources },
     { "nDumpGraphicsInfo",        "(Ljava/io/FileDescriptor;)V",
-      (void*) android_app_ActivityThread_dumpGraphics }
+      (void*) android_app_ActivityThread_dumpGraphics },
+    { "nInitZygoteChildHeapProfiling",        "()V",
+      (void*) android_app_ActivityThread_initZygoteChildHeapProfiling }
 };
 
 int register_android_app_ActivityThread(JNIEnv* env) {
diff --git a/core/jni/android_graphics_ColorSpace.cpp b/core/jni/android_graphics_ColorSpace.cpp
index 7a9963e..7648fd0 100644
--- a/core/jni/android_graphics_ColorSpace.cpp
+++ b/core/jni/android_graphics_ColorSpace.cpp
@@ -20,7 +20,6 @@
 
 #include "SkColor.h"
 #include "SkColorSpace.h"
-#include "SkHalf.h"
 
 using namespace android;
 
@@ -42,6 +41,12 @@
 
 ///////////////////////////////////////////////////////////////////////////////
 
+static float halfToFloat(uint16_t bits) {
+  __fp16 h;
+  memcpy(&h, &bits, 2);
+  return (float)h;
+}
+
 SkColor4f GraphicsJNI::convertColorLong(jlong color) {
     if ((color & 0x3f) == 0) {
         // This corresponds to sRGB, which is treated differently than the rest.
@@ -54,9 +59,9 @@
     }
 
     // These match the implementation of android.graphics.Color#red(long) etc.
-    float r = SkHalfToFloat((SkHalf)(color >> 48 & 0xffff));
-    float g = SkHalfToFloat((SkHalf)(color >> 32 & 0xffff));
-    float b = SkHalfToFloat((SkHalf)(color >> 16 & 0xffff));
+    float r = halfToFloat((uint16_t)(color >> 48 & 0xffff));
+    float g = halfToFloat((uint16_t)(color >> 32 & 0xffff));
+    float b = halfToFloat((uint16_t)(color >> 16 & 0xffff));
     float a =                       (color >>  6 &  0x3ff) / 1023.0f;
 
     return SkColor4f{r, g, b, a};
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_hardware_location_ActivityRecognitionHardware.cpp b/core/jni/android_hardware_location_ActivityRecognitionHardware.cpp
new file mode 100644
index 0000000..1c9ab94
--- /dev/null
+++ b/core/jni/android_hardware_location_ActivityRecognitionHardware.cpp
@@ -0,0 +1,132 @@
+/*
+ * Copyright 2014, The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#define LOG_TAG "ActivityRecognitionHardware"
+
+#include <jni.h>
+#include <nativehelper/JNIHelp.h>
+
+#include <android_runtime/AndroidRuntime.h>
+#include <android_runtime/Log.h>
+
+// #include <hardware/activity_recognition.h>
+// The activity recognition HAL is being deprecated. This means -
+//    i) Android framework code shall not depend on activity recognition
+//       being provided through the activity_recognition.h interface.
+//   ii) activity recognition HAL will not be binderized as the other HALs.
+//
+
+/**
+ * Initializes the ActivityRecognitionHardware class from the native side.
+ */
+static void class_init(JNIEnv* /*env*/, jclass /*clazz*/) {
+    ALOGE("activity_recognition HAL is deprecated. %s is effectively a no-op",
+          __FUNCTION__);
+}
+
+/**
+ * Initializes and connect the callbacks handlers in the HAL.
+ */
+static void initialize(JNIEnv* /*env*/, jobject /*obj*/) {
+    ALOGE("activity_recognition HAL is deprecated. %s is effectively a no-op",
+          __FUNCTION__);
+}
+
+/**
+ * De-initializes the ActivityRecognitionHardware from the native side.
+ */
+static void release(JNIEnv* /*env*/, jobject /*obj*/) {
+    ALOGE("activity_recognition HAL is deprecated. %s is effectively a no-op",
+          __FUNCTION__);
+}
+
+/**
+ * Returns true if ActivityRecognition HAL is supported, false otherwise.
+ */
+static jboolean is_supported(JNIEnv* /*env*/, jclass /*clazz*/) {
+    ALOGE("activity_recognition HAL is deprecated. %s is effectively a no-op",
+          __FUNCTION__);
+    return JNI_FALSE;
+}
+
+/**
+ * Gets an array representing the supported activities.
+ */
+static jobjectArray get_supported_activities(JNIEnv* /*env*/, jobject /*obj*/) {
+    ALOGE("activity_recognition HAL is deprecated. %s is effectively a no-op",
+          __FUNCTION__);
+    return NULL;
+}
+
+/**
+ * Enables a given activity event to be actively monitored.
+ */
+static int enable_activity_event(
+        JNIEnv* /*env*/,
+        jobject /*obj*/,
+        jint /*activity_handle*/,
+        jint /*event_type*/,
+        jlong /*report_latency_ns*/) {
+    ALOGE("activity_recognition HAL is deprecated. %s is effectively a no-op",
+          __FUNCTION__);
+    return android::NO_INIT;
+}
+
+/**
+ * Disables a given activity event from being actively monitored.
+ */
+static int disable_activity_event(
+        JNIEnv* /*env*/,
+        jobject /*obj*/,
+        jint /*activity_handle*/,
+        jint /*event_type*/) {
+    ALOGE("activity_recognition HAL is deprecated. %s is effectively a no-op",
+          __FUNCTION__);
+    return android::NO_INIT;
+}
+
+/**
+ * Request flush for al batch buffers.
+ */
+static int flush(JNIEnv* /*env*/, jobject /*obj*/) {
+    ALOGE("activity_recognition HAL is deprecated. %s is effectively a no-op",
+          __FUNCTION__);
+    return android::NO_INIT;
+}
+
+
+static const JNINativeMethod sMethods[] = {
+    // {"name", "signature", (void*) functionPointer },
+    { "nativeClassInit", "()V", (void*) class_init },
+    { "nativeInitialize", "()V", (void*) initialize },
+    { "nativeRelease", "()V", (void*) release },
+    { "nativeIsSupported", "()Z", (void*) is_supported },
+    { "nativeGetSupportedActivities", "()[Ljava/lang/String;", (void*) get_supported_activities },
+    { "nativeEnableActivityEvent", "(IIJ)I", (void*) enable_activity_event },
+    { "nativeDisableActivityEvent", "(II)I", (void*) disable_activity_event },
+    { "nativeFlush", "()I", (void*) flush },
+};
+
+/**
+ * Registration method invoked in JNI load.
+ */
+int register_android_hardware_location_ActivityRecognitionHardware(JNIEnv* env) {
+    return jniRegisterNativeMethods(
+            env,
+            "android/hardware/location/ActivityRecognitionHardware",
+            sMethods,
+            NELEM(sMethods));
+}
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_net_NetUtils.cpp b/core/jni/android_net_NetUtils.cpp
index 9b138eb..7eddcfe 100644
--- a/core/jni/android_net_NetUtils.cpp
+++ b/core/jni/android_net_NetUtils.cpp
@@ -16,8 +16,11 @@
 
 #define LOG_TAG "NetUtils"
 
+#include <vector>
+
 #include "jni.h"
 #include <nativehelper/JNIHelp.h>
+#include <nativehelper/ScopedLocalRef.h>
 #include "NetdClient.h"
 #include <utils/misc.h>
 #include <android_runtime/AndroidRuntime.h>
@@ -55,6 +58,31 @@
 static const uint32_t kUDPDstPortIndirectOffset = kEtherHeaderLen + offsetof(udphdr, dest);
 static const uint16_t kDhcpClientPort = 68;
 
+constexpr int MAXPACKETSIZE = 8 * 1024;
+// FrameworkListener limits the size of commands to 1024 bytes. TODO: fix this.
+constexpr int MAXCMDSIZE = 1024;
+
+static void throwErrnoException(JNIEnv* env, const char* functionName, int error) {
+    ScopedLocalRef<jstring> detailMessage(env, env->NewStringUTF(functionName));
+    if (detailMessage.get() == NULL) {
+        // Not really much we can do here. We're probably dead in the water,
+        // but let's try to stumble on...
+        env->ExceptionClear();
+    }
+    static jclass errnoExceptionClass =
+            MakeGlobalRefOrDie(env, FindClassOrDie(env, "android/system/ErrnoException"));
+
+    static jmethodID errnoExceptionCtor =
+            GetMethodIDOrDie(env, errnoExceptionClass,
+            "<init>", "(Ljava/lang/String;I)V");
+
+    jobject exception = env->NewObject(errnoExceptionClass,
+                                       errnoExceptionCtor,
+                                       detailMessage.get(),
+                                       error);
+    env->Throw(reinterpret_cast<jthrowable>(exception));
+}
+
 static void android_net_utils_attachDhcpFilter(JNIEnv *env, jobject clazz, jobject javaFd)
 {
     struct sock_filter filter_code[] = {
@@ -372,6 +400,63 @@
     }
 }
 
+static jobject android_net_utils_resNetworkQuery(JNIEnv *env, jobject thiz, jint netId,
+        jstring dname, jint ns_class, jint ns_type, jint flags) {
+    const jsize javaCharsCount = env->GetStringLength(dname);
+    const jsize byteCountUTF8 = env->GetStringUTFLength(dname);
+
+    // Only allow dname which could be simply formatted to UTF8.
+    // In native layer, res_mkquery would re-format the input char array to packet.
+    std::vector<char> queryname(byteCountUTF8 + 1, 0);
+
+    env->GetStringUTFRegion(dname, 0, javaCharsCount, queryname.data());
+    int fd = resNetworkQuery(netId, queryname.data(), ns_class, ns_type, flags);
+
+    if (fd < 0) {
+        throwErrnoException(env, "resNetworkQuery", -fd);
+        return nullptr;
+    }
+
+    return jniCreateFileDescriptor(env, fd);
+}
+
+static jobject android_net_utils_resNetworkSend(JNIEnv *env, jobject thiz, jint netId,
+        jbyteArray msg, jint msgLen, jint flags) {
+    uint8_t data[MAXCMDSIZE];
+
+    checkLenAndCopy(env, msg, msgLen, data);
+    int fd = resNetworkSend(netId, data, msgLen, flags);
+
+    if (fd < 0) {
+        throwErrnoException(env, "resNetworkSend", -fd);
+        return nullptr;
+    }
+
+    return jniCreateFileDescriptor(env, fd);
+}
+
+static jbyteArray android_net_utils_resNetworkResult(JNIEnv *env, jobject thiz, jobject javaFd) {
+    int fd = jniGetFDFromFileDescriptor(env, javaFd);
+    int rcode;
+    std::vector<uint8_t> buf(MAXPACKETSIZE, 0);
+
+    int res = resNetworkResult(fd, &rcode, buf.data(), MAXPACKETSIZE);
+    if (res < 0) {
+        throwErrnoException(env, "resNetworkResult", -res);
+        return nullptr;
+    }
+
+    jbyteArray answer = env->NewByteArray(res);
+    if (answer == nullptr) {
+        throwErrnoException(env, "resNetworkResult", ENOMEM);
+        return nullptr;
+    } else {
+        env->SetByteArrayRegion(answer, 0, res,
+                reinterpret_cast<jbyte*>(buf.data()));
+    }
+
+    return answer;
+}
 
 // ----------------------------------------------------------------------------
 
@@ -391,6 +476,9 @@
     { "attachRaFilter", "(Ljava/io/FileDescriptor;I)V", (void*) android_net_utils_attachRaFilter },
     { "attachControlPacketFilter", "(Ljava/io/FileDescriptor;I)V", (void*) android_net_utils_attachControlPacketFilter },
     { "setupRaSocket", "(Ljava/io/FileDescriptor;I)V", (void*) android_net_utils_setupRaSocket },
+    { "resNetworkSend", "(I[BII)Ljava/io/FileDescriptor;", (void*) android_net_utils_resNetworkSend },
+    { "resNetworkQuery", "(ILjava/lang/String;III)Ljava/io/FileDescriptor;", (void*) android_net_utils_resNetworkQuery },
+    { "resNetworkResult", "(Ljava/io/FileDescriptor;)[B", (void*) android_net_utils_resNetworkResult },
 };
 
 int register_android_net_NetworkUtils(JNIEnv* env)
diff --git a/core/jni/android_os_Debug.cpp b/core/jni/android_os_Debug.cpp
index 7d63ec9..7837248 100644
--- a/core/jni/android_os_Debug.cpp
+++ b/core/jni/android_os_Debug.cpp
@@ -45,6 +45,7 @@
 #include <meminfo/sysmeminfo.h>
 #include <memtrack/memtrack.h>
 #include <memunreachable/memunreachable.h>
+#include <android-base/strings.h>
 #include "android_os_Debug.h"
 
 namespace android
@@ -231,244 +232,162 @@
     return err;
 }
 
-static void read_mapinfo(FILE *fp, stats_t* stats, bool* foundSwapPss)
-{
-    char line[1024];
-    int len, nameLen;
-    bool skip, done = false;
-
-    unsigned pss = 0, swappable_pss = 0, rss = 0;
-    float sharing_proportion = 0.0;
-    unsigned shared_clean = 0, shared_dirty = 0;
-    unsigned private_clean = 0, private_dirty = 0;
-    unsigned swapped_out = 0, swapped_out_pss = 0;
-    bool is_swappable = false;
-    unsigned temp;
-
-    uint64_t start;
-    uint64_t end = 0;
-    uint64_t prevEnd = 0;
-    char* name;
-    int name_pos;
-
-    int whichHeap = HEAP_UNKNOWN;
-    int subHeap = HEAP_UNKNOWN;
-    int prevHeap = HEAP_UNKNOWN;
-
-    *foundSwapPss = false;
-
-    if(fgets(line, sizeof(line), fp) == 0) return;
-
-    while (!done) {
-        prevHeap = whichHeap;
-        prevEnd = end;
-        whichHeap = HEAP_UNKNOWN;
-        subHeap = HEAP_UNKNOWN;
-        skip = false;
-        is_swappable = false;
-
-        len = strlen(line);
-        if (len < 1) return;
-        line[--len] = 0;
-
-        if (sscanf(line, "%" SCNx64 "-%" SCNx64 " %*s %*x %*x:%*x %*d%n", &start, &end, &name_pos) != 2) {
-            skip = true;
-        } else {
-            while (isspace(line[name_pos])) {
-                name_pos += 1;
-            }
-            name = line + name_pos;
-            nameLen = strlen(name);
-            // Trim the end of the line if it is " (deleted)".
-            const char* deleted_str = " (deleted)";
-            if (nameLen > (int)strlen(deleted_str) &&
-                strcmp(name+nameLen-strlen(deleted_str), deleted_str) == 0) {
-                nameLen -= strlen(deleted_str);
-                name[nameLen] = '\0';
-            }
-            if ((strstr(name, "[heap]") == name)) {
-                whichHeap = HEAP_NATIVE;
-            } else if (strncmp(name, "[anon:libc_malloc]", 18) == 0) {
-                whichHeap = HEAP_NATIVE;
-            } else if (strncmp(name, "[stack", 6) == 0) {
-                whichHeap = HEAP_STACK;
-            } else if (nameLen > 3 && strcmp(name+nameLen-3, ".so") == 0) {
-                whichHeap = HEAP_SO;
-                is_swappable = true;
-            } else if (nameLen > 4 && strcmp(name+nameLen-4, ".jar") == 0) {
-                whichHeap = HEAP_JAR;
-                is_swappable = true;
-            } else if (nameLen > 4 && strcmp(name+nameLen-4, ".apk") == 0) {
-                whichHeap = HEAP_APK;
-                is_swappable = true;
-            } else if (nameLen > 4 && strcmp(name+nameLen-4, ".ttf") == 0) {
-                whichHeap = HEAP_TTF;
-                is_swappable = true;
-            } else if ((nameLen > 4 && strstr(name, ".dex") != NULL) ||
-                       (nameLen > 5 && strcmp(name+nameLen-5, ".odex") == 0)) {
-                whichHeap = HEAP_DEX;
-                subHeap = HEAP_DEX_APP_DEX;
-                is_swappable = true;
-            } else if (nameLen > 5 && strcmp(name+nameLen-5, ".vdex") == 0) {
-                whichHeap = HEAP_DEX;
-                // Handle system@framework@boot* and system/framework/boot*
-                if (strstr(name, "@boot") != NULL || strstr(name, "/boot") != NULL) {
-                    subHeap = HEAP_DEX_BOOT_VDEX;
-                } else {
-                    subHeap = HEAP_DEX_APP_VDEX;
-                }
-                is_swappable = true;
-            } else if (nameLen > 4 && strcmp(name+nameLen-4, ".oat") == 0) {
-                whichHeap = HEAP_OAT;
-                is_swappable = true;
-            } else if (nameLen > 4 && strcmp(name+nameLen-4, ".art") == 0) {
-                whichHeap = HEAP_ART;
-                // Handle system@framework@boot* and system/framework/boot*
-                if (strstr(name, "@boot") != NULL || strstr(name, "/boot") != NULL) {
-                    subHeap = HEAP_ART_BOOT;
-                } else {
-                    subHeap = HEAP_ART_APP;
-                }
-                is_swappable = true;
-            } else if (strncmp(name, "/dev/", 5) == 0) {
-                whichHeap = HEAP_UNKNOWN_DEV;
-                if (strncmp(name, "/dev/kgsl-3d0", 13) == 0) {
-                    whichHeap = HEAP_GL_DEV;
-                } else if (strncmp(name, "/dev/ashmem/CursorWindow", 24) == 0) {
-                    whichHeap = HEAP_CURSOR;
-                } else if (strncmp(name, "/dev/ashmem", 11)) {
-                    whichHeap = HEAP_ASHMEM;
-                }
-            } else if (strncmp(name, "[anon:", 6) == 0) {
-                whichHeap = HEAP_UNKNOWN;
-                if (strncmp(name, "[anon:dalvik-", 13) == 0) {
-                    whichHeap = HEAP_DALVIK_OTHER;
-                    if (strstr(name, "[anon:dalvik-LinearAlloc") == name) {
-                        subHeap = HEAP_DALVIK_OTHER_LINEARALLOC;
-                    } else if ((strstr(name, "[anon:dalvik-alloc space") == name) ||
-                               (strstr(name, "[anon:dalvik-main space") == name)) {
-                        // This is the regular Dalvik heap.
-                        whichHeap = HEAP_DALVIK;
-                        subHeap = HEAP_DALVIK_NORMAL;
-                    } else if (strstr(name, "[anon:dalvik-large object space") == name ||
-                               strstr(name, "[anon:dalvik-free list large object space")
-                                   == name) {
-                        whichHeap = HEAP_DALVIK;
-                        subHeap = HEAP_DALVIK_LARGE;
-                    } else if (strstr(name, "[anon:dalvik-non moving space") == name) {
-                        whichHeap = HEAP_DALVIK;
-                        subHeap = HEAP_DALVIK_NON_MOVING;
-                    } else if (strstr(name, "[anon:dalvik-zygote space") == name) {
-                        whichHeap = HEAP_DALVIK;
-                        subHeap = HEAP_DALVIK_ZYGOTE;
-                    } else if (strstr(name, "[anon:dalvik-indirect ref") == name) {
-                        subHeap = HEAP_DALVIK_OTHER_INDIRECT_REFERENCE_TABLE;
-                    } else if (strstr(name, "[anon:dalvik-jit-code-cache") == name ||
-                               strstr(name, "[anon:dalvik-data-code-cache") == name) {
-                        subHeap = HEAP_DALVIK_OTHER_CODE_CACHE;
-                    } else if (strstr(name, "[anon:dalvik-CompilerMetadata") == name) {
-                        subHeap = HEAP_DALVIK_OTHER_COMPILER_METADATA;
-                    } else {
-                        subHeap = HEAP_DALVIK_OTHER_ACCOUNTING;  // Default to accounting.
-                    }
-                }
-            } else if (nameLen > 0) {
-                whichHeap = HEAP_UNKNOWN_MAP;
-            } else if (start == prevEnd && prevHeap == HEAP_SO) {
-                // bss section of a shared library.
-                whichHeap = HEAP_SO;
-            }
-        }
-
-        //ALOGI("native=%d dalvik=%d sqlite=%d: %s\n", isNativeHeap, isDalvikHeap,
-        //    isSqliteHeap, line);
-
-        shared_clean = 0;
-        shared_dirty = 0;
-        private_clean = 0;
-        private_dirty = 0;
-        swapped_out = 0;
-        swapped_out_pss = 0;
-
-        while (true) {
-            if (fgets(line, 1024, fp) == 0) {
-                done = true;
-                break;
-            }
-
-            if (line[0] == 'S' && sscanf(line, "Size: %d kB", &temp) == 1) {
-                /* size = temp; */
-            } else if (line[0] == 'R' && sscanf(line, "Rss: %d kB", &temp) == 1) {
-                rss = temp;
-            } else if (line[0] == 'P' && sscanf(line, "Pss: %d kB", &temp) == 1) {
-                pss = temp;
-            } else if (line[0] == 'S' && sscanf(line, "Shared_Clean: %d kB", &temp) == 1) {
-                shared_clean = temp;
-            } else if (line[0] == 'S' && sscanf(line, "Shared_Dirty: %d kB", &temp) == 1) {
-                shared_dirty = temp;
-            } else if (line[0] == 'P' && sscanf(line, "Private_Clean: %d kB", &temp) == 1) {
-                private_clean = temp;
-            } else if (line[0] == 'P' && sscanf(line, "Private_Dirty: %d kB", &temp) == 1) {
-                private_dirty = temp;
-            } else if (line[0] == 'R' && sscanf(line, "Referenced: %d kB", &temp) == 1) {
-                /* referenced = temp; */
-            } else if (line[0] == 'S' && sscanf(line, "Swap: %d kB", &temp) == 1) {
-                swapped_out = temp;
-            } else if (line[0] == 'S' && sscanf(line, "SwapPss: %d kB", &temp) == 1) {
-                *foundSwapPss = true;
-                swapped_out_pss = temp;
-            } else if (sscanf(line, "%" SCNx64 "-%" SCNx64 " %*s %*x %*x:%*x %*d", &start, &end) == 2) {
-                // looks like a new mapping
-                // example: "10000000-10001000 ---p 10000000 00:00 0"
-                break;
-            }
-        }
-
-        if (!skip) {
-            if (is_swappable && (pss > 0)) {
-                sharing_proportion = 0.0;
-                if ((shared_clean > 0) || (shared_dirty > 0)) {
-                    sharing_proportion = (pss - private_clean
-                            - private_dirty)/(shared_clean+shared_dirty);
-                }
-                swappable_pss = (sharing_proportion*shared_clean) + private_clean;
-            } else
-                swappable_pss = 0;
-
-            stats[whichHeap].pss += pss;
-            stats[whichHeap].swappablePss += swappable_pss;
-            stats[whichHeap].rss += rss;
-            stats[whichHeap].privateDirty += private_dirty;
-            stats[whichHeap].sharedDirty += shared_dirty;
-            stats[whichHeap].privateClean += private_clean;
-            stats[whichHeap].sharedClean += shared_clean;
-            stats[whichHeap].swappedOut += swapped_out;
-            stats[whichHeap].swappedOutPss += swapped_out_pss;
-            if (whichHeap == HEAP_DALVIK || whichHeap == HEAP_DALVIK_OTHER ||
-                    whichHeap == HEAP_DEX || whichHeap == HEAP_ART) {
-                stats[subHeap].pss += pss;
-                stats[subHeap].swappablePss += swappable_pss;
-                stats[subHeap].rss += rss;
-                stats[subHeap].privateDirty += private_dirty;
-                stats[subHeap].sharedDirty += shared_dirty;
-                stats[subHeap].privateClean += private_clean;
-                stats[subHeap].sharedClean += shared_clean;
-                stats[subHeap].swappedOut += swapped_out;
-                stats[subHeap].swappedOutPss += swapped_out_pss;
-            }
-        }
-    }
-}
-
 static void load_maps(int pid, stats_t* stats, bool* foundSwapPss)
 {
     *foundSwapPss = false;
+    uint64_t prev_end = 0;
+    int prev_heap = HEAP_UNKNOWN;
 
     std::string smaps_path = base::StringPrintf("/proc/%d/smaps", pid);
-    UniqueFile fp = MakeUniqueFile(smaps_path.c_str(), "re");
-    if (fp == nullptr) return;
+    auto vma_scan = [&](const meminfo::Vma& vma) {
+        int which_heap = HEAP_UNKNOWN;
+        int sub_heap = HEAP_UNKNOWN;
+        bool is_swappable = false;
+        std::string name;
+        if (base::EndsWith(vma.name, " (deleted)")) {
+            name = vma.name.substr(0, vma.name.size() - strlen(" (deleted)"));
+        } else {
+            name = vma.name;
+        }
 
-    read_mapinfo(fp.get(), stats, foundSwapPss);
+        uint32_t namesz = name.size();
+        if (base::StartsWith(name, "[heap]")) {
+            which_heap = HEAP_NATIVE;
+        } else if (base::StartsWith(name, "[anon:libc_malloc]")) {
+            which_heap = HEAP_NATIVE;
+        } else if (base::StartsWith(name, "[stack")) {
+            which_heap = HEAP_NATIVE;
+        } else if (base::EndsWith(name, ".so")) {
+            which_heap = HEAP_SO;
+            is_swappable = true;
+        } else if (base::EndsWith(name, ".jar")) {
+            which_heap = HEAP_JAR;
+            is_swappable = true;
+        } else if (base::EndsWith(name, ".apk")) {
+            which_heap = HEAP_APK;
+            is_swappable = true;
+        } else if (base::EndsWith(name, ".ttf")) {
+            which_heap = HEAP_TTF;
+            is_swappable = true;
+        } else if ((base::EndsWith(name, ".odex")) ||
+                (namesz > 4 && strstr(name.c_str(), ".dex") != nullptr)) {
+            which_heap = HEAP_DEX;
+            sub_heap = HEAP_DEX_APP_DEX;
+            is_swappable = true;
+        } else if (base::EndsWith(name, ".vdex")) {
+            which_heap = HEAP_DEX;
+            // Handle system@framework@boot and system/framework/boot
+            if ((strstr(name.c_str(), "@boot") != nullptr) ||
+                    (strstr(name.c_str(), "/boot"))) {
+                sub_heap = HEAP_DEX_BOOT_VDEX;
+            } else {
+                sub_heap = HEAP_DEX_APP_VDEX;
+            }
+            is_swappable = true;
+        } else if (base::EndsWith(name, ".oat")) {
+            which_heap = HEAP_OAT;
+            is_swappable = true;
+        } else if (base::EndsWith(name, ".art")) {
+            which_heap = HEAP_ART;
+            // Handle system@framework@boot* and system/framework/boot*
+            if ((strstr(name.c_str(), "@boot") != nullptr) ||
+                    (strstr(name.c_str(), "/boot"))) {
+                sub_heap = HEAP_DEX_BOOT_VDEX;
+            } else {
+                sub_heap = HEAP_DEX_APP_VDEX;
+            }
+            is_swappable = true;
+        } else if (base::StartsWith(name, "/dev/")) {
+            which_heap = HEAP_UNKNOWN_DEV;
+            if (base::StartsWith(name, "/dev/kgsl-3d0")) {
+                which_heap = HEAP_GL_DEV;
+            } else if (base::StartsWith(name, "/dev/ashmem/CursorWindow")) {
+                which_heap = HEAP_CURSOR;
+            } else if (base::StartsWith(name, "/dev/ashmem")) {
+                which_heap = HEAP_ASHMEM;
+            }
+        } else if (base::StartsWith(name, "[anon:")) {
+            which_heap = HEAP_UNKNOWN;
+            if (base::StartsWith(name, "[anon:dalvik-")) {
+                which_heap = HEAP_DALVIK_OTHER;
+                if (base::StartsWith(name, "[anon:dalvik-LinearAlloc")) {
+                    sub_heap = HEAP_DALVIK_OTHER_LINEARALLOC;
+                } else if (base::StartsWith(name, "[anon:dalvik-alloc space") ||
+                        base::StartsWith(name, "[anon:dalvik-main space")) {
+                    // This is the regular Dalvik heap.
+                    which_heap = HEAP_DALVIK;
+                    sub_heap = HEAP_DALVIK_NORMAL;
+                } else if (base::StartsWith(name,
+                            "[anon:dalvik-large object space") ||
+                        base::StartsWith(
+                            name, "[anon:dalvik-free list large object space")) {
+                    which_heap = HEAP_DALVIK;
+                    sub_heap = HEAP_DALVIK_LARGE;
+                } else if (base::StartsWith(name, "[anon:dalvik-non moving space")) {
+                    which_heap = HEAP_DALVIK;
+                    sub_heap = HEAP_DALVIK_NON_MOVING;
+                } else if (base::StartsWith(name, "[anon:dalvik-zygote space")) {
+                    which_heap = HEAP_DALVIK;
+                    sub_heap = HEAP_DALVIK_ZYGOTE;
+                } else if (base::StartsWith(name, "[anon:dalvik-indirect ref")) {
+                    sub_heap = HEAP_DALVIK_OTHER_INDIRECT_REFERENCE_TABLE;
+                } else if (base::StartsWith(name, "[anon:dalvik-jit-code-cache") ||
+                        base::StartsWith(name, "[anon:dalvik-data-code-cache")) {
+                    sub_heap = HEAP_DALVIK_OTHER_CODE_CACHE;
+                } else if (base::StartsWith(name, "[anon:dalvik-CompilerMetadata")) {
+                    sub_heap = HEAP_DALVIK_OTHER_COMPILER_METADATA;
+                } else {
+                    sub_heap = HEAP_DALVIK_OTHER_ACCOUNTING;  // Default to accounting.
+                }
+            }
+        } else if (namesz > 0) {
+            which_heap = HEAP_UNKNOWN_MAP;
+        } else if (vma.start == prev_end && prev_heap == HEAP_SO) {
+            // bss section of a shared library
+            which_heap = HEAP_SO;
+        }
+
+        prev_end = vma.end;
+        prev_heap = which_heap;
+
+        const meminfo::MemUsage& usage = vma.usage;
+        if (usage.swap_pss > 0 && *foundSwapPss != true) {
+            *foundSwapPss = true;
+        }
+
+        uint64_t swapable_pss = 0;
+        if (is_swappable && (usage.pss > 0)) {
+            float sharing_proportion = 0.0;
+            if ((usage.shared_clean > 0) || (usage.shared_dirty > 0)) {
+                sharing_proportion = (usage.pss - usage.uss) / (usage.shared_clean + usage.shared_dirty);
+            }
+            swapable_pss = (sharing_proportion * usage.shared_clean) + usage.private_clean;
+        }
+
+        stats[which_heap].pss += usage.pss;
+        stats[which_heap].swappablePss += swapable_pss;
+        stats[which_heap].rss += usage.rss;
+        stats[which_heap].privateDirty += usage.private_dirty;
+        stats[which_heap].sharedDirty += usage.shared_dirty;
+        stats[which_heap].privateClean += usage.private_clean;
+        stats[which_heap].sharedClean += usage.shared_clean;
+        stats[which_heap].swappedOut += usage.swap;
+        stats[which_heap].swappedOutPss += usage.swap_pss;
+        if (which_heap == HEAP_DALVIK || which_heap == HEAP_DALVIK_OTHER ||
+                which_heap == HEAP_DEX || which_heap == HEAP_ART) {
+            stats[sub_heap].pss += usage.pss;
+            stats[sub_heap].swappablePss += swapable_pss;
+            stats[sub_heap].rss += usage.rss;
+            stats[sub_heap].privateDirty += usage.private_dirty;
+            stats[sub_heap].sharedDirty += usage.shared_dirty;
+            stats[sub_heap].privateClean += usage.private_clean;
+            stats[sub_heap].sharedClean += usage.shared_clean;
+            stats[sub_heap].swappedOut += usage.swap;
+            stats[sub_heap].swappedOutPss += usage.swap_pss;
+        }
+    };
+
+    meminfo::ForEachVmaFromFile(smaps_path, vma_scan);
 }
 
 static void android_os_Debug_getDirtyPagesPid(JNIEnv *env, jobject clazz,
diff --git a/core/jni/android_view_SurfaceControl.cpp b/core/jni/android_view_SurfaceControl.cpp
index 897427f..f1b259e 100644
--- a/core/jni/android_view_SurfaceControl.cpp
+++ b/core/jni/android_view_SurfaceControl.cpp
@@ -124,13 +124,28 @@
 
 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(android_view_SurfaceSession_getClient(env, sessionObj));
+    sp<SurfaceComposerClient> client;
+    if (sessionObj != NULL) {
+        client = android_view_SurfaceSession_getClient(env, sessionObj);
+    } else {
+        client = SurfaceComposerClient::getDefault();
+    }
     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;
@@ -277,6 +292,21 @@
     transaction->setPosition(ctrl, x, y);
 }
 
+static void nativeSetGeometry(JNIEnv* env, jclass clazz, jlong transactionObj, jlong nativeObject,
+        jobject sourceObj, jobject dstObj, jlong orientation) {
+    auto transaction = reinterpret_cast<SurfaceComposerClient::Transaction*>(transactionObj);
+    SurfaceControl* const ctrl = reinterpret_cast<SurfaceControl *>(nativeObject);
+
+    Rect source, dst;
+    if (sourceObj != NULL) {
+        source = rectFromObj(env, sourceObj);
+    }
+    if (dstObj != NULL) {
+        dst = rectFromObj(env, dstObj);
+    }
+    transaction->setGeometry(ctrl, source, dst, orientation);
+}
+
 static void nativeSetGeometryAppliesWithResize(JNIEnv* env, jclass clazz,
 jlong transactionObj,
         jlong nativeObject) {
@@ -340,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();
 
@@ -357,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);
@@ -868,13 +920,13 @@
 
 static void nativeReparent(JNIEnv* env, jclass clazz, jlong transactionObj,
         jlong nativeObject,
-        jobject newParentObject) {
+        jlong newParentObject) {
     auto ctrl = reinterpret_cast<SurfaceControl *>(nativeObject);
-    sp<IBinder> parentHandle = ibinderForJavaObject(env, newParentObject);
+    auto newParent = reinterpret_cast<SurfaceControl *>(newParentObject);
 
     {
         auto transaction = reinterpret_cast<SurfaceComposerClient::Transaction*>(transactionObj);
-        transaction->reparent(ctrl, parentHandle);
+        transaction->reparent(ctrl, newParent != NULL ? newParent->getHandle() : NULL);
     }
 }
 
@@ -961,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 },
@@ -1063,7 +1115,7 @@
             (void*)nativeDeferTransactionUntilSurface },
     {"nativeReparentChildren", "(JJLandroid/os/IBinder;)V",
             (void*)nativeReparentChildren } ,
-    {"nativeReparent", "(JJLandroid/os/IBinder;)V",
+    {"nativeReparent", "(JJJ)V",
             (void*)nativeReparent },
     {"nativeSeverChildren", "(JJ)V",
             (void*)nativeSeverChildren } ,
@@ -1079,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 },
@@ -1087,6 +1141,8 @@
     {"nativeGetDisplayedContentSample",
             "(Landroid/os/IBinder;JJ)Landroid/hardware/display/DisplayedContentSample;",
             (void*)nativeGetDisplayedContentSample },
+    {"nativeSetGeometry", "(JJLandroid/graphics/Rect;Landroid/graphics/Rect;J)V",
+            (void*)nativeSetGeometry }
 };
 
 int register_android_view_SurfaceControl(JNIEnv* env)
diff --git a/core/jni/android_view_ThreadedRenderer.cpp b/core/jni/android_view_ThreadedRenderer.cpp
index 5a8ab3c..4052919 100644
--- a/core/jni/android_view_ThreadedRenderer.cpp
+++ b/core/jni/android_view_ThreadedRenderer.cpp
@@ -48,6 +48,7 @@
 #include <FrameInfo.h>
 #include <FrameMetricsObserver.h>
 #include <IContextFactory.h>
+#include <Picture.h>
 #include <Properties.h>
 #include <PropertyValuesAnimatorSet.h>
 #include <RenderNode.h>
@@ -71,6 +72,11 @@
 } gFrameMetricsObserverClassInfo;
 
 struct {
+    jclass clazz;
+    jmethodID invokePictureCapturedCallback;
+} gHardwareRenderer;
+
+struct {
     jmethodID onFrameDraw;
 } gFrameDrawingCallback;
 
@@ -905,6 +911,27 @@
     jobject mObject;
 };
 
+static void android_view_ThreadedRenderer_setPictureCapturedCallbackJNI(JNIEnv* env,
+        jobject clazz, jlong proxyPtr, jobject pictureCallback) {
+    RenderProxy* proxy = reinterpret_cast<RenderProxy*>(proxyPtr);
+    if (!pictureCallback) {
+        proxy->setPictureCapturedCallback(nullptr);
+    } else {
+        JavaVM* vm = nullptr;
+        LOG_ALWAYS_FATAL_IF(env->GetJavaVM(&vm) != JNI_OK, "Unable to get Java VM");
+        auto globalCallbackRef = std::make_shared<JGlobalRefHolder>(vm,
+                env->NewGlobalRef(pictureCallback));
+        proxy->setPictureCapturedCallback([globalCallbackRef](sk_sp<SkPicture>&& picture) {
+            JNIEnv* env = getenv(globalCallbackRef->vm());
+            Picture* wrapper = new Picture{std::move(picture)};
+            env->CallStaticVoidMethod(gHardwareRenderer.clazz,
+                    gHardwareRenderer.invokePictureCapturedCallback,
+                    static_cast<jlong>(reinterpret_cast<intptr_t>(wrapper)),
+                    globalCallbackRef->object());
+        });
+    }
+}
+
 static void android_view_ThreadedRenderer_setFrameCallback(JNIEnv* env,
         jobject clazz, jlong proxyPtr, jobject frameCallback) {
     RenderProxy* proxy = reinterpret_cast<RenderProxy*>(proxyPtr);
@@ -1011,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);
 }
@@ -1145,6 +1173,8 @@
     { "nRemoveRenderNode", "(JJ)V", (void*) android_view_ThreadedRenderer_removeRenderNode},
     { "nDrawRenderNode", "(JJ)V", (void*) android_view_ThreadedRendererd_drawRenderNode},
     { "nSetContentDrawBounds", "(JIIII)V", (void*)android_view_ThreadedRenderer_setContentDrawBounds},
+    { "nSetPictureCaptureCallback", "(JLandroid/graphics/HardwareRenderer$PictureCapturedCallback;)V",
+            (void*) android_view_ThreadedRenderer_setPictureCapturedCallbackJNI },
     { "nSetFrameCallback", "(JLandroid/graphics/HardwareRenderer$FrameDrawingCallback;)V",
             (void*)android_view_ThreadedRenderer_setFrameCallback},
     { "nSetFrameCompleteCallback", "(JLandroid/graphics/HardwareRenderer$FrameCompleteCallback;)V",
@@ -1198,6 +1228,13 @@
     gFrameMetricsObserverClassInfo.timingDataBuffer = GetFieldIDOrDie(
             env, metricsClass, "mTimingData", "[J");
 
+    jclass hardwareRenderer = FindClassOrDie(env,
+            "android/graphics/HardwareRenderer");
+    gHardwareRenderer.clazz = reinterpret_cast<jclass>(env->NewGlobalRef(hardwareRenderer));
+    gHardwareRenderer.invokePictureCapturedCallback = GetStaticMethodIDOrDie(env, hardwareRenderer,
+            "invokePictureCapturedCallback",
+            "(JLandroid/graphics/HardwareRenderer$PictureCapturedCallback;)V");
+
     jclass frameCallbackClass = FindClassOrDie(env,
             "android/graphics/HardwareRenderer$FrameDrawingCallback");
     gFrameDrawingCallback.onFrameDraw = GetMethodIDOrDie(env, frameCallbackClass,
diff --git a/core/jni/com_android_internal_os_Zygote.cpp b/core/jni/com_android_internal_os_Zygote.cpp
index e81b627..6ee9606 100644
--- a/core/jni/com_android_internal_os_Zygote.cpp
+++ b/core/jni/com_android_internal_os_Zygote.cpp
@@ -29,13 +29,17 @@
 #include <sys/mount.h>
 #include <linux/fs.h>
 
+#include <array>
+#include <atomic>
 #include <functional>
 #include <list>
 #include <optional>
 #include <sstream>
 #include <string>
+#include <string_view>
 
 #include <android/fdsan.h>
+#include <arpa/inet.h>
 #include <fcntl.h>
 #include <grp.h>
 #include <inttypes.h>
@@ -46,9 +50,11 @@
 #include <stdlib.h>
 #include <sys/capability.h>
 #include <sys/cdefs.h>
+#include <sys/eventfd.h>
 #include <sys/personality.h>
 #include <sys/prctl.h>
 #include <sys/resource.h>
+#include <sys/socket.h>
 #include <sys/stat.h>
 #include <sys/time.h>
 #include <sys/types.h>
@@ -56,10 +62,11 @@
 #include <sys/wait.h>
 #include <unistd.h>
 
-#include "android-base/logging.h"
+#include <android-base/logging.h>
 #include <android-base/properties.h>
 #include <android-base/file.h>
 #include <android-base/stringprintf.h>
+#include <android-base/unique_fd.h>
 #include <cutils/fs.h>
 #include <cutils/multiuser.h>
 #include <private/android_filesystem_config.h>
@@ -81,6 +88,9 @@
 
 namespace {
 
+// TODO (chriswailes): Add a function to initialize native Zygote data.
+// TODO (chriswailes): Fix mixed indentation style (2 and 4 spaces).
+
 using namespace std::placeholders;
 
 using android::String8;
@@ -92,6 +102,9 @@
 #define CREATE_ERROR(...) StringPrintf("%s:%d: ", __FILE__, __LINE__). \
                               append(StringPrintf(__VA_ARGS__))
 
+// This type is duplicated in fd_utils.h
+typedef const std::function<void(std::string)>& fail_fn_t;
+
 static pid_t gSystemServerPid = 0;
 
 static const char kIsolatedStorage[] = "persist.sys.isolated_storage";
@@ -103,6 +116,152 @@
 
 static bool g_is_security_enforced = true;
 
+/**
+ * The maximum number of characters (not including a null terminator) that a
+ * process name may contain.
+ */
+static constexpr size_t MAX_NAME_LENGTH = 15;
+
+/**
+ * The prefix string for environmental variables storing socket FDs created by
+ * init.
+ */
+
+static constexpr std::string_view ANDROID_SOCKET_PREFIX("ANDROID_SOCKET_");
+
+/**
+ * The file descriptor for the Zygote socket opened by init.
+ */
+
+static int gZygoteSocketFD = -1;
+
+/**
+ * The file descriptor for the Blastula pool socket opened by init.
+ */
+
+static int gBlastulaPoolSocketFD = -1;
+
+/**
+ * The number of Blastulas currently in this Zygote's pool.
+ */
+static std::atomic_uint32_t gBlastulaPoolCount = 0;
+
+/**
+ * Event file descriptor used to communicate reaped blastulas to the
+ * ZygoteServer.
+ */
+static int gBlastulaPoolEventFD = -1;
+
+/**
+ * The maximum value that the gBlastulaPoolMax variable may take.  This value
+ * is a mirror of Zygote.BLASTULA_POOL_MAX_LIMIT
+ */
+static constexpr int BLASTULA_POOL_MAX_LIMIT = 10;
+
+/**
+ * A helper class containing accounting information for Blastulas.
+ */
+class BlastulaTableEntry {
+ public:
+  struct EntryStorage {
+    int32_t pid;
+    int32_t read_pipe_fd;
+
+    bool operator!=(const EntryStorage& other) {
+      return pid != other.pid || read_pipe_fd != other.read_pipe_fd;
+    }
+  };
+
+ private:
+  static constexpr EntryStorage INVALID_ENTRY_VALUE = {-1, -1};
+
+  std::atomic<EntryStorage> mStorage;
+  static_assert(decltype(mStorage)::is_always_lock_free);
+
+ public:
+  constexpr BlastulaTableEntry() : mStorage(INVALID_ENTRY_VALUE) {}
+
+  /**
+   * If the provided PID matches the one stored in this entry, the entry will
+   * be invalidated and the associated file descriptor will be closed.  If the
+   * PIDs don't match nothing will happen.
+   *
+   * @param pid The ID of the process who's entry we want to clear.
+   * @return True if the entry was cleared; false otherwise
+   */
+  bool ClearForPID(int32_t pid) {
+    EntryStorage storage = mStorage.load();
+
+    if (storage.pid == pid) {
+      /*
+       * There are three possible outcomes from this compare-and-exchange:
+       *   1) It succeeds, in which case we close the FD
+       *   2) It fails and the new value is INVALID_ENTRY_VALUE, in which case
+       *      the entry has already been cleared.
+       *   3) It fails and the new value isn't INVALID_ENTRY_VALUE, in which
+       *      case the entry has already been cleared and re-used.
+       *
+       * In all three cases the goal of the caller has been met and we can
+       * return true.
+       */
+      if (mStorage.compare_exchange_strong(storage, INVALID_ENTRY_VALUE)) {
+        close(storage.read_pipe_fd);
+      }
+
+      return true;
+    } else {
+      return false;
+    }
+  }
+
+  /**
+   * @return A copy of the data stored in this entry.
+   */
+  std::optional<EntryStorage> GetValues() {
+    EntryStorage storage = mStorage.load();
+
+    if (storage != INVALID_ENTRY_VALUE) {
+      return storage;
+    } else {
+      return std::nullopt;
+    }
+  }
+
+  /**
+   * Sets the entry to the given values if it is currently invalid.
+   *
+   * @param pid  The process ID for the new entry.
+   * @param read_pipe_fd  The read end of the blastula control pipe for this
+   * process.
+   * @return True if the entry was set; false otherwise.
+   */
+  bool SetIfInvalid(int32_t pid, int32_t read_pipe_fd) {
+    EntryStorage new_value_storage;
+
+    new_value_storage.pid = pid;
+    new_value_storage.read_pipe_fd = read_pipe_fd;
+
+    EntryStorage expected = INVALID_ENTRY_VALUE;
+
+    return mStorage.compare_exchange_strong(expected, new_value_storage);
+  }
+};
+
+/**
+ * A table containing information about the Blastulas currently in the pool.
+ *
+ * Multiple threads may be attempting to modify the table, either from the
+ * signal handler or from the ZygoteServer poll loop.  Atomic loads/stores in
+ * the BlastulaTableEntry class prevent data races during these concurrent
+ * operations.
+ */
+static std::array<BlastulaTableEntry, BLASTULA_POOL_MAX_LIMIT> gBlastulaTable;
+
+/**
+ * The list of open zygote file descriptors.
+ */
+static FileDescriptorTable* gOpenFdTable = nullptr;
+
 // Must match values in com.android.internal.os.Zygote.
 enum MountExternalKind {
   MOUNT_EXTERNAL_NONE = 0,
@@ -119,6 +278,9 @@
   DEBUG_ENABLE_JDWP = 1,
 };
 
+// Forward declaration so we don't have to move the signal handler.
+static bool RemoveBlastulaTableEntry(pid_t blastula_pid);
+
 static void RuntimeAbort(JNIEnv* env, int line, const char* msg) {
   std::ostringstream oss;
   oss << __FILE__ << ":" << line << ": " << msg;
@@ -129,6 +291,7 @@
 static void SigChldHandler(int /*signal_number*/) {
   pid_t pid;
   int status;
+  int64_t blastulas_removed = 0;
 
   // It's necessary to save and restore the errno during this function.
   // Since errno is stored per thread, changing it here modifies the errno
@@ -162,6 +325,11 @@
       ALOGE("Exit zygote because system server (%d) has terminated", pid);
       kill(getpid(), SIGKILL);
     }
+
+    // Check to see if the PID is in the blastula pool and remove it if it is.
+    if (RemoveBlastulaTableEntry(pid)) {
+      ++blastulas_removed;
+    }
   }
 
   // Note that we shouldn't consider ECHILD an error because
@@ -170,6 +338,15 @@
     ALOGW("Zygote SIGCHLD error in waitpid: %s", strerror(errno));
   }
 
+  if (blastulas_removed > 0) {
+    if (write(gBlastulaPoolEventFD, &blastulas_removed, sizeof(blastulas_removed)) == -1) {
+      // If this write fails something went terribly wrong.  We will now kill
+      // the zygote and let the system bring it back up.
+      ALOGE("Zygote failed to write to blastula pool event FD: %s", strerror(errno));
+      kill(getpid(), SIGKILL);
+    }
+  }
+
   errno = saved_errno;
 }
 
@@ -194,13 +371,13 @@
   struct sigaction sig_chld = {};
   sig_chld.sa_handler = SigChldHandler;
 
-  if (sigaction(SIGCHLD, &sig_chld, NULL) < 0) {
+  if (sigaction(SIGCHLD, &sig_chld, nullptr) < 0) {
     ALOGW("Error setting SIGCHLD handler: %s", strerror(errno));
   }
 
   struct sigaction sig_hup = {};
   sig_hup.sa_handler = SIG_IGN;
-  if (sigaction(SIGHUP, &sig_hup, NULL) < 0) {
+  if (sigaction(SIGHUP, &sig_hup, nullptr) < 0) {
     ALOGW("Error setting SIGHUP handler: %s", strerror(errno));
   }
 }
@@ -211,64 +388,57 @@
   memset(&sa, 0, sizeof(sa));
   sa.sa_handler = SIG_DFL;
 
-  if (sigaction(SIGCHLD, &sa, NULL) < 0) {
+  if (sigaction(SIGCHLD, &sa, nullptr) < 0) {
     ALOGW("Error unsetting SIGCHLD handler: %s", strerror(errno));
   }
 }
 
 // Calls POSIX setgroups() using the int[] object as an argument.
-// A NULL argument is tolerated.
-static bool SetGids(JNIEnv* env, jintArray javaGids, std::string* error_msg) {
-  if (javaGids == NULL) {
-    return true;
+// A nullptr argument is tolerated.
+static void SetGids(JNIEnv* env, jintArray managed_gids, fail_fn_t fail_fn) {
+  if (managed_gids == nullptr) {
+    return;
   }
 
-  ScopedIntArrayRO gids(env, javaGids);
-  if (gids.get() == NULL) {
-    *error_msg = CREATE_ERROR("Getting gids int array failed");
-    return false;
-  }
-  int rc = setgroups(gids.size(), reinterpret_cast<const gid_t*>(&gids[0]));
-  if (rc == -1) {
-    *error_msg = CREATE_ERROR("setgroups failed: %s, gids.size=%zu", strerror(errno), gids.size());
-    return false;
+  ScopedIntArrayRO gids(env, managed_gids);
+  if (gids.get() == nullptr) {
+    fail_fn(CREATE_ERROR("Getting gids int array failed"));
   }
 
-  return true;
+  if (setgroups(gids.size(), reinterpret_cast<const gid_t*>(&gids[0])) == -1) {
+    fail_fn(CREATE_ERROR("setgroups failed: %s, gids.size=%zu", strerror(errno), gids.size()));
+  }
 }
 
 // Sets the resource limits via setrlimit(2) for the values in the
 // two-dimensional array of integers that's passed in. The second dimension
-// contains a tuple of length 3: (resource, rlim_cur, rlim_max). NULL is
+// contains a tuple of length 3: (resource, rlim_cur, rlim_max). nullptr is
 // treated as an empty array.
-static bool SetRLimits(JNIEnv* env, jobjectArray javaRlimits, std::string* error_msg) {
-  if (javaRlimits == NULL) {
-    return true;
+static void SetRLimits(JNIEnv* env, jobjectArray managed_rlimits, fail_fn_t fail_fn) {
+  if (managed_rlimits == nullptr) {
+    return;
   }
 
   rlimit rlim;
   memset(&rlim, 0, sizeof(rlim));
 
-  for (int i = 0; i < env->GetArrayLength(javaRlimits); ++i) {
-    ScopedLocalRef<jobject> javaRlimitObject(env, env->GetObjectArrayElement(javaRlimits, i));
-    ScopedIntArrayRO javaRlimit(env, reinterpret_cast<jintArray>(javaRlimitObject.get()));
-    if (javaRlimit.size() != 3) {
-      *error_msg = CREATE_ERROR("rlimits array must have a second dimension of size 3");
-      return false;
+  for (int i = 0; i < env->GetArrayLength(managed_rlimits); ++i) {
+    ScopedLocalRef<jobject>
+        managed_rlimit_object(env, env->GetObjectArrayElement(managed_rlimits, i));
+    ScopedIntArrayRO rlimit_handle(env, reinterpret_cast<jintArray>(managed_rlimit_object.get()));
+
+    if (rlimit_handle.size() != 3) {
+      fail_fn(CREATE_ERROR("rlimits array must have a second dimension of size 3"));
     }
 
-    rlim.rlim_cur = javaRlimit[1];
-    rlim.rlim_max = javaRlimit[2];
+    rlim.rlim_cur = rlimit_handle[1];
+    rlim.rlim_max = rlimit_handle[2];
 
-    int rc = setrlimit(javaRlimit[0], &rlim);
-    if (rc == -1) {
-      *error_msg = CREATE_ERROR("setrlimit(%d, {%ld, %ld}) failed", javaRlimit[0], rlim.rlim_cur,
-            rlim.rlim_max);
-      return false;
+    if (setrlimit(rlimit_handle[0], &rlim) == -1) {
+      fail_fn(CREATE_ERROR("setrlimit(%d, {%ld, %ld}) failed",
+                           rlimit_handle[0], rlim.rlim_cur, rlim.rlim_max));
     }
   }
-
-  return true;
 }
 
 static void EnableDebugger() {
@@ -323,10 +493,7 @@
   // Apply system or app filter based on uid.
   if (uid >= AID_APP_START) {
     if (is_child_zygote) {
-      // set_app_zygote_seccomp_filter();
-      // TODO(b/111434506) install the filter; for now, install the app filter
-      // which is more restrictive.
-      set_app_seccomp_filter();
+      set_app_zygote_seccomp_filter();
     } else {
       set_app_seccomp_filter();
     }
@@ -335,32 +502,26 @@
   }
 }
 
-static bool EnableKeepCapabilities(std::string* error_msg) {
-  int rc = prctl(PR_SET_KEEPCAPS, 1, 0, 0, 0);
-  if (rc == -1) {
-    *error_msg = CREATE_ERROR("prctl(PR_SET_KEEPCAPS) failed: %s", strerror(errno));
-    return false;
+static void EnableKeepCapabilities(fail_fn_t fail_fn) {
+  if (prctl(PR_SET_KEEPCAPS, 1, 0, 0, 0) == -1) {
+    fail_fn(CREATE_ERROR("prctl(PR_SET_KEEPCAPS) failed: %s", strerror(errno)));
   }
-  return true;
 }
 
-static bool DropCapabilitiesBoundingSet(std::string* error_msg) {
-  for (int i = 0; prctl(PR_CAPBSET_READ, i, 0, 0, 0) >= 0; i++) {
-    int rc = prctl(PR_CAPBSET_DROP, i, 0, 0, 0);
-    if (rc == -1) {
+static void DropCapabilitiesBoundingSet(fail_fn_t fail_fn) {
+  for (int i = 0; prctl(PR_CAPBSET_READ, i, 0, 0, 0) >= 0; i++) {;
+    if (prctl(PR_CAPBSET_DROP, i, 0, 0, 0) == -1) {
       if (errno == EINVAL) {
         ALOGE("prctl(PR_CAPBSET_DROP) failed with EINVAL. Please verify "
               "your kernel is compiled with file capabilities support");
       } else {
-        *error_msg = CREATE_ERROR("prctl(PR_CAPBSET_DROP, %d) failed: %s", i, strerror(errno));
-        return false;
+        fail_fn(CREATE_ERROR("prctl(PR_CAPBSET_DROP, %d) failed: %s", i, strerror(errno)));
       }
     }
   }
-  return true;
 }
 
-static bool SetInheritable(uint64_t inheritable, std::string* error_msg) {
+static void SetInheritable(uint64_t inheritable, fail_fn_t fail_fn) {
   __user_cap_header_struct capheader;
   memset(&capheader, 0, sizeof(capheader));
   capheader.version = _LINUX_CAPABILITY_VERSION_3;
@@ -368,23 +529,19 @@
 
   __user_cap_data_struct capdata[2];
   if (capget(&capheader, &capdata[0]) == -1) {
-    *error_msg = CREATE_ERROR("capget failed: %s", strerror(errno));
-    return false;
+    fail_fn(CREATE_ERROR("capget failed: %s", strerror(errno)));
   }
 
   capdata[0].inheritable = inheritable;
   capdata[1].inheritable = inheritable >> 32;
 
   if (capset(&capheader, &capdata[0]) == -1) {
-    *error_msg = CREATE_ERROR("capset(inh=%" PRIx64 ") failed: %s", inheritable, strerror(errno));
-    return false;
+    fail_fn(CREATE_ERROR("capset(inh=%" PRIx64 ") failed: %s", inheritable, strerror(errno)));
   }
-
-  return true;
 }
 
-static bool SetCapabilities(uint64_t permitted, uint64_t effective, uint64_t inheritable,
-                            std::string* error_msg) {
+static void SetCapabilities(uint64_t permitted, uint64_t effective, uint64_t inheritable,
+                            fail_fn_t fail_fn) {
   __user_cap_header_struct capheader;
   memset(&capheader, 0, sizeof(capheader));
   capheader.version = _LINUX_CAPABILITY_VERSION_3;
@@ -400,27 +557,23 @@
   capdata[1].inheritable = inheritable >> 32;
 
   if (capset(&capheader, &capdata[0]) == -1) {
-    *error_msg = CREATE_ERROR("capset(perm=%" PRIx64 ", eff=%" PRIx64 ", inh=%" PRIx64 ") "
-                              "failed: %s", permitted, effective, inheritable, strerror(errno));
-    return false;
+    fail_fn(CREATE_ERROR("capset(perm=%" PRIx64 ", eff=%" PRIx64 ", inh=%" PRIx64 ") "
+                         "failed: %s", permitted, effective, inheritable, strerror(errno)));
   }
-  return true;
 }
 
-static bool SetSchedulerPolicy(std::string* error_msg) {
+static void SetSchedulerPolicy(fail_fn_t fail_fn) {
   errno = -set_sched_policy(0, SP_DEFAULT);
   if (errno != 0) {
-    *error_msg = CREATE_ERROR("set_sched_policy(0, SP_DEFAULT) failed: %s", strerror(errno));
-    return false;
+    fail_fn(CREATE_ERROR("set_sched_policy(0, SP_DEFAULT) failed: %s", strerror(errno)));
   }
-  return true;
 }
 
 static int UnmountTree(const char* path) {
     size_t path_len = strlen(path);
 
     FILE* fp = setmntent("/proc/mounts", "r");
-    if (fp == NULL) {
+    if (fp == nullptr) {
         ALOGE("Error opening /proc/mounts: %s", strerror(errno));
         return -errno;
     }
@@ -429,7 +582,7 @@
     // reverse order to give us the best chance of success.
     std::list<std::string> toUnmount;
     mntent* mentry;
-    while ((mentry = getmntent(fp)) != NULL) {
+    while ((mentry = getmntent(fp)) != nullptr) {
         if (strncmp(mentry->mnt_dir, path, path_len) == 0) {
             toUnmount.push_front(std::string(mentry->mnt_dir));
         }
@@ -444,56 +597,55 @@
     return 0;
 }
 
-static bool createPkgSandbox(uid_t uid, const std::string& package_name, std::string* error_msg) {
+static void CreatePkgSandbox(uid_t uid, const std::string& package_name, fail_fn_t fail_fn) {
     // Create /mnt/user/0/package/<package-name>
     userid_t user_id = multiuser_get_user_id(uid);
     std::string pkg_sandbox_dir = StringPrintf("/mnt/user/%d", user_id);
     if (fs_prepare_dir(pkg_sandbox_dir.c_str(), 0751, AID_ROOT, AID_ROOT) != 0) {
-        *error_msg = CREATE_ERROR("fs_prepare_dir failed on %s", pkg_sandbox_dir.c_str());
-        return false;
+        fail_fn(CREATE_ERROR("fs_prepare_dir failed on %s", pkg_sandbox_dir.c_str()));
     }
+
     StringAppendF(&pkg_sandbox_dir, "/package");
     if (fs_prepare_dir(pkg_sandbox_dir.c_str(), 0700, AID_ROOT, AID_ROOT) != 0) {
-        *error_msg = CREATE_ERROR("fs_prepare_dir failed on %s", pkg_sandbox_dir.c_str());
-        return false;
+        fail_fn(CREATE_ERROR("fs_prepare_dir failed on %s", pkg_sandbox_dir.c_str()));
     }
+
     StringAppendF(&pkg_sandbox_dir, "/%s", package_name.c_str());
     if (fs_prepare_dir(pkg_sandbox_dir.c_str(), 0755, uid, uid) != 0) {
-        *error_msg = CREATE_ERROR("fs_prepare_dir failed on %s", pkg_sandbox_dir.c_str());
-        return false;
+        fail_fn(CREATE_ERROR("fs_prepare_dir failed on %s", pkg_sandbox_dir.c_str()));
     }
-    return true;
 }
 
-static bool bindMount(const std::string& sourceDir, const std::string& targetDir,
-        std::string* error_msg) {
-    if (TEMP_FAILURE_RETRY(mount(sourceDir.c_str(), targetDir.c_str(),
-            nullptr, MS_BIND | MS_REC, nullptr)) == -1) {
-        *error_msg = CREATE_ERROR("Failed to mount %s to %s: %s",
-                sourceDir.c_str(), targetDir.c_str(), strerror(errno));
-        return false;
+static void BindMount(const std::string& sourceDir, const std::string& targetDir,
+                      fail_fn_t fail_fn) {
+    if (TEMP_FAILURE_RETRY(mount(sourceDir.c_str(), targetDir.c_str(), nullptr,
+                                 MS_BIND | MS_REC, nullptr)) == -1) {
+        fail_fn(CREATE_ERROR("Failed to mount %s to %s: %s",
+                             sourceDir.c_str(), targetDir.c_str(), strerror(errno)));
     }
-    if (TEMP_FAILURE_RETRY(mount(nullptr, targetDir.c_str(),
-            nullptr, MS_SLAVE | MS_REC, nullptr)) == -1) {
-        *error_msg = CREATE_ERROR("Failed to set MS_SLAVE for %s", targetDir.c_str());
-        return false;
+
+    if (TEMP_FAILURE_RETRY(mount(nullptr, targetDir.c_str(), nullptr,
+                                 MS_SLAVE | MS_REC, nullptr)) == -1) {
+        fail_fn(CREATE_ERROR("Failed to set MS_SLAVE for %s", targetDir.c_str()));
     }
-    return true;
 }
 
-static bool mountPkgSpecificDir(const std::string& mntSourceRoot,
-        const std::string& mntTargetRoot, const std::string& packageName,
-        const char* dirName, std::string* error_msg) {
+static void MountPkgSpecificDir(const std::string& mntSourceRoot,
+                                const std::string& mntTargetRoot,
+                                const std::string& packageName,
+                                const char* dirName,
+                                fail_fn_t fail_fn) {
     std::string mntSourceDir = StringPrintf("%s/Android/%s/%s",
             mntSourceRoot.c_str(), dirName, packageName.c_str());
     std::string mntTargetDir = StringPrintf("%s/Android/%s/%s",
             mntTargetRoot.c_str(), dirName, packageName.c_str());
-    return bindMount(mntSourceDir, mntTargetDir, error_msg);
+
+    BindMount(mntSourceDir, mntTargetDir, fail_fn);
 }
 
-static bool preparePkgSpecificDirs(const std::vector<std::string>& packageNames,
-        const std::vector<std::string>& volumeLabels, bool mountAllObbs,
-        userid_t userId, std::string* error_msg) {
+static void PreparePkgSpecificDirs(const std::vector<std::string>& packageNames,
+                                   const std::vector<std::string>& volumeLabels,
+                                   bool mountAllObbs, userid_t userId, fail_fn_t fail_fn) {
     for (auto& label : volumeLabels) {
         std::string mntSource = StringPrintf("/mnt/runtime/write/%s", label.c_str());
         std::string mntTarget = StringPrintf("/storage/%s", label.c_str());
@@ -501,28 +653,29 @@
             StringAppendF(&mntSource, "/%d", userId);
             StringAppendF(&mntTarget, "/%d", userId);
         }
+
         for (auto& package : packageNames) {
-            mountPkgSpecificDir(mntSource, mntTarget, package, "data", error_msg);
-            mountPkgSpecificDir(mntSource, mntTarget, package, "media", error_msg);
+            MountPkgSpecificDir(mntSource, mntTarget, package, "data", fail_fn);
+            MountPkgSpecificDir(mntSource, mntTarget, package, "media", fail_fn);
             if (!mountAllObbs) {
-                mountPkgSpecificDir(mntSource, mntTarget, package, "obb", error_msg);
+                MountPkgSpecificDir(mntSource, mntTarget, package, "obb", fail_fn);
             }
         }
+
         if (mountAllObbs) {
             StringAppendF(&mntSource, "/Android/obb");
             StringAppendF(&mntTarget, "/Android/obb");
-            bindMount(mntSource, mntTarget, error_msg);
+            BindMount(mntSource, mntTarget, fail_fn);
         }
     }
-    return true;
 }
 
 // Create a private mount namespace and bind mount appropriate emulated
 // storage for the given user.
-static bool MountEmulatedStorage(uid_t uid, jint mount_mode,
-        bool force_mount_namespace, std::string* error_msg, const std::string& package_name,
+static void MountEmulatedStorage(uid_t uid, jint mount_mode,
+        bool force_mount_namespace, const std::string& package_name,
         const std::vector<std::string>& packages_for_uid,
-        const std::vector<std::string>& visible_vol_ids) {
+        const std::vector<std::string>& visible_vol_ids, fail_fn_t fail_fn) {
     // See storage config details at http://source.android.com/tech/storage/
 
     String8 storageSource;
@@ -534,18 +687,17 @@
         storageSource = "/mnt/runtime/write";
     } else if (mount_mode == MOUNT_EXTERNAL_NONE && !force_mount_namespace) {
         // Sane default of no storage visible
-        return true;
+        return;
     }
 
     // Create a second private mount namespace for our process
     if (unshare(CLONE_NEWNS) == -1) {
-        *error_msg = CREATE_ERROR("Failed to unshare(): %s", strerror(errno));
-        return false;
+        fail_fn(CREATE_ERROR("Failed to unshare(): %s", strerror(errno)));
     }
 
     // Handle force_mount_namespace with MOUNT_EXTERNAL_NONE.
     if (mount_mode == MOUNT_EXTERNAL_NONE) {
-        return true;
+        return;
     }
 
     if (GetBoolProperty(kIsolatedStorageSnapshot, GetBoolProperty(kIsolatedStorage, false))) {
@@ -553,66 +705,64 @@
             storageSource = (mount_mode == MOUNT_EXTERNAL_FULL)
                     ? "/mnt/runtime/full" : "/mnt/runtime/write";
             if (TEMP_FAILURE_RETRY(mount(storageSource.string(), "/storage",
-                    NULL, MS_BIND | MS_REC | MS_SLAVE, NULL)) == -1) {
-                *error_msg = CREATE_ERROR("Failed to mount %s to /storage: %s",
-                                          storageSource.string(),
-                                          strerror(errno));
-                return false;
+                                         NULL, MS_BIND | MS_REC | MS_SLAVE, NULL)) == -1) {
+                fail_fn(CREATE_ERROR("Failed to mount %s to /storage: %s",
+                                     storageSource.string(),
+                                     strerror(errno)));
             }
 
             // Mount user-specific symlink helper into place
             userid_t user_id = multiuser_get_user_id(uid);
             const String8 userSource(String8::format("/mnt/user/%d", user_id));
             if (fs_prepare_dir(userSource.string(), 0751, 0, 0) == -1) {
-                *error_msg = CREATE_ERROR("fs_prepare_dir failed on %s", userSource.string());
-                return false;
+                fail_fn(CREATE_ERROR("fs_prepare_dir failed on %s (%s)",
+                                     userSource.string(), strerror(errno)));
             }
-            if (TEMP_FAILURE_RETRY(mount(userSource.string(), "/storage/self",
-                    NULL, MS_BIND, NULL)) == -1) {
-                *error_msg = CREATE_ERROR("Failed to mount %s to /storage/self: %s",
-                                          userSource.string(),
-                                          strerror(errno));
-                return false;
+
+            if (TEMP_FAILURE_RETRY(mount(userSource.string(), "/storage/self", nullptr, MS_BIND,
+                                         nullptr)) == -1) {
+                fail_fn(CREATE_ERROR("Failed to mount %s to /storage/self: %s",
+                                     userSource.string(),
+                                     strerror(errno)));
             }
         } else {
             if (package_name.empty()) {
-                return true;
+                return;
             }
+
             userid_t user_id = multiuser_get_user_id(uid);
-            std::string pkgSandboxDir = StringPrintf("/mnt/user/%d/package/%s",
-                    user_id, package_name.c_str());
+            std::string pkgSandboxDir =
+                StringPrintf("/mnt/user/%d/package/%s", user_id, package_name.c_str());
             struct stat sb;
             bool sandboxAlreadyCreated = true;
             if (TEMP_FAILURE_RETRY(lstat(pkgSandboxDir.c_str(), &sb)) == -1) {
                 if (errno == ENOENT) {
                     ALOGD("Sandbox not yet created for %s", pkgSandboxDir.c_str());
                     sandboxAlreadyCreated = false;
-                    if (!createPkgSandbox(uid, package_name, error_msg)) {
-                        return false;
-                    }
+                    CreatePkgSandbox(uid, package_name, fail_fn);
                 } else {
-                    ALOGE("Failed to lstat %s", pkgSandboxDir.c_str());
-                    return false;
+                    fail_fn(CREATE_ERROR("Failed to lstat %s: %s",
+                                         pkgSandboxDir.c_str(), strerror(errno)));
                 }
             }
+
             if (TEMP_FAILURE_RETRY(mount(pkgSandboxDir.c_str(), "/storage",
-                    nullptr, MS_BIND | MS_REC | MS_SLAVE, nullptr)) == -1) {
-                *error_msg = CREATE_ERROR("Failed to mount %s to /storage: %s",
-                        pkgSandboxDir.c_str(), strerror(errno));
-                return false;
+                                         nullptr, MS_BIND | MS_REC | MS_SLAVE, nullptr)) == -1) {
+                fail_fn(CREATE_ERROR("Failed to mount %s to /storage: %s",
+                                     pkgSandboxDir.c_str(), strerror(errno)));
             }
+
             if (access("/storage/obb_mount", F_OK) == 0) {
                 if (mount_mode != MOUNT_EXTERNAL_INSTALLER) {
                     remove("/storage/obb_mount");
                 }
             } else {
                 if (mount_mode == MOUNT_EXTERNAL_INSTALLER) {
-                    int fd = TEMP_FAILURE_RETRY(open("/storage/obb_mount",
-                            O_RDWR | O_CREAT, 0660));
+                    int fd =
+                        TEMP_FAILURE_RETRY(open("/storage/obb_mount", O_RDWR | O_CREAT, 0660));
                     if (fd == -1) {
-                        *error_msg = CREATE_ERROR("Couldn't create /storage/obb_mount: %s",
-                                strerror(errno));
-                        return false;
+                        fail_fn(CREATE_ERROR("Couldn't create /storage/obb_mount: %s",
+                                             strerror(errno)));
                     }
                     close(fd);
                 }
@@ -621,38 +771,32 @@
             // pkg specific directories. Otherwise, leave as is and bind mounts will be taken
             // care of by vold later.
             if (sandboxAlreadyCreated) {
-                if (!preparePkgSpecificDirs(packages_for_uid, visible_vol_ids,
-                        mount_mode == MOUNT_EXTERNAL_INSTALLER, user_id, error_msg)) {
-                    return false;
-                }
+                PreparePkgSpecificDirs(packages_for_uid, visible_vol_ids,
+                    mount_mode == MOUNT_EXTERNAL_INSTALLER, user_id, fail_fn);
             }
         }
     } else {
-        if (TEMP_FAILURE_RETRY(mount(storageSource.string(), "/storage",
-                NULL, MS_BIND | MS_REC | MS_SLAVE, NULL)) == -1) {
-            *error_msg = CREATE_ERROR("Failed to mount %s to /storage: %s",
-                                      storageSource.string(),
-                                      strerror(errno));
-            return false;
+        if (TEMP_FAILURE_RETRY(mount(storageSource.string(), "/storage", nullptr,
+                                     MS_BIND | MS_REC | MS_SLAVE, nullptr)) == -1) {
+            fail_fn(CREATE_ERROR("Failed to mount %s to /storage: %s",
+                                 storageSource.string(),
+                                 strerror(errno)));
         }
 
         // Mount user-specific symlink helper into place
         userid_t user_id = multiuser_get_user_id(uid);
         const String8 userSource(String8::format("/mnt/user/%d", user_id));
         if (fs_prepare_dir(userSource.string(), 0751, 0, 0) == -1) {
-            *error_msg = CREATE_ERROR("fs_prepare_dir failed on %s", userSource.string());
-            return false;
+          fail_fn(CREATE_ERROR("fs_prepare_dir failed on %s",
+                               userSource.string()));
         }
+
         if (TEMP_FAILURE_RETRY(mount(userSource.string(), "/storage/self",
-                NULL, MS_BIND, NULL)) == -1) {
-            *error_msg = CREATE_ERROR("Failed to mount %s to /storage/self: %s",
-                                      userSource.string(),
-                                      strerror(errno));
-            return false;
+                               nullptr, MS_BIND, nullptr)) == -1) {
+          fail_fn(CREATE_ERROR("Failed to mount %s to /storage/self: %s",
+                               userSource.string(), strerror(errno)));
         }
     }
-
-    return true;
 }
 
 static bool NeedsNoRandomizeWorkaround() {
@@ -677,58 +821,48 @@
 
 // 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 bool DetachDescriptors(JNIEnv* env, jintArray fdsToClose, std::string* error_msg) {
-  if (!fdsToClose) {
-    return true;
-  }
-  jsize count = env->GetArrayLength(fdsToClose);
-  ScopedIntArrayRO ar(env, fdsToClose);
-  if (ar.get() == NULL) {
-    *error_msg = "Bad fd array";
-    return false;
-  }
-  jsize i;
-  int devnull;
-  for (i = 0; i < count; i++) {
-    devnull = open("/dev/null", O_RDWR);
-    if (devnull < 0) {
-      *error_msg = std::string("Failed to open /dev/null: ").append(strerror(errno));
-      return false;
+static void DetachDescriptors(JNIEnv* env,
+                              const std::vector<int>& fds_to_close,
+                              fail_fn_t fail_fn) {
+
+  if (fds_to_close.size() > 0) {
+    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)));
     }
-    ALOGV("Switching descriptor %d to /dev/null: %s", ar[i], strerror(errno));
-    if (dup2(devnull, ar[i]) < 0) {
-      *error_msg = StringPrintf("Failed dup2() on descriptor %d: %s", ar[i], strerror(errno));
-      return false;
+
+    for (int fd : fds_to_close) {
+      ALOGV("Switching descriptor %d to /dev/null", fd);
+      if (dup3(devnull_fd, fd, O_CLOEXEC) == -1) {
+        fail_fn(StringPrintf("Failed dup3() on descriptor %d: %s", fd, strerror(errno)));
+      }
     }
-    close(devnull);
   }
-  return true;
 }
 
-void SetThreadName(const char* thread_name) {
+void SetThreadName(const std::string& thread_name) {
   bool hasAt = false;
   bool hasDot = false;
-  const char* s = thread_name;
-  while (*s) {
-    if (*s == '.') {
+
+  for (const char str_el : thread_name) {
+    if (str_el == '.') {
       hasDot = true;
-    } else if (*s == '@') {
+    } else if (str_el == '@') {
       hasAt = true;
     }
-    s++;
   }
-  const int len = s - thread_name;
-  if (len < 15 || hasAt || !hasDot) {
-    s = thread_name;
-  } else {
-    s = thread_name + len - 15;
+
+  const char* name_start_ptr = thread_name.c_str();
+  if (thread_name.length() >= MAX_NAME_LENGTH && !hasAt && hasDot) {
+    name_start_ptr += thread_name.length() - MAX_NAME_LENGTH;
   }
+
   // pthread_setname_np fails rather than truncating long strings.
   char buf[16];       // MAX_TASK_COMM_LEN=16 is hard-coded into bionic
-  strlcpy(buf, s, sizeof(buf)-1);
+  strlcpy(buf, name_start_ptr, sizeof(buf) - 1);
   errno = pthread_setname_np(pthread_self(), buf);
   if (errno != 0) {
     ALOGW("Unable to set the name of current thread to '%s': %s", buf, strerror(errno));
@@ -737,28 +871,16 @@
   android::base::SetDefaultTag(buf);
 }
 
-// The list of open zygote file descriptors.
-static FileDescriptorTable* gOpenFdTable = NULL;
-
-static bool FillFileDescriptorVector(JNIEnv* env,
-                                     jintArray managed_fds,
-                                     std::vector<int>* fds,
-                                     std::string* error_msg) {
-  CHECK(fds != nullptr);
-  if (managed_fds != nullptr) {
-    ScopedIntArrayRO ar(env, managed_fds);
-    if (ar.get() == nullptr) {
-      *error_msg = "Bad fd array";
-      return false;
-    }
-    fds->reserve(ar.size());
-    for (size_t i = 0; i < ar.size(); ++i) {
-      fds->push_back(ar[i]);
-    }
-  }
-  return true;
-}
-
+/**
+ * A failure function used to report fatal errors to the managed runtime.  This
+ * function is often curried with the process name information and then passed
+ * to called functions.
+ *
+ * @param env  Managed runtime environment
+ * @param process_name  A native representation of the process name
+ * @param managed_process_name  A managed representation of the process name
+ * @param msg  The error message to be reported
+ */
 [[noreturn]]
 static void ZygoteFailure(JNIEnv* env,
                           const char* process_name,
@@ -779,12 +901,25 @@
   __builtin_unreachable();
 }
 
+/**
+ * A helper method for converting managed strings to native strings.  A fatal
+ * error is generated if a problem is encountered in extracting a non-null
+ * string.
+ *
+ * @param env  Managed runtime environment
+ * @param process_name  A native representation of the process name
+ * @param managed_process_name  A managed representation of the process name
+ * @param managed_string  The managed string to extract
+ *
+ * @return An empty option if the managed string is null.  A optional-wrapped
+ * string otherwise.
+ */
 static std::optional<std::string> ExtractJString(JNIEnv* env,
                                                  const char* process_name,
                                                  jstring managed_process_name,
                                                  jstring managed_string) {
   if (managed_string == nullptr) {
-    return std::optional<std::string>();
+    return std::nullopt;
   } else {
     ScopedUtfChars scoped_string_chars(env, managed_string);
 
@@ -796,15 +931,124 @@
   }
 }
 
-// Utility routine to fork a zygote.
-static pid_t ForkCommon(JNIEnv* env, bool is_system_server,
-                        jintArray managed_fds_to_close, jintArray managed_fds_to_ignore) {
-  SetSignalHandlers();
+/**
+ * A helper method for converting managed string arrays to native vectors.  A
+ * fatal error is generated if a problem is encountered in extracting a non-null array.
+ *
+ * @param env  Managed runtime environment
+ * @param process_name  A native representation of the process name
+ * @param managed_process_name  A managed representation of the process name
+ * @param managed_array  The managed integer array to extract
+ *
+ * @return An empty option if the managed array is null.  A optional-wrapped
+ * vector otherwise.
+ */
+static std::optional<std::vector<int>> ExtractJIntArray(JNIEnv* env,
+                                                        const char* process_name,
+                                                        jstring managed_process_name,
+                                                        jintArray managed_array) {
+  if (managed_array == nullptr) {
+    return std::nullopt;
+  } else {
+    ScopedIntArrayRO managed_array_handle(env, managed_array);
 
-  // Block SIGCHLD prior to fork.
-  sigset_t sigchld;
-  sigemptyset(&sigchld);
-  sigaddset(&sigchld, SIGCHLD);
+    if (managed_array_handle.get() != nullptr) {
+      std::vector<int> native_array;
+      native_array.reserve(managed_array_handle.size());
+
+      for (size_t array_index = 0; array_index < managed_array_handle.size(); ++array_index) {
+        native_array.push_back(managed_array_handle[array_index]);
+      }
+
+      return std::move(native_array);
+
+    } else {
+      ZygoteFailure(env, process_name, managed_process_name, "Failed to extract JIntArray.");
+    }
+  }
+}
+
+/**
+ * A helper method for converting managed string arrays to native vectors.  A
+ * fatal error is generated if a problem is encountered in extracting a non-null array.
+ *
+ * @param env  Managed runtime environment
+ * @param process_name  A native representation of the process name
+ * @param managed_process_name  A managed representation of the process name
+ * @param managed_array  The managed string array to extract
+ *
+ * @return An empty option if the managed array is null.  A optional-wrapped
+ * vector otherwise.
+ */
+static std::optional<std::vector<std::string>> ExtractJStringArray(JNIEnv* env,
+                                                                   const char* process_name,
+                                                                   jstring managed_process_name,
+                                                                   jobjectArray managed_array) {
+  if (managed_array == nullptr) {
+    return std::nullopt;
+  } else {
+    jsize element_count = env->GetArrayLength(managed_array);
+    std::vector<std::string> native_string_vector;
+    native_string_vector.reserve(element_count);
+
+    for (jsize array_index = 0; array_index < element_count; ++array_index) {
+      jstring managed_string = (jstring) env->GetObjectArrayElement(managed_array, array_index);
+      auto native_string = ExtractJString(env, process_name, managed_process_name, managed_string);
+
+      if (LIKELY(native_string.has_value())) {
+        native_string_vector.emplace_back(std::move(native_string.value()));
+      } else {
+        ZygoteFailure(env, process_name, managed_process_name,
+                      "Null string found in managed string array.");
+      }
+    }
+
+    return std::move(native_string_vector);
+  }
+}
+
+/**
+ * A utility function for blocking signals.
+ *
+ * @param signum  Signal number to block
+ * @param fail_fn  Fatal error reporting function
+ *
+ * @see ZygoteFailure
+ */
+static void BlockSignal(int signum, fail_fn_t fail_fn) {
+  sigset_t sigs;
+  sigemptyset(&sigs);
+  sigaddset(&sigs, signum);
+
+  if (sigprocmask(SIG_BLOCK, &sigs, nullptr) == -1) {
+    fail_fn(CREATE_ERROR("Failed to block signal %s: %s", strsignal(signum), strerror(errno)));
+  }
+}
+
+
+/**
+ * A utility function for unblocking signals.
+ *
+ * @param signum  Signal number to unblock
+ * @param fail_fn  Fatal error reporting function
+ *
+ * @see ZygoteFailure
+ */
+static void UnblockSignal(int signum, fail_fn_t fail_fn) {
+  sigset_t sigs;
+  sigemptyset(&sigs);
+  sigaddset(&sigs, signum);
+
+  if (sigprocmask(SIG_UNBLOCK, &sigs, nullptr) == -1) {
+    fail_fn(CREATE_ERROR("Failed to un-block signal %s: %s", strsignal(signum), strerror(errno)));
+  }
+}
+
+// Utility routine to fork a process from the zygote.
+static pid_t ForkCommon(JNIEnv* env, bool is_system_server,
+                        const std::vector<int>& fds_to_close,
+                        const std::vector<int>& fds_to_ignore) {
+  SetSignalHandlers();
 
   // Curry a failure function.
   auto fail_fn = std::bind(ZygoteFailure, env, is_system_server ? "system_server" : "zygote",
@@ -815,9 +1059,7 @@
   // This would cause failures because the FDs are not whitelisted.
   //
   // Note that the zygote process is single threaded at this point.
-  if (sigprocmask(SIG_BLOCK, &sigchld, nullptr) == -1) {
-    fail_fn(CREATE_ERROR("sigprocmask(SIG_SETMASK, { SIGCHLD }) failed: %s", strerror(errno)));
-  }
+  BlockSignal(SIGCHLD, fail_fn);
 
   // Close any logging related FDs before we start evaluating the list of
   // file descriptors.
@@ -827,19 +1069,10 @@
   // If this is the first fork for this zygote, create the open FD table.  If
   // it isn't, we just need to check whether the list of open files has changed
   // (and it shouldn't in the normal case).
-  std::string error_msg;
-  std::vector<int> fds_to_ignore;
-  if (!FillFileDescriptorVector(env, managed_fds_to_ignore, &fds_to_ignore, &error_msg)) {
-    fail_fn(error_msg);
-  }
-
   if (gOpenFdTable == nullptr) {
-    gOpenFdTable = FileDescriptorTable::Create(fds_to_ignore, &error_msg);
-    if (gOpenFdTable == nullptr) {
-      fail_fn(error_msg);
-    }
-  } else if (!gOpenFdTable->Restat(fds_to_ignore, &error_msg)) {
-    fail_fn(error_msg);
+    gOpenFdTable = FileDescriptorTable::Create(fds_to_ignore, fail_fn);
+  } else {
+    gOpenFdTable->Restat(fds_to_ignore, fail_fn);
   }
 
   android_fdsan_error_level fdsan_error_level = android_fdsan_get_error_level();
@@ -851,24 +1084,18 @@
     PreApplicationInit();
 
     // Clean up any descriptors which must be closed immediately
-    if (!DetachDescriptors(env, managed_fds_to_close, &error_msg)) {
-      fail_fn(error_msg);
-    }
+    DetachDescriptors(env, fds_to_close, fail_fn);
 
     // Re-open all remaining open file descriptors so that they aren't shared
     // with the zygote across a fork.
-    if (!gOpenFdTable->ReopenOrDetach(&error_msg)) {
-      fail_fn(error_msg);
-    }
+    gOpenFdTable->ReopenOrDetach(fail_fn);
 
     // Turn fdsan back on.
     android_fdsan_set_error_level(fdsan_error_level);
   }
 
   // We blocked SIGCHLD prior to a fork, we unblock it here.
-  if (sigprocmask(SIG_UNBLOCK, &sigchld, nullptr) == -1) {
-    fail_fn(CREATE_ERROR("sigprocmask(SIG_SETMASK, { SIGCHLD }) failed: %s", strerror(errno)));
-  }
+  UnblockSignal(SIGCHLD, fail_fn);
 
   return pid;
 }
@@ -883,10 +1110,9 @@
                              jstring managed_app_data_dir, jstring managed_package_name,
                              jobjectArray managed_pacakges_for_uid,
                              jobjectArray managed_visible_vol_ids) {
-  auto fail_fn = std::bind(ZygoteFailure, env, is_system_server ? "system_server" : "zygote",
-                           managed_nice_name, _1);
-  auto extract_fn = std::bind(ExtractJString, env, is_system_server ? "system_server" : "zygote",
-                              managed_nice_name, _1);
+  const char* process_name = is_system_server ? "system_server" : "zygote";
+  auto fail_fn = std::bind(ZygoteFailure, env, process_name, managed_nice_name, _1);
+  auto extract_fn = std::bind(ExtractJString, env, process_name, managed_nice_name, _1);
 
   auto se_info = extract_fn(managed_se_info);
   auto nice_name = extract_fn(managed_nice_name);
@@ -894,22 +1120,14 @@
   auto app_data_dir = extract_fn(managed_app_data_dir);
   auto package_name = extract_fn(managed_package_name);
 
-  std::string error_msg;
-
   // Keep capabilities across UID change, unless we're staying root.
   if (uid != 0) {
-    if (!EnableKeepCapabilities(&error_msg)) {
-      fail_fn(error_msg);
-    }
+    EnableKeepCapabilities(fail_fn);
   }
 
-  if (!SetInheritable(permitted_capabilities, &error_msg)) {
-    fail_fn(error_msg);
-  }
+  SetInheritable(permitted_capabilities, fail_fn);
 
-  if (!DropCapabilitiesBoundingSet(&error_msg)) {
-    fail_fn(error_msg);
-  }
+  DropCapabilitiesBoundingSet(fail_fn);
 
   bool use_native_bridge = !is_system_server &&
                            instruction_set.has_value() &&
@@ -934,56 +1152,21 @@
     }
   }
 
-  std::vector<std::string> packages_for_uid;
-  if (managed_pacakges_for_uid != nullptr) {
-    jsize count = env->GetArrayLength(managed_pacakges_for_uid);
-    for (jsize package_index = 0; package_index < count; ++package_index) {
-      jstring managed_package_for_uid =
-          (jstring) env->GetObjectArrayElement(managed_pacakges_for_uid, package_index);
+  std::vector<std::string> packages_for_uid =
+      ExtractJStringArray(env, process_name, managed_nice_name, managed_pacakges_for_uid).
+      value_or(std::vector<std::string>());
 
-      auto package_for_uid = extract_fn(managed_package_for_uid);
-      if (LIKELY(package_for_uid.has_value())) {
-        packages_for_uid.emplace_back(std::move(package_for_uid.value()));
-      } else {
-        fail_fn("Null string found in managed packages_for_uid.");
-      }
-    }
-  }
+  std::vector<std::string> visible_vol_ids =
+      ExtractJStringArray(env, process_name, managed_nice_name, managed_visible_vol_ids).
+      value_or(std::vector<std::string>());
 
-  std::vector<std::string> visible_vol_ids;
-  if (managed_visible_vol_ids != nullptr) {
-    jsize count = env->GetArrayLength(managed_visible_vol_ids);
-    for (jsize vol_id_index = 0; vol_id_index < count; ++vol_id_index) {
-      jstring managed_visible_vol_id =
-          (jstring) env->GetObjectArrayElement(managed_visible_vol_ids, vol_id_index);
-
-      auto visible_vol_id = extract_fn(managed_visible_vol_id);
-      if (LIKELY(visible_vol_id.has_value())) {
-        visible_vol_ids.emplace_back(std::move(visible_vol_id.value()));
-      } else {
-        fail_fn("Null string found in managed visible_vol_ids.");
-      }
-    }
-  }
-
-  if (!MountEmulatedStorage(uid, mount_external, use_native_bridge, &error_msg,
-                            package_name.value(), packages_for_uid, visible_vol_ids)) {
-    ALOGW("Failed to mount emulated storage: %s (%s)", error_msg.c_str(), strerror(errno));
-    if (errno == ENOTCONN || errno == EROFS) {
-      // When device is actively encrypting, we get ENOTCONN here
-      // since FUSE was mounted before the framework restarted.
-      // When encrypted device is booting, we get EROFS since
-      // FUSE hasn't been created yet by init.
-      // In either case, continue without external storage.
-    } else {
-      fail_fn(error_msg);
-    }
-  }
+  MountEmulatedStorage(uid, mount_external, use_native_bridge, package_name.value(),
+                       packages_for_uid, visible_vol_ids, fail_fn);
 
   // If this zygote isn't root, it won't be able to create a process group,
   // since the directory is owned by root.
   if (!is_system_server && getuid() == 0) {
-    int rc = createProcessGroup(uid, getpid());
+    const int rc = createProcessGroup(uid, getpid());
     if (rc == -EROFS) {
       ALOGW("createProcessGroup failed, kernel missing CONFIG_CGROUP_CPUACCT?");
     } else if (rc != 0) {
@@ -991,13 +1174,8 @@
     }
   }
 
-  if (!SetGids(env, gids, &error_msg)) {
-    fail_fn(error_msg);
-  }
-
-  if (!SetRLimits(env, rlimits, &error_msg)) {
-    fail_fn(error_msg);
-  }
+  SetGids(env, gids, fail_fn);
+  SetRLimits(env, rlimits, fail_fn);
 
   if (use_native_bridge) {
     // Due to the logic behind use_native_bridge we know that both app_data_dir
@@ -1056,14 +1234,9 @@
     }
   }
 
-  if (!SetCapabilities(permitted_capabilities, effective_capabilities, permitted_capabilities,
-                       &error_msg)) {
-    fail_fn(error_msg);
-  }
+  SetCapabilities(permitted_capabilities, effective_capabilities, permitted_capabilities, fail_fn);
 
-  if (!SetSchedulerPolicy(&error_msg)) {
-    fail_fn(error_msg);
-  }
+  SetSchedulerPolicy(fail_fn);
 
   const char* se_info_ptr = se_info.has_value() ? se_info.value().c_str() : nullptr;
   const char* nice_name_ptr = nice_name.has_value() ? nice_name.value().c_str() : nullptr;
@@ -1076,7 +1249,7 @@
   // Make it easier to debug audit logs by setting the main thread's name to the
   // nice name rather than "app_process".
   if (nice_name.has_value()) {
-    SetThreadName(nice_name.value().c_str());
+    SetThreadName(nice_name.value());
   } else if (is_system_server) {
     SetThreadName("system_server");
   }
@@ -1089,6 +1262,7 @@
     if (env->ExceptionCheck()) {
       fail_fn("Error calling post fork system server hooks.");
     }
+
     // TODO(oth): Remove hardcoded label here (b/117874058).
     static const char* kSystemServerLabel = "u:r:system_server:s0";
     if (selinux_android_setcon(kSystemServerLabel) != 0) {
@@ -1192,6 +1366,74 @@
 
   return capabilities & GetEffectiveCapabilityMask(env);
 }
+
+/**
+ * Adds the given information about a newly created blastula to the Zygote's
+ * blastula table.
+ *
+ * @param blastula_pid  Process ID of the newly created blastula
+ * @param read_pipe_fd  File descriptor for the read end of the blastula
+ * reporting pipe.  Used in the ZygoteServer poll loop to track blastula
+ * specialization.
+ */
+static void AddBlastulaTableEntry(pid_t blastula_pid, int read_pipe_fd) {
+  static int sBlastulaTableInsertIndex = 0;
+
+  int search_index = sBlastulaTableInsertIndex;
+
+  do {
+    if (gBlastulaTable[search_index].SetIfInvalid(blastula_pid, read_pipe_fd)) {
+      // Start our next search right after where we finished this one.
+      sBlastulaTableInsertIndex = (search_index + 1) % gBlastulaTable.size();
+
+      return;
+    }
+
+    search_index = (search_index + 1) % gBlastulaTable.size();
+  } while (search_index != sBlastulaTableInsertIndex);
+
+  // Much like money in the banana stand, there should always be an entry
+  // in the blastula table.
+  __builtin_unreachable();
+}
+
+/**
+ * Invalidates the entry in the BlastulaTable corresponding to the provided
+ * process ID if it is present.  If an entry was removed the blastula pool
+ * count is decremented.
+ *
+ * @param blastula_pid  Process ID of the blastula entry to invalidate
+ * @return True if an entry was invalidated; false otherwise
+ */
+static bool RemoveBlastulaTableEntry(pid_t blastula_pid) {
+  for (BlastulaTableEntry& entry : gBlastulaTable) {
+    if (entry.ClearForPID(blastula_pid)) {
+      --gBlastulaPoolCount;
+      return true;
+    }
+  }
+
+  return false;
+}
+
+/**
+ * @return A vector of the read pipe FDs for each of the active blastulas.
+ */
+std::vector<int> MakeBlastulaPipeReadFDVector() {
+  std::vector<int> fd_vec;
+  fd_vec.reserve(gBlastulaTable.size());
+
+  for (BlastulaTableEntry& entry : gBlastulaTable) {
+    auto entry_values = entry.GetValues();
+
+    if (entry_values.has_value()) {
+      fd_vec.push_back(entry_values.value().read_pipe_fd);
+    }
+  }
+
+  return fd_vec;
+}
+
 }  // anonymous namespace
 
 namespace android {
@@ -1210,12 +1452,35 @@
         JNIEnv* env, jclass, jint uid, jint gid, jintArray gids,
         jint runtime_flags, jobjectArray rlimits,
         jint mount_external, jstring se_info, jstring nice_name,
-        jintArray fds_to_close, jintArray fds_to_ignore, jboolean is_child_zygote,
+        jintArray managed_fds_to_close, jintArray managed_fds_to_ignore, jboolean is_child_zygote,
         jstring instruction_set, jstring app_data_dir, jstring package_name,
         jobjectArray packages_for_uid, jobjectArray visible_vol_ids) {
     jlong capabilities = CalculateCapabilities(env, uid, gid, gids, is_child_zygote);
 
+    if (UNLIKELY(managed_fds_to_close == nullptr)) {
+      ZygoteFailure(env, "zygote", nice_name, "Zygote received a null fds_to_close vector.");
+    }
+
+    std::vector<int> fds_to_close =
+        ExtractJIntArray(env, "zygote", nice_name, managed_fds_to_close).value();
+    std::vector<int> fds_to_ignore =
+        ExtractJIntArray(env, "zygote", nice_name, managed_fds_to_ignore)
+            .value_or(std::vector<int>());
+
+    std::vector<int> blastula_pipes = MakeBlastulaPipeReadFDVector();
+
+    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);
+
+    if (gBlastulaPoolEventFD != -1) {
+      fds_to_close.push_back(gBlastulaPoolEventFD);
+      fds_to_ignore.push_back(gBlastulaPoolEventFD);
+    }
+
     pid_t pid = ForkCommon(env, false, fds_to_close, fds_to_ignore);
+
     if (pid == 0) {
       SpecializeCommon(env, uid, gid, gids, runtime_flags, rlimits,
                        capabilities, capabilities,
@@ -1230,9 +1495,19 @@
         JNIEnv* env, jclass, uid_t uid, gid_t gid, jintArray gids,
         jint runtime_flags, jobjectArray rlimits, jlong permitted_capabilities,
         jlong effective_capabilities) {
+  std::vector<int> fds_to_close(MakeBlastulaPipeReadFDVector()),
+                   fds_to_ignore(fds_to_close);
+
+  fds_to_close.push_back(gBlastulaPoolSocketFD);
+
+  if (gBlastulaPoolEventFD != -1) {
+    fds_to_close.push_back(gBlastulaPoolEventFD);
+    fds_to_ignore.push_back(gBlastulaPoolEventFD);
+  }
+
   pid_t pid = ForkCommon(env, true,
-                         /* managed_fds_to_close= */ nullptr,
-                         /* managed_fds_to_ignore= */ nullptr);
+                         fds_to_close,
+                         fds_to_ignore);
   if (pid == 0) {
       SpecializeCommon(env, uid, gid, gids, runtime_flags, rlimits,
                        permitted_capabilities, effective_capabilities,
@@ -1266,6 +1541,52 @@
   return pid;
 }
 
+/**
+ * A JNI function that forks a blastula from the Zygote while ensuring proper
+ * file descriptor hygiene.
+ *
+ * @param env  Managed runtime environment
+ * @param read_pipe_fd  The read FD for the blastula reporting pipe.  Manually closed by blastlas
+ * in managed code.
+ * @param write_pipe_fd  The write FD for the blastula reporting pipe.  Manually closed by the
+ * zygote in managed code.
+ * @param managed_session_socket_fds  A list of anonymous session sockets that must be ignored by
+ * the FD hygiene code and automatically "closed" in the new blastula.
+ * @return
+ */
+static jint com_android_internal_os_Zygote_nativeForkBlastula(JNIEnv* env, jclass,
+    jint read_pipe_fd, jint write_pipe_fd, jintArray managed_session_socket_fds) {
+  std::vector<int> fds_to_close(MakeBlastulaPipeReadFDVector()),
+                   fds_to_ignore(fds_to_close);
+
+  std::vector<int> session_socket_fds =
+      ExtractJIntArray(env, "blastula", nullptr, managed_session_socket_fds)
+          .value_or(std::vector<int>());
+
+  // The Blastula Pool Event FD is created during the initialization of the
+  // blastula pool and should always be valid here.
+
+  fds_to_close.push_back(gZygoteSocketFD);
+  fds_to_close.push_back(gBlastulaPoolEventFD);
+  fds_to_close.insert(fds_to_close.end(), session_socket_fds.begin(), session_socket_fds.end());
+
+  fds_to_ignore.push_back(gZygoteSocketFD);
+  fds_to_ignore.push_back(gBlastulaPoolSocketFD);
+  fds_to_ignore.push_back(gBlastulaPoolEventFD);
+  fds_to_ignore.push_back(read_pipe_fd);
+  fds_to_ignore.push_back(write_pipe_fd);
+  fds_to_ignore.insert(fds_to_ignore.end(), session_socket_fds.begin(), session_socket_fds.end());
+
+  pid_t blastula_pid = ForkCommon(env, /* is_system_server= */ false, fds_to_close, fds_to_ignore);
+
+  if (blastula_pid != 0) {
+    ++gBlastulaPoolCount;
+    AddBlastulaTableEntry(blastula_pid, read_pipe_fd);
+  }
+
+  return blastula_pid;
+}
+
 static void com_android_internal_os_Zygote_nativeAllowFileAcrossFork(
         JNIEnv* env, jclass, jstring path) {
     ScopedUtfChars path_native(env, path);
@@ -1321,14 +1642,125 @@
     return;
   }
 
-  // TODO(b/111434506) install the filter
-
-  /*
   bool installed = install_setuidgid_seccomp_filter(uidGidMin, uidGidMax);
   if (!installed) {
       RuntimeAbort(env, __LINE__, "Could not install setuid/setgid seccomp filter.");
   }
-  */
+}
+
+/**
+ * Called from a blastula to specialize the process for a specific application.
+ *
+ * @param env  Managed runtime environment
+ * @param uid  User ID of the new application
+ * @param gid  Group ID of the new application
+ * @param gids  Extra groups that the process belongs to
+ * @param runtime_flags  Flags for changing the behavior of the managed runtime
+ * @param rlimits  Resource limits
+ * @param mount_external  The mode (read/write/normal) that external storage will be mounted with
+ * @param se_info  SELinux policy information
+ * @param nice_name  New name for this process
+ * @param is_child_zygote  If the process is to become a WebViewZygote
+ * @param instruction_set  The instruction set expected/requested by the new application
+ * @param app_data_dir  Path to the application's data directory
+ */
+static void com_android_internal_os_Zygote_nativeSpecializeBlastula(
+    JNIEnv* env, jclass, jint uid, jint gid, jintArray gids,
+    jint runtime_flags, jobjectArray rlimits,
+    jint mount_external, jstring se_info, jstring nice_name,
+    jboolean is_child_zygote, jstring instruction_set, jstring app_data_dir,
+    jstring package_name, jobjectArray packages_for_uid, jobjectArray visible_vol_ids) {
+  jlong capabilities = CalculateCapabilities(env, uid, gid, gids, is_child_zygote);
+
+  SpecializeCommon(env, uid, gid, gids, runtime_flags, rlimits,
+                   capabilities, capabilities,
+                   mount_external, se_info, nice_name, false,
+                   is_child_zygote == JNI_TRUE, instruction_set, app_data_dir,
+                   package_name, packages_for_uid, visible_vol_ids);
+}
+
+/**
+ * A helper method for fetching socket file descriptors that were opened by init from the
+ * environment.
+ *
+ * @param env  Managed runtime environment
+ * @param is_primary  If this process is the primary or secondary Zygote; used to compute the name
+ * of the environment variable storing the file descriptors.
+ */
+static void com_android_internal_os_Zygote_nativeGetSocketFDs(JNIEnv* env, jclass,
+                                                              jboolean is_primary) {
+  std::string android_socket_prefix(ANDROID_SOCKET_PREFIX);
+  std::string env_var_name = android_socket_prefix + (is_primary ? "zygote" : "zygote_secondary");
+  char* env_var_val = getenv(env_var_name.c_str());
+
+  if (env_var_val != nullptr) {
+    gZygoteSocketFD = atoi(env_var_val);
+    ALOGV("Zygote:zygoteSocketFD = %d", gZygoteSocketFD);
+  } else {
+    ALOGE("Unable to fetch Zygote socket file descriptor");
+  }
+
+  env_var_name = android_socket_prefix + (is_primary ? "blastula_pool" : "blastula_pool_secondary");
+  env_var_val = getenv(env_var_name.c_str());
+
+  if (env_var_val != nullptr) {
+    gBlastulaPoolSocketFD = atoi(env_var_val);
+    ALOGV("Zygote:blastulaPoolSocketFD = %d", gBlastulaPoolSocketFD);
+  } else {
+    ALOGE("Unable to fetch Blastula pool socket file descriptor");
+  }
+}
+
+/**
+ * @param env  Managed runtime environment
+ * @return  A managed array of raw file descriptors for the read ends of the blastula reporting
+ * pipes.
+ */
+static jintArray com_android_internal_os_Zygote_nativeGetBlastulaPipeFDs(JNIEnv* env, jclass) {
+  std::vector<int> blastula_fds = MakeBlastulaPipeReadFDVector();
+
+  jintArray managed_blastula_fds = env->NewIntArray(blastula_fds.size());
+  env->SetIntArrayRegion(managed_blastula_fds, 0, blastula_fds.size(), blastula_fds.data());
+
+  return managed_blastula_fds;
+}
+
+/**
+ * A JNI wrapper around RemoveBlastulaTableEntry.
+ *
+ * @param env  Managed runtime environment
+ * @param blastula_pid  Process ID of the blastula entry to invalidate
+ * @return  True if an entry was invalidated; false otherwise.
+ */
+static jboolean com_android_internal_os_Zygote_nativeRemoveBlastulaTableEntry(JNIEnv* env, jclass,
+                                                                              jint blastula_pid) {
+  return RemoveBlastulaTableEntry(blastula_pid);
+}
+
+/**
+ * Creates the blastula pool event FD if it doesn't exist and returns it.  This is used by the
+ * ZygoteServer poll loop to know when to re-fill the blastula pool.
+ *
+ * @param env  Managed runtime environment
+ * @return A raw event file descriptor used to communicate (from the signal handler) when the
+ * Zygote receives a SIGCHLD for a blastula
+ */
+static jint com_android_internal_os_Zygote_nativeGetBlastulaPoolEventFD(JNIEnv* env, jclass) {
+  if (gBlastulaPoolEventFD == -1) {
+    if ((gBlastulaPoolEventFD = eventfd(0, 0)) == -1) {
+      ZygoteFailure(env, "zygote", nullptr, StringPrintf("Unable to create eventfd: %s", strerror(errno)));
+    }
+  }
+
+  return gBlastulaPoolEventFD;
+}
+
+/**
+ * @param env  Managed runtime environment
+ * @return The number of blastulas currently in the blastula pool
+ */
+static jint com_android_internal_os_Zygote_nativeGetBlastulaPoolCount(JNIEnv* env, jclass) {
+  return gBlastulaPoolCount;
 }
 
 static const JNINativeMethod gMethods[] = {
@@ -1346,7 +1778,22 @@
     { "nativePreApplicationInit", "()V",
       (void *) com_android_internal_os_Zygote_nativePreApplicationInit },
     { "nativeInstallSeccompUidGidFilter", "(II)V",
-      (void *) com_android_internal_os_Zygote_nativeInstallSeccompUidGidFilter }
+      (void *) com_android_internal_os_Zygote_nativeInstallSeccompUidGidFilter },
+    { "nativeForkBlastula", "(II[I)I",
+      (void *) com_android_internal_os_Zygote_nativeForkBlastula },
+    { "nativeSpecializeBlastula",
+      "(II[II[[IILjava/lang/String;Ljava/lang/String;ZLjava/lang/String;Ljava/lang/String;Ljava/lang/String;[Ljava/lang/String;[Ljava/lang/String;)V",
+      (void *) com_android_internal_os_Zygote_nativeSpecializeBlastula },
+    { "nativeGetSocketFDs", "(Z)V",
+      (void *) com_android_internal_os_Zygote_nativeGetSocketFDs },
+    { "nativeGetBlastulaPipeFDs", "()[I",
+      (void *) com_android_internal_os_Zygote_nativeGetBlastulaPipeFDs },
+    { "nativeRemoveBlastulaTableEntry", "(I)Z",
+      (void *) com_android_internal_os_Zygote_nativeRemoveBlastulaTableEntry },
+    { "nativeGetBlastulaPoolEventFD", "()I",
+      (void *) com_android_internal_os_Zygote_nativeGetBlastulaPoolEventFD },
+    { "nativeGetBlastulaPoolCount", "()I",
+      (void *) com_android_internal_os_Zygote_nativeGetBlastulaPoolCount }
 };
 
 int register_com_android_internal_os_Zygote(JNIEnv* env) {
diff --git a/core/jni/fd_utils.cpp b/core/jni/fd_utils.cpp
index d60d1a6..4b37f13 100644
--- a/core/jni/fd_utils.cpp
+++ b/core/jni/fd_utils.cpp
@@ -38,6 +38,8 @@
   "/dev/null",
   "/dev/socket/zygote",
   "/dev/socket/zygote_secondary",
+  "/dev/socket/blastula_pool",
+  "/dev/socket/blastula_pool_secondary",
   "/dev/socket/webview_zygote",
   "/dev/socket/heapprofd",
   "/sys/kernel/debug/tracing/trace_marker",
@@ -70,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)
@@ -77,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
@@ -134,15 +144,14 @@
 // open zygote file descriptor.
 class FileDescriptorInfo {
  public:
-  // Create a FileDescriptorInfo for a given file descriptor. Returns
-  // |NULL| if an error occurred.
-  static FileDescriptorInfo* CreateFromFd(int fd, std::string* error_msg);
+  // Create a FileDescriptorInfo for a given file descriptor.
+  static FileDescriptorInfo* CreateFromFd(int fd, fail_fn_t fail_fn);
 
   // Checks whether the file descriptor associated with this object
   // refers to the same description.
-  bool Restat() const;
+  bool RefersToSameFile() const;
 
-  bool ReopenOrDetach(std::string* error_msg) const;
+  void ReopenOrDetach(fail_fn_t fail_fn) const;
 
   const int fd;
   const struct stat stat;
@@ -168,19 +177,18 @@
   //   address).
   static bool GetSocketName(const int fd, std::string* result);
 
-  bool DetachSocket(std::string* error_msg) const;
+  void DetachSocket(fail_fn_t fail_fn) const;
 
   DISALLOW_COPY_AND_ASSIGN(FileDescriptorInfo);
 };
 
 // static
-FileDescriptorInfo* FileDescriptorInfo::CreateFromFd(int fd, std::string* error_msg) {
+FileDescriptorInfo* FileDescriptorInfo::CreateFromFd(int fd, fail_fn_t fail_fn) {
   struct stat f_stat;
   // This should never happen; the zygote should always have the right set
   // of permissions required to stat all its open files.
   if (TEMP_FAILURE_RETRY(fstat(fd, &f_stat)) == -1) {
-    *error_msg = android::base::StringPrintf("Unable to stat %d", fd);
-    return nullptr;
+    fail_fn(android::base::StringPrintf("Unable to stat %d", fd));
   }
 
   const FileDescriptorWhitelist* whitelist = FileDescriptorWhitelist::Get();
@@ -188,15 +196,13 @@
   if (S_ISSOCK(f_stat.st_mode)) {
     std::string socket_name;
     if (!GetSocketName(fd, &socket_name)) {
-      *error_msg = "Unable to get socket name";
-      return nullptr;
+      fail_fn("Unable to get socket name");
     }
 
     if (!whitelist->IsAllowed(socket_name)) {
-      *error_msg = android::base::StringPrintf("Socket name not whitelisted : %s (fd=%d)",
-                                               socket_name.c_str(),
-                                               fd);
-      return nullptr;
+      fail_fn(android::base::StringPrintf("Socket name not whitelisted : %s (fd=%d)",
+                                          socket_name.c_str(),
+                                          fd));
     }
 
     return new FileDescriptorInfo(fd);
@@ -209,26 +215,35 @@
   // S_ISDIR : Not supported. (We could if we wanted to, but it's unused).
   // S_ISLINK : Not supported.
   // S_ISBLK : Not supported.
-  // S_ISFIFO : Not supported. Note that the zygote uses pipes to communicate
-  // with the child process across forks but those should have been closed
-  // before we got to this point.
+  // S_ISFIFO : Not supported. Note that the Zygote and blastulas use pipes to
+  // communicate with the child processes across forks but those should have been
+  // added to the redirection exemption list.
   if (!S_ISCHR(f_stat.st_mode) && !S_ISREG(f_stat.st_mode)) {
-    *error_msg = android::base::StringPrintf("Unsupported st_mode %u", f_stat.st_mode);
-    return nullptr;
+    std::string mode = "Unknown";
+
+    if (S_ISDIR(f_stat.st_mode)) {
+      mode = "DIR";
+    } else if (S_ISLNK(f_stat.st_mode)) {
+      mode = "LINK";
+    } else if (S_ISBLK(f_stat.st_mode)) {
+      mode = "BLOCK";
+    } else if (S_ISFIFO(f_stat.st_mode)) {
+      mode = "FIFO";
+    }
+
+    fail_fn(android::base::StringPrintf("Unsupported st_mode for FD %d:  %s", fd, mode.c_str()));
   }
 
   std::string file_path;
   const std::string fd_path = android::base::StringPrintf("/proc/self/fd/%d", fd);
   if (!android::base::Readlink(fd_path, &file_path)) {
-    *error_msg = android::base::StringPrintf("Could not read fd link %s: %s",
-                                             fd_path.c_str(),
-                                             strerror(errno));
-    return nullptr;
+    fail_fn(android::base::StringPrintf("Could not read fd link %s: %s",
+                                        fd_path.c_str(),
+                                        strerror(errno)));
   }
 
   if (!whitelist->IsAllowed(file_path)) {
-    *error_msg = std::string("Not whitelisted : ").append(file_path);
-    return nullptr;
+    fail_fn(std::string("Not whitelisted : ").append(file_path));
   }
 
   // File descriptor flags : currently on FD_CLOEXEC. We can set these
@@ -236,11 +251,10 @@
   // there won't be any races.
   const int fd_flags = TEMP_FAILURE_RETRY(fcntl(fd, F_GETFD));
   if (fd_flags == -1) {
-    *error_msg = android::base::StringPrintf("Failed fcntl(%d, F_GETFD) (%s): %s",
-                                             fd,
-                                             file_path.c_str(),
-                                             strerror(errno));
-    return nullptr;
+    fail_fn(android::base::StringPrintf("Failed fcntl(%d, F_GETFD) (%s): %s",
+                                        fd,
+                                        file_path.c_str(),
+                                        strerror(errno)));
   }
 
   // File status flags :
@@ -257,11 +271,10 @@
   //   their presence and pass them in to open().
   int fs_flags = TEMP_FAILURE_RETRY(fcntl(fd, F_GETFL));
   if (fs_flags == -1) {
-    *error_msg = android::base::StringPrintf("Failed fcntl(%d, F_GETFL) (%s): %s",
-                                             fd,
-                                             file_path.c_str(),
-                                             strerror(errno));
-    return nullptr;
+    fail_fn(android::base::StringPrintf("Failed fcntl(%d, F_GETFL) (%s): %s",
+                                        fd,
+                                        file_path.c_str(),
+                                        strerror(errno)));
   }
 
   // File offset : Ignore the offset for non seekable files.
@@ -276,7 +289,7 @@
   return new FileDescriptorInfo(f_stat, file_path, fd, open_flags, fd_flags, fs_flags, offset);
 }
 
-bool FileDescriptorInfo::Restat() const {
+bool FileDescriptorInfo::RefersToSameFile() const {
   struct stat f_stat;
   if (TEMP_FAILURE_RETRY(fstat(fd, &f_stat)) == -1) {
     PLOG(ERROR) << "Unable to restat fd " << fd;
@@ -286,9 +299,9 @@
   return f_stat.st_ino == stat.st_ino && f_stat.st_dev == stat.st_dev;
 }
 
-bool FileDescriptorInfo::ReopenOrDetach(std::string* error_msg) const {
+void FileDescriptorInfo::ReopenOrDetach(fail_fn_t fail_fn) const {
   if (is_sock) {
-    return DetachSocket(error_msg);
+    return DetachSocket(fail_fn);
   }
 
   // NOTE: This might happen if the file was unlinked after being opened.
@@ -297,57 +310,50 @@
   const int new_fd = TEMP_FAILURE_RETRY(open(file_path.c_str(), open_flags));
 
   if (new_fd == -1) {
-    *error_msg = android::base::StringPrintf("Failed open(%s, %i): %s",
-                                             file_path.c_str(),
-                                             open_flags,
-                                             strerror(errno));
-    return false;
+    fail_fn(android::base::StringPrintf("Failed open(%s, %i): %s",
+                                        file_path.c_str(),
+                                        open_flags,
+                                        strerror(errno)));
   }
 
   if (TEMP_FAILURE_RETRY(fcntl(new_fd, F_SETFD, fd_flags)) == -1) {
     close(new_fd);
-    *error_msg = android::base::StringPrintf("Failed fcntl(%d, F_SETFD, %d) (%s): %s",
-                                             new_fd,
-                                             fd_flags,
-                                             file_path.c_str(),
-                                             strerror(errno));
-    return false;
+    fail_fn(android::base::StringPrintf("Failed fcntl(%d, F_SETFD, %d) (%s): %s",
+                                        new_fd,
+                                        fd_flags,
+                                        file_path.c_str(),
+                                        strerror(errno)));
   }
 
   if (TEMP_FAILURE_RETRY(fcntl(new_fd, F_SETFL, fs_flags)) == -1) {
     close(new_fd);
-    *error_msg = android::base::StringPrintf("Failed fcntl(%d, F_SETFL, %d) (%s): %s",
-                                             new_fd,
-                                             fs_flags,
-                                             file_path.c_str(),
-                                             strerror(errno));
-    return false;
+    fail_fn(android::base::StringPrintf("Failed fcntl(%d, F_SETFL, %d) (%s): %s",
+                                        new_fd,
+                                        fs_flags,
+                                        file_path.c_str(),
+                                        strerror(errno)));
   }
 
   if (offset != -1 && TEMP_FAILURE_RETRY(lseek64(new_fd, offset, SEEK_SET)) == -1) {
     close(new_fd);
-    *error_msg = android::base::StringPrintf("Failed lseek64(%d, SEEK_SET) (%s): %s",
-                                             new_fd,
-                                             file_path.c_str(),
-                                             strerror(errno));
-    return false;
+    fail_fn(android::base::StringPrintf("Failed lseek64(%d, SEEK_SET) (%s): %s",
+                                        new_fd,
+                                        file_path.c_str(),
+                                        strerror(errno)));
   }
 
-  int dupFlags = (fd_flags & FD_CLOEXEC) ? O_CLOEXEC : 0;
-  if (TEMP_FAILURE_RETRY(dup3(new_fd, fd, dupFlags)) == -1) {
+  int dup_flags = (fd_flags & FD_CLOEXEC) ? O_CLOEXEC : 0;
+  if (TEMP_FAILURE_RETRY(dup3(new_fd, fd, dup_flags)) == -1) {
     close(new_fd);
-    *error_msg = android::base::StringPrintf("Failed dup3(%d, %d, %d) (%s): %s",
-                                             fd,
-                                             new_fd,
-                                             dupFlags,
-                                             file_path.c_str(),
-                                             strerror(errno));
-    return false;
+    fail_fn(android::base::StringPrintf("Failed dup3(%d, %d, %d) (%s): %s",
+                                        fd,
+                                        new_fd,
+                                        dup_flags,
+                                        file_path.c_str(),
+                                        strerror(errno)));
   }
 
   close(new_fd);
-
-  return true;
 }
 
 FileDescriptorInfo::FileDescriptorInfo(int fd) :
@@ -373,7 +379,6 @@
   is_sock(false) {
 }
 
-// static
 bool FileDescriptorInfo::GetSocketName(const int fd, std::string* result) {
   sockaddr_storage ss;
   sockaddr* addr = reinterpret_cast<sockaddr*>(&ss);
@@ -417,86 +422,75 @@
   return true;
 }
 
-bool FileDescriptorInfo::DetachSocket(std::string* error_msg) const {
-  const int dev_null_fd = open("/dev/null", O_RDWR);
+void FileDescriptorInfo::DetachSocket(fail_fn_t fail_fn) const {
+  const int dev_null_fd = open("/dev/null", O_RDWR | O_CLOEXEC);
   if (dev_null_fd < 0) {
-    *error_msg = std::string("Failed to open /dev/null: ").append(strerror(errno));
-    return false;
+    fail_fn(std::string("Failed to open /dev/null: ").append(strerror(errno)));
   }
 
-  if (dup2(dev_null_fd, fd) == -1) {
-    *error_msg = android::base::StringPrintf("Failed dup2 on socket descriptor %d: %s",
-                                             fd,
-                                             strerror(errno));
-    return false;
+  if (dup3(dev_null_fd, fd, O_CLOEXEC) == -1) {
+    fail_fn(android::base::StringPrintf("Failed dup3 on socket descriptor %d: %s",
+                                        fd,
+                                        strerror(errno)));
   }
 
   if (close(dev_null_fd) == -1) {
-    *error_msg = android::base::StringPrintf("Failed close(%d): %s", dev_null_fd, strerror(errno));
-    return false;
+    fail_fn(android::base::StringPrintf("Failed close(%d): %s", dev_null_fd, strerror(errno)));
   }
-
-  return true;
 }
 
 // static
 FileDescriptorTable* FileDescriptorTable::Create(const std::vector<int>& fds_to_ignore,
-                                                 std::string* error_msg) {
-  DIR* d = opendir(kFdPath);
-  if (d == nullptr) {
-    *error_msg = std::string("Unable to open directory ").append(kFdPath);
-    return nullptr;
+                                                 fail_fn_t fail_fn) {
+  DIR* proc_fd_dir = opendir(kFdPath);
+  if (proc_fd_dir == nullptr) {
+    fail_fn(std::string("Unable to open directory ").append(kFdPath));
   }
-  int dir_fd = dirfd(d);
-  dirent* e;
+
+  int dir_fd = dirfd(proc_fd_dir);
+  dirent* dir_entry;
 
   std::unordered_map<int, FileDescriptorInfo*> open_fd_map;
-  while ((e = readdir(d)) != NULL) {
-    const int fd = ParseFd(e, dir_fd);
+  while ((dir_entry = readdir(proc_fd_dir)) != nullptr) {
+    const int fd = ParseFd(dir_entry, dir_fd);
     if (fd == -1) {
       continue;
     }
+
     if (std::find(fds_to_ignore.begin(), fds_to_ignore.end(), fd) != fds_to_ignore.end()) {
       LOG(INFO) << "Ignoring open file descriptor " << fd;
       continue;
     }
 
-    FileDescriptorInfo* info = FileDescriptorInfo::CreateFromFd(fd, error_msg);
-    if (info == NULL) {
-      if (closedir(d) == -1) {
-        PLOG(ERROR) << "Unable to close directory";
-      }
-      return NULL;
-    }
-    open_fd_map[fd] = info;
+    open_fd_map[fd] = FileDescriptorInfo::CreateFromFd(fd, fail_fn);
   }
 
-  if (closedir(d) == -1) {
-    *error_msg = "Unable to close directory";
-    return nullptr;
+  if (closedir(proc_fd_dir) == -1) {
+    fail_fn("Unable to close directory");
   }
+
   return new FileDescriptorTable(open_fd_map);
 }
 
-bool FileDescriptorTable::Restat(const std::vector<int>& fds_to_ignore, std::string* error_msg) {
+void FileDescriptorTable::Restat(const std::vector<int>& fds_to_ignore, fail_fn_t fail_fn) {
   std::set<int> open_fds;
 
   // First get the list of open descriptors.
-  DIR* d = opendir(kFdPath);
-  if (d == NULL) {
-    *error_msg = android::base::StringPrintf("Unable to open directory %s: %s",
-                                             kFdPath,
-                                             strerror(errno));
-    return false;
+  DIR* proc_fd_dir = opendir(kFdPath);
+  if (proc_fd_dir == nullptr) {
+    fail_fn(android::base::StringPrintf("Unable to open directory %s: %s",
+                                        kFdPath,
+                                        strerror(errno)));
   }
 
-  int dir_fd = dirfd(d);
-  dirent* e;
-  while ((e = readdir(d)) != NULL) {
-    const int fd = ParseFd(e, dir_fd);
+  int dir_fd = dirfd(proc_fd_dir);
+  dirent* dir_entry;
+  while ((dir_entry = readdir(proc_fd_dir)) != nullptr) {
+    const int fd = ParseFd(dir_entry, dir_fd);
     if (fd == -1) {
       continue;
     }
+
     if (std::find(fds_to_ignore.begin(), fds_to_ignore.end(), fd) != fds_to_ignore.end()) {
       LOG(INFO) << "Ignoring open file descriptor " << fd;
       continue;
@@ -505,27 +499,24 @@
     open_fds.insert(fd);
   }
 
-  if (closedir(d) == -1) {
-    *error_msg = android::base::StringPrintf("Unable to close directory: %s", strerror(errno));
-    return false;
+  if (closedir(proc_fd_dir) == -1) {
+    fail_fn(android::base::StringPrintf("Unable to close directory: %s", strerror(errno)));
   }
 
-  return RestatInternal(open_fds, error_msg);
+  RestatInternal(open_fds, fail_fn);
 }
 
-// Reopens all file descriptors that are contained in the table. Returns true
-// if all descriptors were successfully re-opened or detached, and false if an
-// error occurred.
-bool FileDescriptorTable::ReopenOrDetach(std::string* error_msg) {
+// Reopens all file descriptors that are contained in the table.
+void FileDescriptorTable::ReopenOrDetach(fail_fn_t fail_fn) {
   std::unordered_map<int, FileDescriptorInfo*>::const_iterator it;
   for (it = open_fd_map_.begin(); it != open_fd_map_.end(); ++it) {
     const FileDescriptorInfo* info = it->second;
-    if (info == NULL || !info->ReopenOrDetach(error_msg)) {
-      return false;
+    if (info == nullptr) {
+      return;
+    } else {
+      info->ReopenOrDetach(fail_fn);
     }
   }
-
-  return true;
 }
 
 FileDescriptorTable::FileDescriptorTable(
@@ -533,9 +524,7 @@
     : open_fd_map_(map) {
 }
 
-bool FileDescriptorTable::RestatInternal(std::set<int>& open_fds, std::string* error_msg) {
-  bool error = false;
-
+void FileDescriptorTable::RestatInternal(std::set<int>& open_fds, fail_fn_t fail_fn) {
   // Iterate through the list of file descriptors we've already recorded
   // and check whether :
   //
@@ -558,28 +547,18 @@
     } else {
       // The entry from the file descriptor table is still open. Restat
       // it and check whether it refers to the same file.
-      const bool same_file = it->second->Restat();
-      if (!same_file) {
+      if (!it->second->RefersToSameFile()) {
         // The file descriptor refers to a different description. We must
         // update our entry in the table.
         delete it->second;
-        it->second = FileDescriptorInfo::CreateFromFd(*element, error_msg);
-        if (it->second == NULL) {
-          // The descriptor no longer no longer refers to a whitelisted file.
-          // We flag an error and remove it from the list of files we're
-          // tracking.
-          error = true;
-          it = open_fd_map_.erase(it);
-        } else {
-          // Successfully restatted the file, move on to the next open FD.
-          ++it;
-        }
+        it->second = FileDescriptorInfo::CreateFromFd(*element, fail_fn);
       } else {
         // It's the same file. Nothing to do here. Move on to the next open
         // FD.
-        ++it;
       }
 
+      ++it;
+
       // Finally, remove the FD from the set of open_fds. We do this last because
       // |element| will not remain valid after a call to erase.
       open_fds.erase(element);
@@ -598,25 +577,15 @@
     std::set<int>::const_iterator it;
     for (it = open_fds.begin(); it != open_fds.end(); ++it) {
       const int fd = (*it);
-      FileDescriptorInfo* info = FileDescriptorInfo::CreateFromFd(fd, error_msg);
-      if (info == NULL) {
-        // A newly opened file is not on the whitelist. Flag an error and
-        // continue.
-        error = true;
-      } else {
-        // Track the newly opened file.
-        open_fd_map_[fd] = info;
-      }
+      open_fd_map_[fd] = FileDescriptorInfo::CreateFromFd(fd, fail_fn);
     }
   }
-
-  return !error;
 }
 
 // static
-int FileDescriptorTable::ParseFd(dirent* e, int dir_fd) {
+int FileDescriptorTable::ParseFd(dirent* dir_entry, int dir_fd) {
   char* end;
-  const int fd = strtol(e->d_name, &end, 10);
+  const int fd = strtol(dir_entry->d_name, &end, 10);
   if ((*end) != '\0') {
     return -1;
   }
diff --git a/core/jni/fd_utils.h b/core/jni/fd_utils.h
index 09022a2..2caf157 100644
--- a/core/jni/fd_utils.h
+++ b/core/jni/fd_utils.h
@@ -30,6 +30,9 @@
 
 class FileDescriptorInfo;
 
+// This type is duplicated in com_android_internal_os_Zygote.cpp
+typedef const std::function<void(std::string)>& fail_fn_t;
+
 // Whitelist of open paths that the zygote is allowed to keep open.
 //
 // In addition to the paths listed in kPathWhitelist in file_utils.cpp, and
@@ -76,19 +79,19 @@
   // /proc/self/fd for the list of open file descriptors and collects
   // information about them. Returns NULL if an error occurs.
   static FileDescriptorTable* Create(const std::vector<int>& fds_to_ignore,
-                                     std::string* error_msg);
+                                     fail_fn_t fail_fn);
 
-  bool Restat(const std::vector<int>& fds_to_ignore, std::string* error_msg);
+  void Restat(const std::vector<int>& fds_to_ignore, fail_fn_t fail_fn);
 
   // Reopens all file descriptors that are contained in the table. Returns true
   // if all descriptors were successfully re-opened or detached, and false if an
   // error occurred.
-  bool ReopenOrDetach(std::string* error_msg);
+  void ReopenOrDetach(fail_fn_t fail_fn);
 
  private:
   explicit FileDescriptorTable(const std::unordered_map<int, FileDescriptorInfo*>& map);
 
-  bool RestatInternal(std::set<int>& open_fds, std::string* error_msg);
+  void RestatInternal(std::set<int>& open_fds, fail_fn_t fail_fn);
 
   static int ParseFd(dirent* e, int dir_fd);
 
diff --git a/core/proto/android/app/settings_enums.proto b/core/proto/android/app/settings_enums.proto
index f68c760..eb716ac 100644
--- a/core/proto/android/app/settings_enums.proto
+++ b/core/proto/android/app/settings_enums.proto
@@ -2179,4 +2179,14 @@
     // OPEN: Settings > Network & internet > Click Mobile network to land on a page with a list of
     // SIM/eSIM subscriptions.
     MOBILE_NETWORK_LIST = 1627;
+
+    // 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/debug/enums.proto b/core/proto/android/debug/enums.proto
new file mode 100644
index 0000000..6747bb7
--- /dev/null
+++ b/core/proto/android/debug/enums.proto
@@ -0,0 +1,67 @@
+/*
+ * 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.debug;
+
+option java_outer_classname = "AdbProtoEnums";
+option java_multiple_files = true;
+
+/**
+ * adb connection state used to track adb connection changes in AdbDebuggingManager.java.
+ */
+enum AdbConnectionStateEnum {
+    UNKNOWN = 0;
+
+    /**
+     * The adb connection is waiting for approval from the user.
+     */
+    AWAITING_USER_APPROVAL = 1;
+
+    /**
+     * The user allowed the adb connection from the system.
+     */
+    USER_ALLOWED = 2;
+
+    /**
+     * The user denied the adb connection from the system.
+     */
+    USER_DENIED = 3;
+
+    /**
+     * The adb connection was automatically allowed without user interaction due to the system
+     * being previously allowed by the user with the 'always allow' option selected, and the adb
+     * grant has not yet expired.
+     */
+    AUTOMATICALLY_ALLOWED = 4;
+
+    /**
+     * An empty or invalid base64 encoded key was provided to the framework; the connection was
+     * automatically denied.
+     */
+    DENIED_INVALID_KEY = 5;
+
+    /**
+     * vold decrypt has not yet occurred; the connection was automatically denied.
+     */
+    DENIED_VOLD_DECRYPT = 6;
+
+    /**
+     * The adb session has been disconnected.
+     */
+    DISCONNECTED = 7;
+}
+
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..aaf6c63 100644
--- a/core/proto/android/providers/settings/secure.proto
+++ b/core/proto/android/providers/settings/secure.proto
@@ -528,8 +528,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 7f3ea7a..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;
 
@@ -221,6 +226,8 @@
     optional bool use_heartbeats = 23;
 
     message TimeController {
+        option (.android.msg_privacy).dest = DEST_AUTOMATIC;
+
         // Whether or not TimeController should skip setting wakeup alarms for jobs that aren't
         // ready now.
         optional bool skip_not_ready_jobs = 1;
@@ -228,6 +235,8 @@
     optional TimeController time_controller = 25;
 
     message QuotaController {
+        option (.android.msg_privacy).dest = DEST_AUTOMATIC;
+
         // How much time each app will have to run jobs within their standby bucket window.
         optional int64 allowed_time_per_period_ms = 1;
         // How much time the package should have before transitioning from out-of-quota to in-quota.
@@ -251,10 +260,57 @@
         optional int64 rare_window_size_ms = 6;
         // The maximum amount of time an app can have its jobs running within a 24 hour window.
         optional int64 max_execution_time_ms = 7;
+        // The maximum number of jobs an app can run within this particular standby bucket's
+        // window size.
+        optional int32 max_job_count_active = 8;
+        // The maximum number of jobs an app can run within this particular standby bucket's
+        // window size.
+        optional int32 max_job_count_working = 9;
+        // The maximum number of jobs an app can run within this particular standby bucket's
+        // window size.
+        optional int32 max_job_count_frequent = 10;
+        // The maximum number of jobs an app can run within this particular standby bucket's
+        // window size.
+        optional int32 max_job_count_rare = 11;
+        // The maximum number of jobs that should be allowed to run in the past
+        // {@link QUOTA_CONTROLLER_ALLOWED_TIME_PER_PERIOD_MS}.
+        optional int32 max_job_count_per_allowed_time = 12;
     }
     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 {
@@ -788,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/server/usagestatsservice.proto b/core/proto/android/server/usagestatsservice.proto
index 528c1a4..050ec7a 100644
--- a/core/proto/android/server/usagestatsservice.proto
+++ b/core/proto/android/server/usagestatsservice.proto
@@ -88,6 +88,11 @@
     // If class field is an Activity, instance_id is a unique id of the
     // Activity object.
     optional int32 instance_id = 14;
+    // task_root_package_index contains the index + 1 of the task root package name in the string
+    // pool
+    optional int32 task_root_package_index = 15;
+    // task_root_class_index contains the index + 1 of the task root class name in the string pool
+    optional int32 task_root_class_index = 16;
   }
 
   // The following fields contain supplemental data used to build IntervalStats, such as a string
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 ea0c8e2..4a54bd7 100644
--- a/core/res/AndroidManifest.xml
+++ b/core/res/AndroidManifest.xml
@@ -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 -->
     <!-- ======================================= -->
@@ -1748,6 +1754,10 @@
     <permission android:name="android.permission.MANAGE_BLUETOOTH_WHEN_WIRELESS_CONSENT_REQUIRED"
         android:protectionLevel="signature" />
 
+    <!-- @hide Allows the device to be reset, clearing all data and enables Test Harness Mode. -->
+    <permission android:name="android.permission.ENABLE_TEST_HARNESS_MODE"
+        android:protectionLevel="signature" />
+
     <!-- ================================== -->
     <!-- Permissions for accessing accounts -->
     <!-- ================================== -->
@@ -2109,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" />
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 5995640..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>
 
@@ -381,7 +384,7 @@
 
 
     <!-- Configuration of Ethernet interfaces in the following format:
-         <interface name|mac address>;[Network Capabilities];[IP config]
+         <interface name|mac address>;[Network Capabilities];[IP config];[Override Transport]
          Where
                [Network Capabilities] Optional. A comma seprated list of network capabilities.
                    Values must be from NetworkCapabilities#NET_CAPABILITIES_* constants.
@@ -389,11 +392,16 @@
                    use the following format to specify static IP configuration:
                        ip=<ip-address/mask> gateway=<ip-address> dns=<comma-sep-ip-addresses>
                        domains=<comma-sep-domains>
+               [Override Transport] Optional. An override network transport type to allow
+                    the propagation of an interface type on the other end of a local Ethernet
+                    interface. Value must be from NetworkCapabilities#TRANSPORT_* constants. If
+                    left out, this will default to TRANSPORT_ETHERNET.
          -->
     <string-array translatable="false" name="config_ethernet_interfaces">
         <!--
         <item>eth1;12,13,14,15;ip=192.168.0.10/24 gateway=192.168.0.1 dns=4.4.4.4,8.8.8.8</item>
         <item>eth2;;ip=192.168.0.11/24</item>
+        <item>eth3;12,13,14,15;ip=192.168.0.12/24;1</item>
         -->
     </string-array>
 
@@ -525,6 +533,9 @@
     <!-- Boolean indicating whether the wifi chipset has dual frequency band support -->
     <bool translatable="false" name="config_wifi_dual_band_support">false</bool>
 
+    <!-- Maximum number of concurrent WiFi interfaces in AP mode -->
+    <integer translatable="false" name="config_wifi_max_ap_interfaces">1</integer>
+
     <!-- Boolean indicating whether the wifi chipset requires the softap band be -->
     <!-- converted from 5GHz to ANY due to hardware restrictions -->
     <bool translatable="false" name="config_wifi_convert_apband_5ghz_to_any">false</bool>
@@ -705,6 +716,9 @@
          Software implementation will be used if config_hardware_auto_brightness_available is not set -->
     <bool name="config_automatic_brightness_available">false</bool>
 
+    <!-- Flag indicating whether we should enable the adaptive sleep.-->
+    <bool name="config_adaptive_sleep_available">false</bool>
+
     <!-- Flag indicating whether we should enable smart battery. -->
     <bool name="config_smart_battery_available">false</bool>
 
@@ -931,6 +945,10 @@
          in hardware. -->
     <bool name="config_setColorTransformAccelerated">false</bool>
 
+    <!-- Boolean indicating whether the HWC setColorTransform function can be performed efficiently
+         in hardware for individual layers. -->
+    <bool name="config_setColorTransformAcceleratedPerLayer">false</bool>
+
     <!-- Control whether Night display is available. This should only be enabled on devices
          that have a HWC implementation that can apply the matrix passed to setColorTransform
          without impacting power, performance, and app compatibility (e.g. protected content). -->
@@ -939,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>
@@ -1743,6 +1761,19 @@
          config_enableGeofenceOverlay is false. -->
     <string name="config_geofenceProviderPackageName" translatable="false">@null</string>
 
+    <!-- Whether to enable Hardware Activity-Recognition overlay which allows Hardware
+         Activity-Recognition to be replaced by an app at run-time. When disabled, only the
+         config_activityRecognitionHardwarePackageName package will be searched for
+         its implementation, otherwise packages whose signature matches the
+         signatures of config_locationProviderPackageNames will be searched, and
+         the service with the highest version number will be picked. Anyone who
+         wants to disable the overlay mechanism can set it to false.
+         -->
+    <bool name="config_enableActivityRecognitionHardwareOverlay" translatable="false">true</bool>
+    <!-- Package name providing Hardware Activity-Recognition API support. Used only when
+         config_enableActivityRecognitionHardwareOverlay is false. -->
+    <string name="config_activityRecognitionHardwarePackageName" translatable="false">@null</string>
+
     <!-- Package name(s) containing location provider support.
          These packages can contain services implementing location providers,
          such as the Geocode Provider, Network Location Provider, and
@@ -2180,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
@@ -3698,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). -->
@@ -3728,4 +3760,10 @@
 
     <!-- Enable Zram writeback feature to allow unused pages in zram be written to flash. -->
     <bool name="config_zramWriteback">false</bool>
+
+    <!-- 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 0878562..a761baf 100644
--- a/core/res/res/values/strings.xml
+++ b/core/res/res/values/strings.xml
@@ -3353,9 +3353,9 @@
     <string name="wifi_available_action_all_networks">All networks</string>
 
     <!-- Notification title for a connection to a app suggested wireless network.-->
-    <string name="wifi_suggestion_title">Connected to Wi\u2011Fi network proposed by <xliff:g id="name" example="App123">%s</xliff:g></string>
+    <string name="wifi_suggestion_title">A Wi\u2011Fi network proposed by <xliff:g id="name" example="App123">%s</xliff:g> is available</string>
     <!-- Notification content for a connection to a app suggested wireless network.-->
-    <string name="wifi_suggestion_content">Do you want to let <xliff:g id="name" example="App123">%s</xliff:g> propose networks for you?</string>
+    <string name="wifi_suggestion_content">Do you want to connect to networks proposed by <xliff:g id="name" example="App123">%s</xliff:g>?</string>
     <!-- Notification action for allowing app specified in the notification body.-->
     <string name="wifi_suggestion_action_allow_app">Yes</string>
     <!-- Notification action for disallowing app specified in the notification body.-->
@@ -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 53c33a3..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" />
@@ -1845,6 +1847,7 @@
   <java-symbol type="array" name="config_defaultNotificationVibePattern" />
   <java-symbol type="array" name="config_notificationFallbackVibePattern" />
   <java-symbol type="bool" name="config_useAttentionLight" />
+  <java-symbol type="bool" name="config_adaptive_sleep_available" />
   <java-symbol type="bool" name="config_animateScreenLights" />
   <java-symbol type="bool" name="config_automatic_brightness_available" />
   <java-symbol type="bool" name="config_smart_battery_available" />
@@ -1853,6 +1856,7 @@
   <java-symbol type="bool" name="config_enableNightMode" />
   <java-symbol type="bool" name="config_tintNotificationActionButtons" />
   <java-symbol type="bool" name="config_dozeAfterScreenOffByDefault" />
+  <java-symbol type="bool" name="config_enableActivityRecognitionHardwareOverlay" />
   <java-symbol type="bool" name="config_enableFusedLocationOverlay" />
   <java-symbol type="bool" name="config_enableHardwareFlpOverlay" />
   <java-symbol type="bool" name="config_enableGeocoderOverlay" />
@@ -1877,6 +1881,7 @@
   <java-symbol type="bool" name="config_supportLongPressPowerWhenNonInteractive" />
   <java-symbol type="bool" name="config_wifi_background_scan_support" />
   <java-symbol type="bool" name="config_wifi_dual_band_support" />
+  <java-symbol type="integer" name="config_wifi_max_ap_interfaces" />
   <java-symbol type="bool" name="config_wifi_convert_apband_5ghz_to_any" />
   <java-symbol type="bool" name="config_wifi_local_only_hotspot_5ghz" />
   <java-symbol type="bool" name="config_wifi_connected_mac_randomization_supported" />
@@ -2021,6 +2026,7 @@
   <java-symbol type="string" name="car_mode_disable_notification_title" />
   <java-symbol type="string" name="chooser_wallpaper" />
   <java-symbol type="string" name="config_datause_iface" />
+  <java-symbol type="string" name="config_activityRecognitionHardwarePackageName" />
   <java-symbol type="string" name="config_fusedLocationProviderPackageName" />
   <java-symbol type="string" name="config_hardwareFlpPackageName" />
   <java-symbol type="string" name="config_geocoderProviderPackageName" />
@@ -2100,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" />
@@ -3031,6 +3041,7 @@
   <java-symbol type="drawable" name="ic_doc_generic" />
 
   <java-symbol type="bool" name="config_setColorTransformAccelerated" />
+  <java-symbol type="bool" name="config_setColorTransformAcceleratedPerLayer" />
   <java-symbol type="bool" name="config_displayWhiteBalanceAvailable" />
   <java-symbol type="bool" name="config_nightDisplayAvailable" />
   <java-symbol type="bool" name="config_allowDisablingAssistDisclosure" />
@@ -3517,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" />
@@ -3540,4 +3549,13 @@
   <java-symbol type="bool" name="config_silenceSensorAvailable" />
 
   <java-symbol type="bool" name="config_zramWriteback" />
+
+  <!-- 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..8e8b07a 100644
--- a/core/tests/coretests/Android.mk
+++ b/core/tests/coretests/Android.mk
@@ -49,6 +49,7 @@
 LOCAL_JAVA_LIBRARIES := \
     android.test.runner \
     telephony-common \
+    testables \
     org.apache.http.legacy \
     android.test.base \
     android.test.mock \
diff --git a/core/tests/coretests/src/android/app/admin/PasswordMetricsTest.java b/core/tests/coretests/src/android/app/admin/PasswordMetricsTest.java
index 8d42c74..5731daa 100644
--- a/core/tests/coretests/src/android/app/admin/PasswordMetricsTest.java
+++ b/core/tests/coretests/src/android/app/admin/PasswordMetricsTest.java
@@ -20,12 +20,23 @@
 import static android.app.admin.DevicePolicyManager.PASSWORD_COMPLEXITY_LOW;
 import static android.app.admin.DevicePolicyManager.PASSWORD_COMPLEXITY_MEDIUM;
 import static android.app.admin.DevicePolicyManager.PASSWORD_COMPLEXITY_NONE;
+import static android.app.admin.DevicePolicyManager.PASSWORD_QUALITY_ALPHABETIC;
+import static android.app.admin.DevicePolicyManager.PASSWORD_QUALITY_ALPHANUMERIC;
+import static android.app.admin.DevicePolicyManager.PASSWORD_QUALITY_COMPLEX;
+import static android.app.admin.DevicePolicyManager.PASSWORD_QUALITY_NUMERIC;
+import static android.app.admin.DevicePolicyManager.PASSWORD_QUALITY_NUMERIC_COMPLEX;
 import static android.app.admin.DevicePolicyManager.PASSWORD_QUALITY_SOMETHING;
+import static android.app.admin.DevicePolicyManager.PASSWORD_QUALITY_UNSPECIFIED;
+import static android.app.admin.PasswordMetrics.complexityLevelToMinQuality;
+import static android.app.admin.PasswordMetrics.getActualRequiredQuality;
+import static android.app.admin.PasswordMetrics.getMinimumMetrics;
+import static android.app.admin.PasswordMetrics.getTargetQualityMetrics;
+import static android.app.admin.PasswordMetrics.sanitizeComplexityLevel;
 
 import static org.junit.Assert.assertEquals;
 import static org.junit.Assert.assertNotEquals;
+import static org.junit.Assert.assertTrue;
 
-import android.app.admin.PasswordMetrics.PasswordComplexityBucket;
 import android.os.Parcel;
 
 import androidx.test.filters.SmallTest;
@@ -109,7 +120,7 @@
         assertEquals(DevicePolicyManager.PASSWORD_QUALITY_NUMERIC_COMPLEX,
                 PasswordMetrics.computeForPassword("1").quality);
         // contains a long sequence so isn't complex
-        assertEquals(DevicePolicyManager.PASSWORD_QUALITY_NUMERIC,
+        assertEquals(PASSWORD_QUALITY_NUMERIC,
                 PasswordMetrics.computeForPassword("1234").quality);
         assertEquals(DevicePolicyManager.PASSWORD_QUALITY_UNSPECIFIED,
                 PasswordMetrics.computeForPassword("").quality);
@@ -145,7 +156,7 @@
                 new PasswordMetrics(DevicePolicyManager.PASSWORD_QUALITY_SOMETHING, 5));
 
         assertNotEquals(new PasswordMetrics(DevicePolicyManager.PASSWORD_QUALITY_SOMETHING, 4),
-                new PasswordMetrics(DevicePolicyManager.PASSWORD_QUALITY_COMPLEX, 4));
+                new PasswordMetrics(PASSWORD_QUALITY_COMPLEX, 4));
 
         metrics0 = PasswordMetrics.computeForPassword("1234abcd,./");
         metrics1 = PasswordMetrics.computeForPassword("1234abcd,./");
@@ -176,9 +187,9 @@
     @Test
     public void testConstructQuality() {
         PasswordMetrics expected = new PasswordMetrics();
-        expected.quality = DevicePolicyManager.PASSWORD_QUALITY_COMPLEX;
+        expected.quality = PASSWORD_QUALITY_COMPLEX;
 
-        PasswordMetrics actual = new PasswordMetrics(DevicePolicyManager.PASSWORD_QUALITY_COMPLEX);
+        PasswordMetrics actual = new PasswordMetrics(PASSWORD_QUALITY_COMPLEX);
 
         assertEquals(expected, actual);
     }
@@ -256,42 +267,178 @@
     }
 
     @Test
-    public void testComplexityLevelToBucket_none() {
-        PasswordMetrics[] bucket = PasswordComplexityBucket.complexityLevelToBucket(
-                PASSWORD_COMPLEXITY_NONE).getMetrics();
+    public void testSanitizeComplexityLevel_none() {
+        assertEquals(PASSWORD_COMPLEXITY_NONE, sanitizeComplexityLevel(PASSWORD_COMPLEXITY_NONE));
 
-        for (PasswordMetrics metrics : bucket) {
-            assertEquals(PASSWORD_COMPLEXITY_NONE, metrics.determineComplexity());
-        }
     }
 
     @Test
-    public void testComplexityLevelToBucket_low() {
-        PasswordMetrics[] bucket = PasswordComplexityBucket.complexityLevelToBucket(
-                PASSWORD_COMPLEXITY_LOW).getMetrics();
-
-        for (PasswordMetrics metrics : bucket) {
-            assertEquals(PASSWORD_COMPLEXITY_LOW, metrics.determineComplexity());
-        }
+    public void testSanitizeComplexityLevel_low() {
+        assertEquals(PASSWORD_COMPLEXITY_LOW, sanitizeComplexityLevel(PASSWORD_COMPLEXITY_LOW));
     }
 
     @Test
-    public void testComplexityLevelToBucket_medium() {
-        PasswordMetrics[] bucket = PasswordComplexityBucket.complexityLevelToBucket(
-                PASSWORD_COMPLEXITY_MEDIUM).getMetrics();
-
-        for (PasswordMetrics metrics : bucket) {
-            assertEquals(PASSWORD_COMPLEXITY_MEDIUM, metrics.determineComplexity());
-        }
+    public void testSanitizeComplexityLevel_medium() {
+        assertEquals(
+                PASSWORD_COMPLEXITY_MEDIUM, sanitizeComplexityLevel(PASSWORD_COMPLEXITY_MEDIUM));
     }
 
     @Test
-    public void testComplexityLevelToBucket_high() {
-        PasswordMetrics[] bucket = PasswordComplexityBucket.complexityLevelToBucket(
-                PASSWORD_COMPLEXITY_HIGH).getMetrics();
+    public void testSanitizeComplexityLevel_high() {
+        assertEquals(PASSWORD_COMPLEXITY_HIGH, sanitizeComplexityLevel(PASSWORD_COMPLEXITY_HIGH));
+    }
 
-        for (PasswordMetrics metrics : bucket) {
-            assertEquals(PASSWORD_COMPLEXITY_HIGH, metrics.determineComplexity());
-        }
+    @Test
+    public void testSanitizeComplexityLevel_invalid() {
+        assertEquals(PASSWORD_COMPLEXITY_NONE, sanitizeComplexityLevel(-1));
+    }
+
+    @Test
+    public void testComplexityLevelToMinQuality_none() {
+        assertEquals(PASSWORD_QUALITY_UNSPECIFIED,
+                complexityLevelToMinQuality(PASSWORD_COMPLEXITY_NONE));
+    }
+
+    @Test
+    public void testComplexityLevelToMinQuality_low() {
+        assertEquals(PASSWORD_QUALITY_SOMETHING,
+                complexityLevelToMinQuality(PASSWORD_COMPLEXITY_LOW));
+    }
+
+    @Test
+    public void testComplexityLevelToMinQuality_medium() {
+        assertEquals(PASSWORD_QUALITY_NUMERIC_COMPLEX,
+                complexityLevelToMinQuality(PASSWORD_COMPLEXITY_MEDIUM));
+    }
+
+    @Test
+    public void testComplexityLevelToMinQuality_high() {
+        assertEquals(PASSWORD_QUALITY_NUMERIC_COMPLEX,
+                complexityLevelToMinQuality(PASSWORD_COMPLEXITY_HIGH));
+    }
+
+    @Test
+    public void testComplexityLevelToMinQuality_invalid() {
+        assertEquals(PASSWORD_QUALITY_UNSPECIFIED, complexityLevelToMinQuality(-1));
+    }
+
+    @Test
+    public void testGetTargetQualityMetrics_noneComplexityReturnsDefaultMetrics() {
+        PasswordMetrics metrics =
+                getTargetQualityMetrics(PASSWORD_COMPLEXITY_NONE, PASSWORD_QUALITY_ALPHANUMERIC);
+
+        assertTrue(metrics.isDefault());
+    }
+
+    @Test
+    public void testGetTargetQualityMetrics_qualityNotAllowedReturnsMinQualityMetrics() {
+        PasswordMetrics metrics =
+                getTargetQualityMetrics(PASSWORD_COMPLEXITY_MEDIUM, PASSWORD_QUALITY_NUMERIC);
+
+        assertEquals(PASSWORD_QUALITY_NUMERIC_COMPLEX, metrics.quality);
+        assertEquals(/* expected= */ 4, metrics.length);
+    }
+
+    @Test
+    public void testGetTargetQualityMetrics_highComplexityNumericComplex() {
+        PasswordMetrics metrics = getTargetQualityMetrics(
+                PASSWORD_COMPLEXITY_HIGH, PASSWORD_QUALITY_NUMERIC_COMPLEX);
+
+        assertEquals(PASSWORD_QUALITY_NUMERIC_COMPLEX, metrics.quality);
+        assertEquals(/* expected= */ 8, metrics.length);
+    }
+
+    @Test
+    public void testGetTargetQualityMetrics_mediumComplexityAlphabetic() {
+        PasswordMetrics metrics = getTargetQualityMetrics(
+                PASSWORD_COMPLEXITY_MEDIUM, PASSWORD_QUALITY_ALPHABETIC);
+
+        assertEquals(PASSWORD_QUALITY_ALPHABETIC, metrics.quality);
+        assertEquals(/* expected= */ 4, metrics.length);
+    }
+
+    @Test
+    public void testGetTargetQualityMetrics_lowComplexityAlphanumeric() {
+        PasswordMetrics metrics = getTargetQualityMetrics(
+                PASSWORD_COMPLEXITY_MEDIUM, PASSWORD_QUALITY_ALPHANUMERIC);
+
+        assertEquals(PASSWORD_QUALITY_ALPHANUMERIC, metrics.quality);
+        assertEquals(/* expected= */ 4, metrics.length);
+    }
+
+    @Test
+    public void testGetActualRequiredQuality_nonComplex() {
+        int actual = getActualRequiredQuality(
+                PASSWORD_QUALITY_NUMERIC_COMPLEX,
+                /* requiresNumeric= */ false,
+                /* requiresLettersOrSymbols= */ false);
+
+        assertEquals(PASSWORD_QUALITY_NUMERIC_COMPLEX, actual);
+    }
+
+    @Test
+    public void testGetActualRequiredQuality_complexRequiresNone() {
+        int actual = getActualRequiredQuality(
+                PASSWORD_QUALITY_COMPLEX,
+                /* requiresNumeric= */ false,
+                /* requiresLettersOrSymbols= */ false);
+
+        assertEquals(PASSWORD_QUALITY_UNSPECIFIED, actual);
+    }
+
+    @Test
+    public void testGetActualRequiredQuality_complexRequiresNumeric() {
+        int actual = getActualRequiredQuality(
+                PASSWORD_QUALITY_COMPLEX,
+                /* requiresNumeric= */ true,
+                /* requiresLettersOrSymbols= */ false);
+
+        assertEquals(PASSWORD_QUALITY_NUMERIC, actual);
+    }
+
+    @Test
+    public void testGetActualRequiredQuality_complexRequiresLetters() {
+        int actual = getActualRequiredQuality(
+                PASSWORD_QUALITY_COMPLEX,
+                /* requiresNumeric= */ false,
+                /* requiresLettersOrSymbols= */ true);
+
+        assertEquals(PASSWORD_QUALITY_ALPHABETIC, actual);
+    }
+
+    @Test
+    public void testGetActualRequiredQuality_complexRequiresNumericAndLetters() {
+        int actual = getActualRequiredQuality(
+                PASSWORD_QUALITY_COMPLEX,
+                /* requiresNumeric= */ true,
+                /* requiresLettersOrSymbols= */ true);
+
+        assertEquals(PASSWORD_QUALITY_ALPHANUMERIC, actual);
+    }
+
+    @Test
+    public void testGetMinimumMetrics_userInputStricter() {
+        PasswordMetrics metrics = getMinimumMetrics(
+                PASSWORD_COMPLEXITY_HIGH,
+                PASSWORD_QUALITY_ALPHANUMERIC,
+                PASSWORD_QUALITY_NUMERIC,
+                /* requiresNumeric= */ false,
+                /* requiresLettersOrSymbols= */ false);
+
+        assertEquals(PASSWORD_QUALITY_ALPHANUMERIC, metrics.quality);
+        assertEquals(/* expected= */ 6, metrics.length);
+    }
+
+    @Test
+    public void testGetMinimumMetrics_actualRequiredQualityStricter() {
+        PasswordMetrics metrics = getMinimumMetrics(
+                PASSWORD_COMPLEXITY_HIGH,
+                PASSWORD_QUALITY_UNSPECIFIED,
+                PASSWORD_QUALITY_NUMERIC,
+                /* requiresNumeric= */ false,
+                /* requiresLettersOrSymbols= */ false);
+
+        assertEquals(PASSWORD_QUALITY_NUMERIC_COMPLEX, metrics.quality);
+        assertEquals(/* expected= */ 8, metrics.length);
     }
 }
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/os/WorkSourceTest.java b/core/tests/coretests/src/android/os/WorkSourceTest.java
index 425ab89..e94d60c 100644
--- a/core/tests/coretests/src/android/os/WorkSourceTest.java
+++ b/core/tests/coretests/src/android/os/WorkSourceTest.java
@@ -341,12 +341,37 @@
     }
 
     public void testGetAttributionId() {
-        WorkSource ws1 = new WorkSource();
-        WorkChain wc = ws1.createWorkChain();
-        wc.addNode(100, "tag");
-        assertEquals(100, wc.getAttributionUid());
-        wc.addNode(200, "tag2");
-        assertEquals(100, wc.getAttributionUid());
+        WorkSource ws = new WorkSource();
+        WorkChain wc1 = ws.createWorkChain();
+        wc1.addNode(100, "tag");
+        assertEquals(100, wc1.getAttributionUid());
+        assertEquals(100, ws.getAttributionUid());
+        wc1.addNode(200, "tag2");
+        assertEquals(100, wc1.getAttributionUid());
+        assertEquals(100, ws.getAttributionUid());
+        WorkChain wc2 = ws.createWorkChain();
+        wc2.addNode(300, "tag3");
+        assertEquals(300, wc2.getAttributionUid());
+        assertEquals(100, ws.getAttributionUid());
+    }
+
+    public void testGetAttributionIdWithoutWorkChain() {
+        WorkSource ws1 = new WorkSource(100);
+        ws1.add(200);
+        WorkSource ws2 = new WorkSource();
+        ws2.add(100);
+        ws2.add(200);
+        assertEquals(100, ws1.getAttributionUid());
+        assertEquals(100, ws2.getAttributionUid());
+    }
+
+    public void testGetAttributionWhenEmpty() {
+        WorkSource ws = new WorkSource();
+        assertEquals(-1, ws.getAttributionUid());
+        WorkChain wc = ws.createWorkChain();
+        assertEquals(-1, ws.getAttributionUid());
+        assertEquals(-1, wc.getAttributionUid());
+        assertNull(wc.getAttributionTag());
     }
 
     public void testGetAttributionTag() {
diff --git a/core/tests/coretests/src/android/provider/SettingsBackupTest.java b/core/tests/coretests/src/android/provider/SettingsBackupTest.java
index 87ad3d1..bd7f852 100644
--- a/core/tests/coretests/src/android/provider/SettingsBackupTest.java
+++ b/core/tests/coretests/src/android/provider/SettingsBackupTest.java
@@ -100,6 +100,7 @@
                     Settings.Global.ACTIVITY_MANAGER_CONSTANTS,
                     Settings.Global.ACTIVITY_STARTS_LOGGING_ENABLED,
                     Settings.Global.ADAPTIVE_BATTERY_MANAGEMENT_ENABLED,
+                    Settings.Global.ADB_ALLOWED_CONNECTION_TIME,
                     Settings.Global.ADB_ENABLED,
                     Settings.Global.ADD_USERS_WHEN_LOCKED,
                     Settings.Global.AIRPLANE_MODE_ON,
@@ -120,6 +121,7 @@
                     Settings.Global.APP_IDLE_CONSTANTS,
                     Settings.Global.APP_OPS_CONSTANTS,
                     Settings.Global.APP_STANDBY_ENABLED,
+                    Settings.Global.APP_TIME_LIMIT_USAGE_SOURCE,
                     Settings.Global.ASSISTED_GPS_ENABLED,
                     Settings.Global.AUDIO_SAFE_VOLUME_STATE,
                     Settings.Global.AUTOFILL_COMPAT_MODE_ALLOWED_PACKAGES,
@@ -129,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,
@@ -296,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,
@@ -386,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,
@@ -481,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,
@@ -562,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/InsetsAnimationControlImplTest.java b/core/tests/coretests/src/android/view/InsetsAnimationControlImplTest.java
index 7f00ad9..7cd3c44 100644
--- a/core/tests/coretests/src/android/view/InsetsAnimationControlImplTest.java
+++ b/core/tests/coretests/src/android/view/InsetsAnimationControlImplTest.java
@@ -101,6 +101,7 @@
     @Test
     public void testChangeInsets() {
         mController.changeInsets(Insets.of(0, 30, 40, 0));
+        mController.applyChangeInsets(new InsetsState());
         assertEquals(Insets.of(0, 30, 40, 0), mController.getCurrentInsets());
 
         ArgumentCaptor<SurfaceParams> captor = ArgumentCaptor.forClass(SurfaceParams.class);
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/accessibility/AccessibilityServiceConnectionImpl.java b/core/tests/coretests/src/android/view/accessibility/AccessibilityServiceConnectionImpl.java
index 0dd7685..683d16b 100644
--- a/core/tests/coretests/src/android/view/accessibility/AccessibilityServiceConnectionImpl.java
+++ b/core/tests/coretests/src/android/view/accessibility/AccessibilityServiceConnectionImpl.java
@@ -88,32 +88,32 @@
 
     public void setOnKeyEventResult(boolean handled, int sequence) {}
 
-    public float getMagnificationScale() {
+    public float getMagnificationScale(int displayId) {
         return 0.0f;
     }
 
-    public float getMagnificationCenterX() {
+    public float getMagnificationCenterX(int displayId) {
         return 0.0f;
     }
 
-    public float getMagnificationCenterY() {
+    public float getMagnificationCenterY(int displayId) {
         return 0.0f;
     }
 
-    public Region getMagnificationRegion() {
+    public Region getMagnificationRegion(int displayId) {
         return null;
     }
 
-    public boolean resetMagnification(boolean animate) {
+    public boolean resetMagnification(int displayId, boolean animate) {
         return false;
     }
 
-    public boolean setMagnificationScaleAndCenter(float scale, float centerX, float centerY,
-            boolean animate) {
+    public boolean setMagnificationScaleAndCenter(int displayId, float scale, float centerX,
+            float centerY, boolean animate) {
         return false;
     }
 
-    public void setMagnificationCallbackEnabled(boolean enabled) {}
+    public void setMagnificationCallbackEnabled(int displayId, boolean enabled) {}
 
     public boolean setSoftKeyboardShowMode(int showMode) {
         return false;
diff --git a/core/tests/coretests/src/android/view/autofill/AutofillIdTest.java b/core/tests/coretests/src/android/view/autofill/AutofillIdTest.java
index 33bc593..2f17b32 100644
--- a/core/tests/coretests/src/android/view/autofill/AutofillIdTest.java
+++ b/core/tests/coretests/src/android/view/autofill/AutofillIdTest.java
@@ -34,26 +34,57 @@
     public void testNonVirtual() {
         final AutofillId id = new AutofillId(42);
         assertThat(id.getViewId()).isEqualTo(42);
-        assertThat(id.isVirtual()).isFalse();
-        assertThat(id.getVirtualChildId()).isEqualTo(View.NO_ID);
+        assertThat(id.isNonVirtual()).isTrue();
+        assertThat(id.isVirtualInt()).isFalse();
+        assertThat(id.isVirtualLong()).isFalse();
+        assertThat(id.getVirtualChildIntId()).isEqualTo(View.NO_ID);
+        assertThat(id.getVirtualChildLongId()).isEqualTo(View.NO_ID);
 
         final AutofillId clone = cloneThroughParcel(id);
         assertThat(clone.getViewId()).isEqualTo(42);
-        assertThat(clone.isVirtual()).isFalse();
-        assertThat(clone.getVirtualChildId()).isEqualTo(View.NO_ID);
+        assertThat(clone.isNonVirtual()).isTrue();
+        assertThat(clone.isVirtualInt()).isFalse();
+        assertThat(clone.isVirtualLong()).isFalse();
+        assertThat(clone.getVirtualChildIntId()).isEqualTo(View.NO_ID);
+        assertThat(clone.getVirtualChildLongId()).isEqualTo(View.NO_ID);
     }
 
     @Test
-    public void testVirtual() {
+    public void testVirtual_int() {
         final AutofillId id = new AutofillId(42, 108);
         assertThat(id.getViewId()).isEqualTo(42);
-        assertThat(id.isVirtual()).isTrue();
-        assertThat(id.getVirtualChildId()).isEqualTo(108);
+        assertThat(id.isVirtualInt()).isTrue();
+        assertThat(id.isVirtualLong()).isFalse();
+        assertThat(id.isNonVirtual()).isFalse();
+        assertThat(id.getVirtualChildIntId()).isEqualTo(108);
+        assertThat(id.getVirtualChildLongId()).isEqualTo(View.NO_ID);
 
         final AutofillId clone = cloneThroughParcel(id);
         assertThat(clone.getViewId()).isEqualTo(42);
-        assertThat(clone.isVirtual()).isTrue();
-        assertThat(clone.getVirtualChildId()).isEqualTo(108);
+        assertThat(clone.isVirtualLong()).isFalse();
+        assertThat(clone.isVirtualInt()).isTrue();
+        assertThat(clone.isNonVirtual()).isFalse();
+        assertThat(clone.getVirtualChildIntId()).isEqualTo(108);
+        assertThat(clone.getVirtualChildLongId()).isEqualTo(View.NO_ID);
+    }
+
+    @Test
+    public void testVirtual_long() {
+        final AutofillId id = new AutofillId(new AutofillId(42), 4815162342L, 108);
+        assertThat(id.getViewId()).isEqualTo(42);
+        assertThat(id.isVirtualLong()).isTrue();
+        assertThat(id.isVirtualInt()).isFalse();
+        assertThat(id.isNonVirtual()).isFalse();
+        assertThat(id.getVirtualChildIntId()).isEqualTo(View.NO_ID);
+        assertThat(id.getVirtualChildLongId()).isEqualTo(4815162342L);
+
+        final AutofillId clone = cloneThroughParcel(id);
+        assertThat(clone.getViewId()).isEqualTo(42);
+        assertThat(clone.isVirtualLong()).isTrue();
+        assertThat(clone.isVirtualInt()).isFalse();
+        assertThat(clone.isNonVirtual()).isFalse();
+        assertThat(clone.getVirtualChildIntId()).isEqualTo(View.NO_ID);
+        assertThat(clone.getVirtualChildLongId()).isEqualTo(4815162342L);
     }
 
     @Test
@@ -62,27 +93,33 @@
 
         final AutofillId id = new AutofillId(new AutofillId(42), 108);
         assertThat(id.getViewId()).isEqualTo(42);
-        assertThat(id.isVirtual()).isTrue();
-        assertThat(id.getVirtualChildId()).isEqualTo(108);
+        assertThat(id.isVirtualInt()).isTrue();
+        assertThat(id.getVirtualChildIntId()).isEqualTo(108);
 
         final AutofillId clone = cloneThroughParcel(id);
         assertThat(clone.getViewId()).isEqualTo(42);
-        assertThat(clone.isVirtual()).isTrue();
-        assertThat(clone.getVirtualChildId()).isEqualTo(108);
+        assertThat(clone.isVirtualInt()).isTrue();
+        assertThat(clone.getVirtualChildIntId()).isEqualTo(108);
     }
 
     @Test
     public void testVirtual_withSession() {
-        final AutofillId id = new AutofillId(new AutofillId(42), 108, 666);
+        final AutofillId id = new AutofillId(new AutofillId(42), 108L, 666);
         assertThat(id.getViewId()).isEqualTo(42);
-        assertThat(id.isVirtual()).isTrue();
-        assertThat(id.getVirtualChildId()).isEqualTo(108);
+        assertThat(id.isVirtualLong()).isTrue();
+        assertThat(id.isVirtualInt()).isFalse();
+        assertThat(id.isNonVirtual()).isFalse();
+        assertThat(id.getVirtualChildLongId()).isEqualTo(108L);
+        assertThat(id.getVirtualChildIntId()).isEqualTo(View.NO_ID);
         assertThat(id.getSessionId()).isEqualTo(666);
 
         final AutofillId clone = cloneThroughParcel(id);
         assertThat(clone.getViewId()).isEqualTo(42);
-        assertThat(clone.isVirtual()).isTrue();
-        assertThat(clone.getVirtualChildId()).isEqualTo(108);
+        assertThat(clone.isVirtualLong()).isTrue();
+        assertThat(clone.isVirtualInt()).isFalse();
+        assertThat(clone.isNonVirtual()).isFalse();
+        assertThat(clone.getVirtualChildLongId()).isEqualTo(108L);
+        assertThat(clone.getVirtualChildIntId()).isEqualTo(View.NO_ID);
         assertThat(clone.getSessionId()).isEqualTo(666);
     }
 
@@ -118,13 +155,14 @@
         assertThat(virtualIdDifferentParent).isNotEqualTo(virtualIdDifferentChild);
         assertThat(virtualIdDifferentChild).isNotEqualTo(virtualIdDifferentParent);
 
-        final AutofillId virtualIdDifferentSession = new AutofillId(new AutofillId(42), 1, 108);
+        final AutofillId virtualIdDifferentSession = new AutofillId(new AutofillId(42), 1L, 108);
         assertThat(virtualIdDifferentSession).isNotEqualTo(virtualId);
         assertThat(virtualId).isNotEqualTo(virtualIdDifferentSession);
         assertThat(virtualIdDifferentSession).isNotEqualTo(realId);
         assertThat(realId).isNotEqualTo(virtualIdDifferentSession);
 
-        final AutofillId sameVirtualIdDifferentSession = new AutofillId(new AutofillId(42), 1, 108);
+        final AutofillId sameVirtualIdDifferentSession =
+                new AutofillId(new AutofillId(42), 1L, 108);
         assertThat(sameVirtualIdDifferentSession).isEqualTo(virtualIdDifferentSession);
         assertThat(virtualIdDifferentSession).isEqualTo(sameVirtualIdDifferentSession);
         assertThat(sameVirtualIdDifferentSession.hashCode())
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 ff97aa1..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}
@@ -48,17 +48,18 @@
 
     @Test
     public void testNewAutofillId_invalid() {
-        assertThrows(NullPointerException.class, () -> mSession1.newAutofillId(null, 42));
+        assertThrows(NullPointerException.class, () -> mSession1.newAutofillId(null, 42L));
         assertThrows(IllegalArgumentException.class,
-                () -> mSession1.newAutofillId(new AutofillId(42, 42), 42));
+                () -> mSession1.newAutofillId(new AutofillId(42, 42), 42L));
     }
 
     @Test
     public void testNewAutofillId_valid() {
         final AutofillId parentId = new AutofillId(42);
-        final AutofillId childId = mSession1.newAutofillId(parentId, 108);
+        final AutofillId childId = mSession1.newAutofillId(parentId, 108L);
         assertThat(childId.getViewId()).isEqualTo(42);
-        assertThat(childId.getVirtualChildId()).isEqualTo(108);
+        assertThat(childId.getVirtualChildLongId()).isEqualTo(108L);
+        assertThat(childId.getVirtualChildIntId()).isEqualTo(View.NO_ID);
         assertThat(childId.getSessionId()).isEqualTo(mSession1.getIdAsInt());
     }
 
@@ -66,8 +67,8 @@
     public void testNewAutofillId_differentSessions() {
         assertThat(mSession1.getIdAsInt()).isNotSameAs(mSession2.getIdAsInt()); //sanity check
         final AutofillId parentId = new AutofillId(42);
-        final AutofillId childId1 = mSession1.newAutofillId(parentId, 108);
-        final AutofillId childId2 = mSession2.newAutofillId(parentId, 108);
+        final AutofillId childId1 = mSession1.newAutofillId(parentId, 108L);
+        final AutofillId childId2 = mSession2.newAutofillId(parentId, 108L);
         assertThat(childId1).isNotEqualTo(childId2);
         assertThat(childId2).isNotEqualTo(childId1);
     }
@@ -91,9 +92,9 @@
     @Test
     public void testNewVirtualViewStructure() {
         final AutofillId parentId = new AutofillId(42);
-        final ViewStructure structure = mSession1.newVirtualViewStructure(parentId, 108);
+        final ViewStructure structure = mSession1.newVirtualViewStructure(parentId, 108L);
         assertThat(structure).isNotNull();
-        final AutofillId childId = mSession1.newAutofillId(parentId, 108);
+        final AutofillId childId = mSession1.newAutofillId(parentId, 108L);
         assertThat(structure.getAutofillId()).isEqualTo(childId);
     }
 
@@ -101,16 +102,16 @@
     public void testNotifyViewsDisappeared_invalid() {
         // Null parent
         assertThrows(NullPointerException.class,
-                () -> mSession1.notifyViewsDisappeared(null, new int[] {42}));
+                () -> mSession1.notifyViewsDisappeared(null, new long[] {42}));
         // Null child
         assertThrows(IllegalArgumentException.class,
                 () -> mSession1.notifyViewsDisappeared(new AutofillId(42), null));
         // Empty child
         assertThrows(IllegalArgumentException.class,
-                () -> mSession1.notifyViewsDisappeared(new AutofillId(42), new int[] {}));
+                () -> mSession1.notifyViewsDisappeared(new AutofillId(42), new long[] {}));
         // Virtual parent
         assertThrows(IllegalArgumentException.class,
-                () -> mSession1.notifyViewsDisappeared(new AutofillId(42, 108), new int[] {666}));
+                () -> mSession1.notifyViewsDisappeared(new AutofillId(42, 108), new long[] {666}));
     }
 
     // Cannot use @Spy because we need to pass the session id on constructor
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/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/core/tests/coretests/src/com/android/internal/os/BatteryStatsCpuTimesTest.java b/core/tests/coretests/src/com/android/internal/os/BatteryStatsCpuTimesTest.java
index 0179ead..9cac7e7 100644
--- a/core/tests/coretests/src/com/android/internal/os/BatteryStatsCpuTimesTest.java
+++ b/core/tests/coretests/src/com/android/internal/os/BatteryStatsCpuTimesTest.java
@@ -43,6 +43,10 @@
 import androidx.test.filters.SmallTest;
 import androidx.test.runner.AndroidJUnit4;
 
+import com.android.internal.os.KernelCpuUidTimeReader.KernelCpuUidActiveTimeReader;
+import com.android.internal.os.KernelCpuUidTimeReader.KernelCpuUidClusterTimeReader;
+import com.android.internal.os.KernelCpuUidTimeReader.KernelCpuUidFreqTimeReader;
+import com.android.internal.os.KernelCpuUidTimeReader.KernelCpuUidUserSysTimeReader;
 import com.android.internal.util.ArrayUtils;
 
 import org.junit.Before;
@@ -76,13 +80,13 @@
 @RunWith(AndroidJUnit4.class)
 public class BatteryStatsCpuTimesTest {
     @Mock
-    KernelUidCpuTimeReader mKernelUidCpuTimeReader;
+    KernelCpuUidUserSysTimeReader mCpuUidUserSysTimeReader;
     @Mock
-    KernelUidCpuFreqTimeReader mKernelUidCpuFreqTimeReader;
+    KernelCpuUidFreqTimeReader mCpuUidFreqTimeReader;
     @Mock
-    KernelUidCpuActiveTimeReader mKernelUidCpuActiveTimeReader;
+    KernelCpuUidActiveTimeReader mCpuUidActiveTimeReader;
     @Mock
-    KernelUidCpuClusterTimeReader mKernelUidCpuClusterTimeReader;
+    KernelCpuUidClusterTimeReader mCpuUidClusterTimeReader;
     @Mock
     BatteryStatsImpl.UserInfoProvider mUserInfoProvider;
     @Mock
@@ -98,10 +102,10 @@
 
         mClocks = new MockClocks();
         mBatteryStatsImpl = new MockBatteryStatsImpl(mClocks)
-                .setKernelUidCpuTimeReader(mKernelUidCpuTimeReader)
-                .setKernelUidCpuFreqTimeReader(mKernelUidCpuFreqTimeReader)
-                .setKernelUidCpuActiveTimeReader(mKernelUidCpuActiveTimeReader)
-                .setKernelUidCpuClusterTimeReader(mKernelUidCpuClusterTimeReader)
+                .setKernelCpuUidUserSysTimeReader(mCpuUidUserSysTimeReader)
+                .setKernelCpuUidFreqTimeReader(mCpuUidFreqTimeReader)
+                .setKernelCpuUidActiveTimeReader(mCpuUidActiveTimeReader)
+                .setKernelCpuUidClusterTimeReader(mCpuUidClusterTimeReader)
                 .setUserInfoProvider(mUserInfoProvider);
     }
 
@@ -113,21 +117,21 @@
         final int numClusters = 3;
         initKernelCpuSpeedReaders(numClusters);
         final long[] freqs = {1, 12, 123, 12, 1234};
-        when(mKernelUidCpuFreqTimeReader.readFreqs(mPowerProfile)).thenReturn(freqs);
+        when(mCpuUidFreqTimeReader.readFreqs(mPowerProfile)).thenReturn(freqs);
 
         // RUN
         mBatteryStatsImpl.updateCpuTimeLocked(false, false);
 
         // VERIFY
         assertArrayEquals("Unexpected cpu freqs", freqs, mBatteryStatsImpl.getCpuFreqs());
-        verify(mKernelUidCpuTimeReader).readDelta(null);
-        verify(mKernelUidCpuFreqTimeReader).readDelta(null);
+        verify(mCpuUidUserSysTimeReader).readDelta(null);
+        verify(mCpuUidFreqTimeReader).readDelta(null);
         for (int i = 0; i < numClusters; ++i) {
             verify(mKernelCpuSpeedReaders[i]).readDelta();
         }
 
         // Prepare for next test
-        Mockito.reset(mUserInfoProvider, mKernelUidCpuFreqTimeReader, mKernelUidCpuTimeReader);
+        Mockito.reset(mUserInfoProvider, mCpuUidFreqTimeReader, mCpuUidUserSysTimeReader);
         for (int i = 0; i < numClusters; ++i) {
             Mockito.reset(mKernelCpuSpeedReaders[i]);
         }
@@ -140,17 +144,18 @@
 
         // VERIFY
         verify(mUserInfoProvider).refreshUserIds();
-        verify(mKernelUidCpuTimeReader).readDelta(any(KernelUidCpuTimeReader.Callback.class));
+        verify(mCpuUidUserSysTimeReader).readDelta(
+                any(KernelCpuUidUserSysTimeReader.Callback.class));
         // perClusterTimesAvailable is called twice, once in updateCpuTimeLocked() and the other
         // in readKernelUidCpuFreqTimesLocked.
-        verify(mKernelUidCpuFreqTimeReader, times(2)).perClusterTimesAvailable();
-        verify(mKernelUidCpuFreqTimeReader).readDelta(
-                any(KernelUidCpuFreqTimeReader.Callback.class));
-        verify(mKernelUidCpuActiveTimeReader).readDelta(
-                any(KernelUidCpuActiveTimeReader.Callback.class));
-        verify(mKernelUidCpuClusterTimeReader).readDelta(
-                any(KernelUidCpuClusterTimeReader.Callback.class));
-        verifyNoMoreInteractions(mKernelUidCpuFreqTimeReader);
+        verify(mCpuUidFreqTimeReader, times(2)).perClusterTimesAvailable();
+        verify(mCpuUidFreqTimeReader).readDelta(
+                any(KernelCpuUidFreqTimeReader.Callback.class));
+        verify(mCpuUidActiveTimeReader).readDelta(
+                any(KernelCpuUidActiveTimeReader.Callback.class));
+        verify(mCpuUidClusterTimeReader).readDelta(
+                any(KernelCpuUidClusterTimeReader.Callback.class));
+        verifyNoMoreInteractions(mCpuUidFreqTimeReader);
         for (int i = 0; i < numClusters; ++i) {
             verify(mKernelCpuSpeedReaders[i]).readDelta();
         }
@@ -253,13 +258,14 @@
                 {12, 34}, {34897394, 3123983}, {79775429834l, 8430434903489l}
         };
         doAnswer(invocation -> {
-            final KernelUidCpuTimeReader.Callback callback =
-                    (KernelUidCpuTimeReader.Callback) invocation.getArguments()[0];
+            final KernelCpuUidUserSysTimeReader.Callback<long[]> callback =
+                    (KernelCpuUidUserSysTimeReader.Callback<long[]>) invocation.getArguments()[0];
             for (int i = 0; i < testUids.length; ++i) {
-                callback.onUidCpuTime(testUids[i], uidTimesUs[i][0], uidTimesUs[i][1]);
+                callback.onUidCpuTime(testUids[i], uidTimesUs[i]);
             }
             return null;
-        }).when(mKernelUidCpuTimeReader).readDelta(any(KernelUidCpuTimeReader.Callback.class));
+        }).when(mCpuUidUserSysTimeReader).readDelta(
+                any(KernelCpuUidUserSysTimeReader.Callback.class));
 
         // RUN
         final SparseLongArray updatedUids = new SparseLongArray();
@@ -287,13 +293,14 @@
                 {9379, 3332409833484l}, {493247, 723234}, {3247819, 123348}
         };
         doAnswer(invocation -> {
-            final KernelUidCpuTimeReader.Callback callback =
-                    (KernelUidCpuTimeReader.Callback) invocation.getArguments()[0];
+            final KernelCpuUidUserSysTimeReader.Callback<long[]> callback =
+                    (KernelCpuUidUserSysTimeReader.Callback<long[]>) invocation.getArguments()[0];
             for (int i = 0; i < testUids.length; ++i) {
-                callback.onUidCpuTime(testUids[i], deltasUs[i][0], deltasUs[i][1]);
+                callback.onUidCpuTime(testUids[i], deltasUs[i]);
             }
             return null;
-        }).when(mKernelUidCpuTimeReader).readDelta(any(KernelUidCpuTimeReader.Callback.class));
+        }).when(mCpuUidUserSysTimeReader).readDelta(
+                any(KernelCpuUidUserSysTimeReader.Callback.class));
 
         // RUN
         mBatteryStatsImpl.readKernelUidCpuTimesLocked(null, null, true);
@@ -326,13 +333,14 @@
                 {12, 34}, {34897394, 3123983}, {79775429834l, 8430434903489l}
         };
         doAnswer(invocation -> {
-            final KernelUidCpuTimeReader.Callback callback =
-                    (KernelUidCpuTimeReader.Callback) invocation.getArguments()[0];
+            final KernelCpuUidUserSysTimeReader.Callback<long[]> callback =
+                    (KernelCpuUidUserSysTimeReader.Callback<long[]>) invocation.getArguments()[0];
             for (int i = 0; i < testUids.length; ++i) {
-                callback.onUidCpuTime(testUids[i], uidTimesUs[i][0], uidTimesUs[i][1]);
+                callback.onUidCpuTime(testUids[i], uidTimesUs[i]);
             }
             return null;
-        }).when(mKernelUidCpuTimeReader).readDelta(any(KernelUidCpuTimeReader.Callback.class));
+        }).when(mCpuUidUserSysTimeReader).readDelta(
+                any(KernelCpuUidUserSysTimeReader.Callback.class));
 
         // RUN
         mBatteryStatsImpl.readKernelUidCpuTimesLocked(null, null, true);
@@ -350,7 +358,7 @@
             assertEquals("Unexpected system cpu time for uid=" + testUids[i],
                     uidTimesUs[i][1], u.getSystemCpuTimeUs(STATS_SINCE_CHARGED));
         }
-        verify(mKernelUidCpuTimeReader).removeUid(isolatedUid);
+        verify(mCpuUidUserSysTimeReader).removeUid(isolatedUid);
 
         // Add an isolated uid mapping and repeat the test.
 
@@ -361,13 +369,14 @@
                 {9379, 3332409833484l}, {493247, 723234}, {3247819, 123348}
         };
         doAnswer(invocation -> {
-            final KernelUidCpuTimeReader.Callback callback =
-                    (KernelUidCpuTimeReader.Callback) invocation.getArguments()[0];
+            final KernelCpuUidUserSysTimeReader.Callback<long[]> callback =
+                    (KernelCpuUidUserSysTimeReader.Callback<long[]>) invocation.getArguments()[0];
             for (int i = 0; i < testUids.length; ++i) {
-                callback.onUidCpuTime(testUids[i], deltasUs[i][0], deltasUs[i][1]);
+                callback.onUidCpuTime(testUids[i], deltasUs[i]);
             }
             return null;
-        }).when(mKernelUidCpuTimeReader).readDelta(any(KernelUidCpuTimeReader.Callback.class));
+        }).when(mCpuUidUserSysTimeReader).readDelta(
+                any(KernelCpuUidUserSysTimeReader.Callback.class));
 
         // RUN
         mBatteryStatsImpl.readKernelUidCpuTimesLocked(null, null, true);
@@ -414,15 +423,16 @@
                 {12, 34}, {34897394, 3123983}, {79775429834l, 8430434903489l}
         };
         doAnswer(invocation -> {
-            final KernelUidCpuTimeReader.Callback callback =
-                    (KernelUidCpuTimeReader.Callback) invocation.getArguments()[0];
+            final KernelCpuUidUserSysTimeReader.Callback<long[]> callback =
+                    (KernelCpuUidUserSysTimeReader.Callback<long[]>) invocation.getArguments()[0];
             for (int i = 0; i < testUids.length; ++i) {
-                callback.onUidCpuTime(testUids[i], uidTimesUs[i][0], uidTimesUs[i][1]);
+                callback.onUidCpuTime(testUids[i], uidTimesUs[i]);
             }
             // And one for the invalid uid
-            callback.onUidCpuTime(invalidUid, 3879, 239);
+            callback.onUidCpuTime(invalidUid, new long[]{3879, 239});
             return null;
-        }).when(mKernelUidCpuTimeReader).readDelta(any(KernelUidCpuTimeReader.Callback.class));
+        }).when(mCpuUidUserSysTimeReader).readDelta(
+                any(KernelCpuUidUserSysTimeReader.Callback.class));
 
         // RUN
         mBatteryStatsImpl.readKernelUidCpuTimesLocked(null, null, true);
@@ -438,7 +448,7 @@
         }
         assertNull("There shouldn't be an entry for invalid uid=" + invalidUid,
                 mBatteryStatsImpl.getUidStats().get(invalidUid));
-        verify(mKernelUidCpuTimeReader).removeUid(invalidUid);
+        verify(mCpuUidUserSysTimeReader).removeUid(invalidUid);
     }
 
     @Test
@@ -462,13 +472,14 @@
                 {12, 34}, {3394, 3123}, {7977, 80434}
         };
         doAnswer(invocation -> {
-            final KernelUidCpuTimeReader.Callback callback =
-                    (KernelUidCpuTimeReader.Callback) invocation.getArguments()[0];
+            final KernelCpuUidUserSysTimeReader.Callback<long[]> callback =
+                    (KernelCpuUidUserSysTimeReader.Callback<long[]>) invocation.getArguments()[0];
             for (int i = 0; i < testUids.length; ++i) {
-                callback.onUidCpuTime(testUids[i], uidTimesUs[i][0], uidTimesUs[i][1]);
+                callback.onUidCpuTime(testUids[i], uidTimesUs[i]);
             }
             return null;
-        }).when(mKernelUidCpuTimeReader).readDelta(any(KernelUidCpuTimeReader.Callback.class));
+        }).when(mCpuUidUserSysTimeReader).readDelta(
+                any(KernelCpuUidUserSysTimeReader.Callback.class));
 
         // RUN
         final SparseLongArray updatedUids = new SparseLongArray();
@@ -541,14 +552,14 @@
                 {8, 25, 3, 0, 42}
         };
         doAnswer(invocation -> {
-            final KernelUidCpuFreqTimeReader.Callback callback =
-                    (KernelUidCpuFreqTimeReader.Callback) invocation.getArguments()[0];
+            final KernelCpuUidFreqTimeReader.Callback callback =
+                    (KernelCpuUidFreqTimeReader.Callback) invocation.getArguments()[0];
             for (int i = 0; i < testUids.length; ++i) {
-                callback.onUidCpuFreqTime(testUids[i], uidTimesMs[i]);
+                callback.onUidCpuTime(testUids[i], uidTimesMs[i]);
             }
             return null;
-        }).when(mKernelUidCpuFreqTimeReader).readDelta(
-                any(KernelUidCpuFreqTimeReader.Callback.class));
+        }).when(mCpuUidFreqTimeReader).readDelta(
+                any(KernelCpuUidFreqTimeReader.Callback.class));
 
         // RUN
         mBatteryStatsImpl.readKernelUidCpuFreqTimesLocked(null, true, false);
@@ -574,14 +585,14 @@
                 {43, 3345, 2143, 123, 4554}
         };
         doAnswer(invocation -> {
-            final KernelUidCpuFreqTimeReader.Callback callback =
-                    (KernelUidCpuFreqTimeReader.Callback) invocation.getArguments()[0];
+            final KernelCpuUidFreqTimeReader.Callback callback =
+                    (KernelCpuUidFreqTimeReader.Callback) invocation.getArguments()[0];
             for (int i = 0; i < testUids.length; ++i) {
-                callback.onUidCpuFreqTime(testUids[i], deltasMs[i]);
+                callback.onUidCpuTime(testUids[i], deltasMs[i]);
             }
             return null;
-        }).when(mKernelUidCpuFreqTimeReader).readDelta(
-                any(KernelUidCpuFreqTimeReader.Callback.class));
+        }).when(mCpuUidFreqTimeReader).readDelta(
+                any(KernelCpuUidFreqTimeReader.Callback.class));
 
         // RUN
         mBatteryStatsImpl.readKernelUidCpuFreqTimesLocked(null, true, true);
@@ -624,15 +635,15 @@
                 {8, 25, 3, 0, 42}
         };
         doAnswer(invocation -> {
-            final KernelUidCpuFreqTimeReader.Callback callback =
-                    (KernelUidCpuFreqTimeReader.Callback) invocation.getArguments()[0];
+            final KernelCpuUidFreqTimeReader.Callback callback =
+                    (KernelCpuUidFreqTimeReader.Callback) invocation.getArguments()[0];
             for (int i = 0; i < testUids.length; ++i) {
-                callback.onUidCpuFreqTime(testUids[i], uidTimesMs[i]);
+                callback.onUidCpuTime(testUids[i], uidTimesMs[i]);
             }
             return null;
-        }).when(mKernelUidCpuFreqTimeReader).readDelta(
-                any(KernelUidCpuFreqTimeReader.Callback.class));
-        when(mKernelUidCpuFreqTimeReader.perClusterTimesAvailable()).thenReturn(true);
+        }).when(mCpuUidFreqTimeReader).readDelta(
+                any(KernelCpuUidFreqTimeReader.Callback.class));
+        when(mCpuUidFreqTimeReader.perClusterTimesAvailable()).thenReturn(true);
 
         // RUN
         mBatteryStatsImpl.readKernelUidCpuFreqTimesLocked(null, true, false);
@@ -668,14 +679,14 @@
                 {43, 3345, 2143, 123, 4554}
         };
         doAnswer(invocation -> {
-            final KernelUidCpuFreqTimeReader.Callback callback =
-                    (KernelUidCpuFreqTimeReader.Callback) invocation.getArguments()[0];
+            final KernelCpuUidFreqTimeReader.Callback callback =
+                    (KernelCpuUidFreqTimeReader.Callback) invocation.getArguments()[0];
             for (int i = 0; i < testUids.length; ++i) {
-                callback.onUidCpuFreqTime(testUids[i], deltasMs[i]);
+                callback.onUidCpuTime(testUids[i], deltasMs[i]);
             }
             return null;
-        }).when(mKernelUidCpuFreqTimeReader).readDelta(
-                any(KernelUidCpuFreqTimeReader.Callback.class));
+        }).when(mCpuUidFreqTimeReader).readDelta(
+                any(KernelCpuUidFreqTimeReader.Callback.class));
 
         // RUN
         mBatteryStatsImpl.readKernelUidCpuFreqTimesLocked(null, true, true);
@@ -734,15 +745,15 @@
                 {8, 25, 3, 0, 42}
         };
         doAnswer(invocation -> {
-            final KernelUidCpuFreqTimeReader.Callback callback =
-                    (KernelUidCpuFreqTimeReader.Callback) invocation.getArguments()[0];
+            final KernelCpuUidFreqTimeReader.Callback callback =
+                    (KernelCpuUidFreqTimeReader.Callback) invocation.getArguments()[0];
             for (int i = 0; i < testUids.length; ++i) {
-                callback.onUidCpuFreqTime(testUids[i], uidTimesMs[i]);
+                callback.onUidCpuTime(testUids[i], uidTimesMs[i]);
             }
             return null;
-        }).when(mKernelUidCpuFreqTimeReader).readDelta(
-                any(KernelUidCpuFreqTimeReader.Callback.class));
-        when(mKernelUidCpuFreqTimeReader.perClusterTimesAvailable()).thenReturn(true);
+        }).when(mCpuUidFreqTimeReader).readDelta(
+                any(KernelCpuUidFreqTimeReader.Callback.class));
+        when(mCpuUidFreqTimeReader.perClusterTimesAvailable()).thenReturn(true);
 
         // RUN
         mBatteryStatsImpl.readKernelUidCpuFreqTimesLocked(partialTimers, true, false);
@@ -824,14 +835,14 @@
                 {8, 25, 3, 0, 42}
         };
         doAnswer(invocation -> {
-            final KernelUidCpuFreqTimeReader.Callback callback =
-                    (KernelUidCpuFreqTimeReader.Callback) invocation.getArguments()[0];
+            final KernelCpuUidFreqTimeReader.Callback callback =
+                    (KernelCpuUidFreqTimeReader.Callback) invocation.getArguments()[0];
             for (int i = 0; i < testUids.length; ++i) {
-                callback.onUidCpuFreqTime(testUids[i], uidTimesMs[i]);
+                callback.onUidCpuTime(testUids[i], uidTimesMs[i]);
             }
             return null;
-        }).when(mKernelUidCpuFreqTimeReader).readDelta(
-                any(KernelUidCpuFreqTimeReader.Callback.class));
+        }).when(mCpuUidFreqTimeReader).readDelta(
+                any(KernelCpuUidFreqTimeReader.Callback.class));
 
         // RUN
         mBatteryStatsImpl.readKernelUidCpuFreqTimesLocked(null, true, false);
@@ -857,14 +868,14 @@
                 {43, 3345, 2143, 123, 4554, 9374983794839l, 979875}
         };
         doAnswer(invocation -> {
-            final KernelUidCpuFreqTimeReader.Callback callback =
-                    (KernelUidCpuFreqTimeReader.Callback) invocation.getArguments()[0];
+            final KernelCpuUidFreqTimeReader.Callback callback =
+                    (KernelCpuUidFreqTimeReader.Callback) invocation.getArguments()[0];
             for (int i = 0; i < testUids.length; ++i) {
-                callback.onUidCpuFreqTime(testUids[i], deltasMs[i]);
+                callback.onUidCpuTime(testUids[i], deltasMs[i]);
             }
             return null;
-        }).when(mKernelUidCpuFreqTimeReader).readDelta(
-                any(KernelUidCpuFreqTimeReader.Callback.class));
+        }).when(mCpuUidFreqTimeReader).readDelta(
+                any(KernelCpuUidFreqTimeReader.Callback.class));
 
         // RUN
         mBatteryStatsImpl.readKernelUidCpuFreqTimesLocked(null, true, true);
@@ -901,14 +912,14 @@
                 {8, 25, 3, 0, 42}
         };
         doAnswer(invocation -> {
-            final KernelUidCpuFreqTimeReader.Callback callback =
-                    (KernelUidCpuFreqTimeReader.Callback) invocation.getArguments()[0];
+            final KernelCpuUidFreqTimeReader.Callback callback =
+                    (KernelCpuUidFreqTimeReader.Callback) invocation.getArguments()[0];
             for (int i = 0; i < testUids.length; ++i) {
-                callback.onUidCpuFreqTime(testUids[i], uidTimesMs[i]);
+                callback.onUidCpuTime(testUids[i], uidTimesMs[i]);
             }
             return null;
-        }).when(mKernelUidCpuFreqTimeReader).readDelta(
-                any(KernelUidCpuFreqTimeReader.Callback.class));
+        }).when(mCpuUidFreqTimeReader).readDelta(
+                any(KernelCpuUidFreqTimeReader.Callback.class));
 
         // RUN
         mBatteryStatsImpl.readKernelUidCpuFreqTimesLocked(null, true, false);
@@ -927,7 +938,7 @@
             assertNull("Unexpected screen-off cpu times for uid=" + testUids[i],
                     u.getScreenOffCpuFreqTimes(STATS_SINCE_CHARGED));
         }
-        verify(mKernelUidCpuFreqTimeReader).removeUid(isolatedUid);
+        verify(mCpuUidFreqTimeReader).removeUid(isolatedUid);
 
 
         // Add an isolated uid mapping and repeat the test.
@@ -941,14 +952,14 @@
                 {43, 3345, 2143, 123, 4554}
         };
         doAnswer(invocation -> {
-            final KernelUidCpuFreqTimeReader.Callback callback =
-                    (KernelUidCpuFreqTimeReader.Callback) invocation.getArguments()[0];
+            final KernelCpuUidFreqTimeReader.Callback callback =
+                    (KernelCpuUidFreqTimeReader.Callback) invocation.getArguments()[0];
             for (int i = 0; i < testUids.length; ++i) {
-                callback.onUidCpuFreqTime(testUids[i], deltasMs[i]);
+                callback.onUidCpuTime(testUids[i], deltasMs[i]);
             }
             return null;
-        }).when(mKernelUidCpuFreqTimeReader).readDelta(
-                any(KernelUidCpuFreqTimeReader.Callback.class));
+        }).when(mCpuUidFreqTimeReader).readDelta(
+                any(KernelCpuUidFreqTimeReader.Callback.class));
 
         // RUN
         mBatteryStatsImpl.readKernelUidCpuFreqTimesLocked(null, true, false);
@@ -996,16 +1007,16 @@
                 {8, 25, 3, 0, 42}
         };
         doAnswer(invocation -> {
-            final KernelUidCpuFreqTimeReader.Callback callback =
-                    (KernelUidCpuFreqTimeReader.Callback) invocation.getArguments()[0];
+            final KernelCpuUidFreqTimeReader.Callback callback =
+                    (KernelCpuUidFreqTimeReader.Callback) invocation.getArguments()[0];
             for (int i = 0; i < testUids.length; ++i) {
-                callback.onUidCpuFreqTime(testUids[i], uidTimesMs[i]);
+                callback.onUidCpuTime(testUids[i], uidTimesMs[i]);
             }
             // And one for the invalid uid
-            callback.onUidCpuFreqTime(invalidUid, new long[]{12, 839, 32, 34, 21});
+            callback.onUidCpuTime(invalidUid, new long[]{12, 839, 32, 34, 21});
             return null;
-        }).when(mKernelUidCpuFreqTimeReader).readDelta(
-                any(KernelUidCpuFreqTimeReader.Callback.class));
+        }).when(mCpuUidFreqTimeReader).readDelta(
+                any(KernelCpuUidFreqTimeReader.Callback.class));
 
         // RUN
         mBatteryStatsImpl.readKernelUidCpuFreqTimesLocked(null, true, false);
@@ -1022,7 +1033,7 @@
         }
         assertNull("There shouldn't be an entry for invalid uid=" + invalidUid,
                 mBatteryStatsImpl.getUidStats().get(invalidUid));
-        verify(mKernelUidCpuFreqTimeReader).removeUid(invalidUid);
+        verify(mCpuUidFreqTimeReader).removeUid(invalidUid);
     }
 
     @Test
@@ -1039,14 +1050,14 @@
         });
         final long[] uidTimesMs = {8000, 25000, 3000, 0, 42000};
         doAnswer(invocation -> {
-            final KernelUidCpuActiveTimeReader.Callback callback =
-                    (KernelUidCpuActiveTimeReader.Callback) invocation.getArguments()[0];
+            final KernelCpuUidActiveTimeReader.Callback callback =
+                    (KernelCpuUidActiveTimeReader.Callback) invocation.getArguments()[0];
             for (int i = 0; i < testUids.length; ++i) {
-                callback.onUidCpuActiveTime(testUids[i], uidTimesMs[i]);
+                callback.onUidCpuTime(testUids[i], uidTimesMs[i]);
             }
             return null;
-        }).when(mKernelUidCpuActiveTimeReader).readDelta(
-                any(KernelUidCpuActiveTimeReader.Callback.class));
+        }).when(mCpuUidActiveTimeReader).readDelta(
+                any(KernelCpuUidActiveTimeReader.Callback.class));
 
         // RUN
         mBatteryStatsImpl.readKernelUidCpuActiveTimesLocked(true);
@@ -1065,14 +1076,14 @@
         updateTimeBasesLocked(true, Display.STATE_OFF, 0, 0);
         final long[] deltasMs = {43000, 3345000, 2143000, 123000, 4554000};
         doAnswer(invocation -> {
-            final KernelUidCpuActiveTimeReader.Callback callback =
-                    (KernelUidCpuActiveTimeReader.Callback) invocation.getArguments()[0];
+            final KernelCpuUidActiveTimeReader.Callback callback =
+                    (KernelCpuUidActiveTimeReader.Callback) invocation.getArguments()[0];
             for (int i = 0; i < testUids.length; ++i) {
-                callback.onUidCpuActiveTime(testUids[i], deltasMs[i]);
+                callback.onUidCpuTime(testUids[i], deltasMs[i]);
             }
             return null;
-        }).when(mKernelUidCpuActiveTimeReader).readDelta(
-                any(KernelUidCpuActiveTimeReader.Callback.class));
+        }).when(mCpuUidActiveTimeReader).readDelta(
+                any(KernelCpuUidActiveTimeReader.Callback.class));
 
         // RUN
         mBatteryStatsImpl.readKernelUidCpuActiveTimesLocked(true);
@@ -1103,16 +1114,16 @@
         });
         final long[] uidTimesMs = {8000, 25000, 3000, 0, 42000};
         doAnswer(invocation -> {
-            final KernelUidCpuActiveTimeReader.Callback callback =
-                    (KernelUidCpuActiveTimeReader.Callback) invocation.getArguments()[0];
+            final KernelCpuUidActiveTimeReader.Callback callback =
+                    (KernelCpuUidActiveTimeReader.Callback) invocation.getArguments()[0];
             for (int i = 0; i < testUids.length; ++i) {
-                callback.onUidCpuActiveTime(testUids[i], uidTimesMs[i]);
+                callback.onUidCpuTime(testUids[i], uidTimesMs[i]);
             }
             // And one for the invalid uid
-            callback.onUidCpuActiveTime(invalidUid, 1200L);
+            callback.onUidCpuTime(invalidUid, 1200L);
             return null;
-        }).when(mKernelUidCpuActiveTimeReader).readDelta(
-                any(KernelUidCpuActiveTimeReader.Callback.class));
+        }).when(mCpuUidActiveTimeReader).readDelta(
+                any(KernelCpuUidActiveTimeReader.Callback.class));
 
         // RUN
         mBatteryStatsImpl.readKernelUidCpuActiveTimesLocked(true);
@@ -1126,7 +1137,7 @@
         }
         assertNull("There shouldn't be an entry for invalid uid=" + invalidUid,
                 mBatteryStatsImpl.getUidStats().get(invalidUid));
-        verify(mKernelUidCpuActiveTimeReader).removeUid(invalidUid);
+        verify(mCpuUidActiveTimeReader).removeUid(invalidUid);
     }
 
     @Test
@@ -1147,14 +1158,14 @@
                 {8000, 0}
         };
         doAnswer(invocation -> {
-            final KernelUidCpuClusterTimeReader.Callback callback =
-                    (KernelUidCpuClusterTimeReader.Callback) invocation.getArguments()[0];
+            final KernelCpuUidClusterTimeReader.Callback callback =
+                    (KernelCpuUidClusterTimeReader.Callback) invocation.getArguments()[0];
             for (int i = 0; i < testUids.length; ++i) {
-                callback.onUidCpuPolicyTime(testUids[i], uidTimesMs[i]);
+                callback.onUidCpuTime(testUids[i], uidTimesMs[i]);
             }
             return null;
-        }).when(mKernelUidCpuClusterTimeReader).readDelta(
-                any(KernelUidCpuClusterTimeReader.Callback.class));
+        }).when(mCpuUidClusterTimeReader).readDelta(
+                any(KernelCpuUidClusterTimeReader.Callback.class));
 
         // RUN
         mBatteryStatsImpl.readKernelUidCpuClusterTimesLocked(true);
@@ -1177,14 +1188,14 @@
                 {43000, 3345000}
         };
         doAnswer(invocation -> {
-            final KernelUidCpuClusterTimeReader.Callback callback =
-                    (KernelUidCpuClusterTimeReader.Callback) invocation.getArguments()[0];
+            final KernelCpuUidClusterTimeReader.Callback callback =
+                    (KernelCpuUidClusterTimeReader.Callback) invocation.getArguments()[0];
             for (int i = 0; i < testUids.length; ++i) {
-                callback.onUidCpuPolicyTime(testUids[i], deltasMs[i]);
+                callback.onUidCpuTime(testUids[i], deltasMs[i]);
             }
             return null;
-        }).when(mKernelUidCpuClusterTimeReader).readDelta(
-                any(KernelUidCpuClusterTimeReader.Callback.class));
+        }).when(mCpuUidClusterTimeReader).readDelta(
+                any(KernelCpuUidClusterTimeReader.Callback.class));
 
         // RUN
         mBatteryStatsImpl.readKernelUidCpuClusterTimesLocked(true);
@@ -1193,7 +1204,8 @@
         for (int i = 0; i < testUids.length; ++i) {
             final BatteryStats.Uid u = mBatteryStatsImpl.getUidStats().get(testUids[i]);
             assertNotNull("No entry for uid=" + testUids[i], u);
-            assertArrayEquals("Unexpected cpu cluster time for uid=" + testUids[i], sum(uidTimesMs[i], deltasMs[i]),
+            assertArrayEquals("Unexpected cpu cluster time for uid=" + testUids[i],
+                    sum(uidTimesMs[i], deltasMs[i]),
                     u.getCpuClusterTimes());
         }
     }
@@ -1219,16 +1231,16 @@
                 {8000, 0}
         };
         doAnswer(invocation -> {
-            final KernelUidCpuClusterTimeReader.Callback callback =
-                    (KernelUidCpuClusterTimeReader.Callback) invocation.getArguments()[0];
+            final KernelCpuUidClusterTimeReader.Callback callback =
+                    (KernelCpuUidClusterTimeReader.Callback) invocation.getArguments()[0];
             for (int i = 0; i < testUids.length; ++i) {
-                callback.onUidCpuPolicyTime(testUids[i], uidTimesMs[i]);
+                callback.onUidCpuTime(testUids[i], uidTimesMs[i]);
             }
             // And one for the invalid uid
-            callback.onUidCpuPolicyTime(invalidUid, new long[] {400, 1000});
+            callback.onUidCpuTime(invalidUid, new long[]{400, 1000});
             return null;
-        }).when(mKernelUidCpuClusterTimeReader).readDelta(
-                any(KernelUidCpuClusterTimeReader.Callback.class));
+        }).when(mCpuUidClusterTimeReader).readDelta(
+                any(KernelCpuUidClusterTimeReader.Callback.class));
 
         // RUN
         mBatteryStatsImpl.readKernelUidCpuClusterTimesLocked(true);
@@ -1242,7 +1254,7 @@
         }
         assertNull("There shouldn't be an entry for invalid uid=" + invalidUid,
                 mBatteryStatsImpl.getUidStats().get(invalidUid));
-        verify(mKernelUidCpuClusterTimeReader).removeUid(invalidUid);
+        verify(mCpuUidClusterTimeReader).removeUid(invalidUid);
     }
 
     @Test
@@ -1268,25 +1280,25 @@
         mClocks.realtime = mClocks.uptime = 400_000;
         mBatteryStatsImpl.clearPendingRemovedUids();
         assertEquals(1, mBatteryStatsImpl.getPendingRemovedUids().size());
-        verify(mKernelUidCpuActiveTimeReader).removeUid(1);
-        verify(mKernelUidCpuActiveTimeReader).removeUidsInRange(5, 10);
-        verify(mKernelUidCpuClusterTimeReader).removeUid(1);
-        verify(mKernelUidCpuClusterTimeReader).removeUidsInRange(5, 10);
-        verify(mKernelUidCpuFreqTimeReader).removeUid(1);
-        verify(mKernelUidCpuFreqTimeReader).removeUidsInRange(5, 10);
-        verify(mKernelUidCpuTimeReader).removeUid(1);
-        verify(mKernelUidCpuTimeReader).removeUidsInRange(5, 10);
+        verify(mCpuUidActiveTimeReader).removeUid(1);
+        verify(mCpuUidActiveTimeReader).removeUidsInRange(5, 10);
+        verify(mCpuUidClusterTimeReader).removeUid(1);
+        verify(mCpuUidClusterTimeReader).removeUidsInRange(5, 10);
+        verify(mCpuUidFreqTimeReader).removeUid(1);
+        verify(mCpuUidFreqTimeReader).removeUidsInRange(5, 10);
+        verify(mCpuUidUserSysTimeReader).removeUid(1);
+        verify(mCpuUidUserSysTimeReader).removeUidsInRange(5, 10);
 
         mClocks.realtime = mClocks.uptime = 800_000;
         mBatteryStatsImpl.clearPendingRemovedUids();
         assertEquals(0, mBatteryStatsImpl.getPendingRemovedUids().size());
-        verify(mKernelUidCpuActiveTimeReader).removeUid(100);
-        verify(mKernelUidCpuClusterTimeReader).removeUid(100);
-        verify(mKernelUidCpuFreqTimeReader).removeUid(100);
-        verify(mKernelUidCpuTimeReader).removeUid(100);
+        verify(mCpuUidActiveTimeReader).removeUid(100);
+        verify(mCpuUidClusterTimeReader).removeUid(100);
+        verify(mCpuUidFreqTimeReader).removeUid(100);
+        verify(mCpuUidUserSysTimeReader).removeUid(100);
 
-        verifyNoMoreInteractions(mKernelUidCpuActiveTimeReader, mKernelUidCpuClusterTimeReader,
-                mKernelUidCpuFreqTimeReader, mKernelUidCpuTimeReader);
+        verifyNoMoreInteractions(mCpuUidActiveTimeReader, mCpuUidClusterTimeReader,
+                mCpuUidFreqTimeReader, mCpuUidUserSysTimeReader);
     }
 
     private void updateTimeBasesLocked(boolean unplugged, int screenState,
diff --git a/core/tests/coretests/src/com/android/internal/os/BatteryStatsImplTest.java b/core/tests/coretests/src/com/android/internal/os/BatteryStatsImplTest.java
index dc93675..0771829 100644
--- a/core/tests/coretests/src/com/android/internal/os/BatteryStatsImplTest.java
+++ b/core/tests/coretests/src/com/android/internal/os/BatteryStatsImplTest.java
@@ -39,6 +39,7 @@
 import androidx.test.filters.LargeTest;
 import androidx.test.runner.AndroidJUnit4;
 
+import com.android.internal.os.KernelCpuUidTimeReader.KernelCpuUidFreqTimeReader;
 import com.android.internal.util.ArrayUtils;
 
 import org.junit.Before;
@@ -54,7 +55,7 @@
 @RunWith(AndroidJUnit4.class)
 public class BatteryStatsImplTest {
     @Mock
-    private KernelUidCpuFreqTimeReader mKernelUidCpuFreqTimeReader;
+    private KernelCpuUidFreqTimeReader mKernelUidCpuFreqTimeReader;
     @Mock
     private KernelSingleUidTimeReader mKernelSingleUidTimeReader;
 
@@ -67,7 +68,7 @@
         when(mKernelUidCpuFreqTimeReader.allUidTimesAvailable()).thenReturn(true);
         when(mKernelSingleUidTimeReader.singleUidCpuTimesAvailable()).thenReturn(true);
         mBatteryStatsImpl = new MockBatteryStatsImpl()
-                .setKernelUidCpuFreqTimeReader(mKernelUidCpuFreqTimeReader)
+                .setKernelCpuUidFreqTimeReader(mKernelUidCpuFreqTimeReader)
                 .setKernelSingleUidTimeReader(mKernelSingleUidTimeReader);
     }
 
diff --git a/core/tests/coretests/src/com/android/internal/os/BatteryStatsTests.java b/core/tests/coretests/src/com/android/internal/os/BatteryStatsTests.java
index d69e1d1..a6329298 100644
--- a/core/tests/coretests/src/com/android/internal/os/BatteryStatsTests.java
+++ b/core/tests/coretests/src/com/android/internal/os/BatteryStatsTests.java
@@ -38,7 +38,6 @@
         BatteryStatsTimerTest.class,
         BatteryStatsUidTest.class,
         BatteryStatsUserLifecycleTests.class,
-        KernelCpuProcReaderTest.class,
         KernelCpuProcStringReaderTest.class,
         KernelCpuUidActiveTimeReaderTest.class,
         KernelCpuUidClusterTimeReaderTest.class,
@@ -46,9 +45,6 @@
         KernelCpuUidUserSysTimeReaderTest.class,
         KernelMemoryBandwidthStatsTest.class,
         KernelSingleUidTimeReaderTest.class,
-        KernelUidCpuFreqTimeReaderTest.class,
-        KernelUidCpuActiveTimeReaderTest.class,
-        KernelUidCpuClusterTimeReaderTest.class,
         KernelWakelockReaderTest.class,
         LongSamplingCounterTest.class,
         LongSamplingCounterArrayTest.class,
diff --git a/core/tests/coretests/src/com/android/internal/os/KernelCpuProcReaderTest.java b/core/tests/coretests/src/com/android/internal/os/KernelCpuProcReaderTest.java
deleted file mode 100644
index a25a7489..0000000
--- a/core/tests/coretests/src/com/android/internal/os/KernelCpuProcReaderTest.java
+++ /dev/null
@@ -1,199 +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.internal.os;
-
-import static org.junit.Assert.assertEquals;
-import static org.junit.Assert.assertNotNull;
-import static org.junit.Assert.assertNull;
-import static org.junit.Assert.assertTrue;
-
-import android.content.Context;
-import android.os.FileUtils;
-import android.os.SystemClock;
-
-import androidx.test.InstrumentationRegistry;
-import androidx.test.filters.SmallTest;
-import androidx.test.runner.AndroidJUnit4;
-
-import org.junit.After;
-import org.junit.Before;
-import org.junit.Test;
-import org.junit.runner.RunWith;
-
-import java.io.File;
-import java.io.OutputStream;
-import java.nio.ByteBuffer;
-import java.nio.file.Files;
-import java.util.Arrays;
-import java.util.Random;
-
-/**
- * Test class for {@link KernelCpuProcReader}.
- *
- * $ atest FrameworksCoreTests:com.android.internal.os.KernelCpuProcReaderTest
- */
-@SmallTest
-@RunWith(AndroidJUnit4.class)
-public class KernelCpuProcReaderTest {
-
-    private File mRoot;
-    private File mTestDir;
-    private File mTestFile;
-    private Random mRand = new Random();
-
-    private KernelCpuProcReader mKernelCpuProcReader;
-
-    private Context getContext() {
-        return InstrumentationRegistry.getContext();
-    }
-
-    @Before
-    public void setUp() {
-        mTestDir = getContext().getDir("test", Context.MODE_PRIVATE);
-        mRoot = getContext().getFilesDir();
-        mTestFile = new File(mTestDir, "test.file");
-        mKernelCpuProcReader = new KernelCpuProcReader(mTestFile.getAbsolutePath());
-    }
-
-    @After
-    public void tearDown() throws Exception {
-        FileUtils.deleteContents(mTestDir);
-        FileUtils.deleteContents(mRoot);
-    }
-
-
-    /**
-     * Tests that reading will return null if the file does not exist.
-     */
-    @Test
-    public void testReadInvalidFile() throws Exception {
-        assertEquals(null, mKernelCpuProcReader.readBytes());
-    }
-
-    /**
-     * Tests that reading will always return null after 5 failures.
-     */
-    @Test
-    public void testReadErrorsLimit() throws Exception {
-        mKernelCpuProcReader.setThrottleInterval(0);
-        for (int i = 0; i < 3; i++) {
-            assertNull(mKernelCpuProcReader.readBytes());
-            SystemClock.sleep(50);
-        }
-
-        final byte[] data = new byte[1024];
-        mRand.nextBytes(data);
-        try (OutputStream os = Files.newOutputStream(mTestFile.toPath())) {
-            os.write(data);
-        }
-        assertTrue(Arrays.equals(data, toArray(mKernelCpuProcReader.readBytes())));
-
-        assertTrue(mTestFile.delete());
-        for (int i = 0; i < 3; i++) {
-            assertNull(mKernelCpuProcReader.readBytes());
-            SystemClock.sleep(50);
-        }
-        try (OutputStream os = Files.newOutputStream(mTestFile.toPath())) {
-            os.write(data);
-        }
-        assertNull(mKernelCpuProcReader.readBytes());
-    }
-
-    /**
-     * Tests reading functionality.
-     */
-    @Test
-    public void testSimpleRead() throws Exception {
-        final byte[] data = new byte[1024];
-        mRand.nextBytes(data);
-        try (OutputStream os = Files.newOutputStream(mTestFile.toPath())) {
-            os.write(data);
-        }
-        assertTrue(Arrays.equals(data, toArray(mKernelCpuProcReader.readBytes())));
-    }
-
-    /**
-     * Tests multiple reading functionality.
-     */
-    @Test
-    public void testMultipleRead() throws Exception {
-        mKernelCpuProcReader.setThrottleInterval(0);
-        for (int i = 0; i < 100; i++) {
-            final byte[] data = new byte[mRand.nextInt(102400) + 4];
-            mRand.nextBytes(data);
-            try (OutputStream os = Files.newOutputStream(mTestFile.toPath())) {
-                os.write(data);
-            }
-            assertTrue(Arrays.equals(data, toArray(mKernelCpuProcReader.readBytes())));
-            assertTrue(mTestFile.delete());
-        }
-    }
-
-    /**
-     * Tests reading with resizing.
-     */
-    @Test
-    public void testReadWithResize() throws Exception {
-        final byte[] data = new byte[128001];
-        mRand.nextBytes(data);
-        try (OutputStream os = Files.newOutputStream(mTestFile.toPath())) {
-            os.write(data);
-        }
-        assertTrue(Arrays.equals(data, toArray(mKernelCpuProcReader.readBytes())));
-    }
-
-    /**
-     * Tests that reading a file over the limit (1MB) will return null.
-     */
-    @Test
-    public void testReadOverLimit() throws Exception {
-        final byte[] data = new byte[1228800];
-        mRand.nextBytes(data);
-        try (OutputStream os = Files.newOutputStream(mTestFile.toPath())) {
-            os.write(data);
-        }
-        assertNull(mKernelCpuProcReader.readBytes());
-    }
-
-    /**
-     * Tests throttling. Deleting underlying file should not affect cache.
-     */
-    @Test
-    public void testThrottle() throws Exception {
-        mKernelCpuProcReader.setThrottleInterval(3000);
-        final byte[] data = new byte[20001];
-        mRand.nextBytes(data);
-        try (OutputStream os = Files.newOutputStream(mTestFile.toPath())) {
-            os.write(data);
-        }
-        assertTrue(Arrays.equals(data, toArray(mKernelCpuProcReader.readBytes())));
-        assertTrue(mTestFile.delete());
-        for (int i = 0; i < 5; i++) {
-            assertTrue(Arrays.equals(data, toArray(mKernelCpuProcReader.readBytes())));
-            SystemClock.sleep(10);
-        }
-        SystemClock.sleep(5000);
-        assertNull(mKernelCpuProcReader.readBytes());
-    }
-
-    private byte[] toArray(ByteBuffer buffer) {
-        assertNotNull(buffer);
-        byte[] arr = new byte[buffer.remaining()];
-        buffer.get(arr);
-        return arr;
-    }
-}
diff --git a/core/tests/coretests/src/com/android/internal/os/KernelCpuProcStringReaderTest.java b/core/tests/coretests/src/com/android/internal/os/KernelCpuProcStringReaderTest.java
index 7a31605..cbd2ba4 100644
--- a/core/tests/coretests/src/com/android/internal/os/KernelCpuProcStringReaderTest.java
+++ b/core/tests/coretests/src/com/android/internal/os/KernelCpuProcStringReaderTest.java
@@ -299,9 +299,10 @@
             assertTrue(mTestFile.delete());
             try (BufferedWriter w = Files.newBufferedWriter(mTestFile.toPath())) {
                 w.write(data1);
-                modify.countDown();
             } catch (Throwable e) {
                 errs.add(e);
+            } finally {
+                modify.countDown();
             }
         }, 600, TimeUnit.MILLISECONDS);
 
diff --git a/core/tests/coretests/src/com/android/internal/os/KernelUidCpuActiveTimeReaderTest.java b/core/tests/coretests/src/com/android/internal/os/KernelUidCpuActiveTimeReaderTest.java
deleted file mode 100644
index 12f6c18..0000000
--- a/core/tests/coretests/src/com/android/internal/os/KernelUidCpuActiveTimeReaderTest.java
+++ /dev/null
@@ -1,260 +0,0 @@
-/*
- * Copyright (C) 2017 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.internal.os;
-
-import static org.mockito.Mockito.verify;
-import static org.mockito.Mockito.verifyNoMoreInteractions;
-import static org.mockito.Mockito.when;
-
-import androidx.test.filters.SmallTest;
-import androidx.test.runner.AndroidJUnit4;
-
-import org.junit.Before;
-import org.junit.Test;
-import org.junit.runner.RunWith;
-import org.mockito.Mock;
-import org.mockito.Mockito;
-import org.mockito.MockitoAnnotations;
-
-import java.nio.ByteBuffer;
-import java.nio.ByteOrder;
-import java.util.Random;
-
-/**
- * Test class for {@link KernelUidCpuActiveTimeReader}.
- *
- * To run it:
- * bit FrameworksCoreTests:com.android.internal.os.KernelUidCpuActiveTimeReaderTest
- */
-@SmallTest
-@RunWith(AndroidJUnit4.class)
-public class KernelUidCpuActiveTimeReaderTest {
-    @Mock
-    private KernelCpuProcReader mProcReader;
-    @Mock
-    private KernelUidCpuActiveTimeReader.Callback mCallback;
-    private KernelUidCpuActiveTimeReader mReader;
-
-    @Before
-    public void setUp() {
-        MockitoAnnotations.initMocks(this);
-        mReader = new KernelUidCpuActiveTimeReader(mProcReader);
-        mReader.setThrottleInterval(0);
-    }
-
-    @Test
-    public void testReadDelta() {
-        final int cores = 8;
-        final int[] uids = {1, 22, 333, 4444, 5555};
-
-        final long[][] times = increaseTime(new long[uids.length][cores]);
-        when(mProcReader.readBytes()).thenReturn(getUidTimesBytes(uids, times));
-        mReader.readDelta(mCallback);
-        for (int i = 0; i < uids.length; i++) {
-            verify(mCallback).onUidCpuActiveTime(uids[i], getTotal(times[i]));
-        }
-        verifyNoMoreInteractions(mCallback);
-
-        // Verify that a second call will only return deltas.
-        Mockito.reset(mCallback);
-        final long[][] times1 = increaseTime(times);
-        when(mProcReader.readBytes()).thenReturn(getUidTimesBytes(uids, times1));
-        mReader.readDelta(mCallback);
-        for (int i = 0; i < uids.length; i++) {
-            verify(mCallback).onUidCpuActiveTime(uids[i], getTotal(subtract(times1[i], times[i])));
-        }
-        verifyNoMoreInteractions(mCallback);
-
-        // Verify that there won't be a callback if the proc file values didn't change.
-        Mockito.reset(mCallback);
-        when(mProcReader.readBytes()).thenReturn(getUidTimesBytes(uids, times1));
-        mReader.readDelta(mCallback);
-        verifyNoMoreInteractions(mCallback);
-
-        // Verify that calling with a null callback doesn't result in any crashes
-        Mockito.reset(mCallback);
-        final long[][] times2 = increaseTime(times1);
-        when(mProcReader.readBytes()).thenReturn(getUidTimesBytes(uids, times2));
-        mReader.readDelta(null);
-
-        // Verify that the readDelta call will only return deltas when
-        // the previous call had null callback.
-        Mockito.reset(mCallback);
-        final long[][] times3 = increaseTime(times2);
-        when(mProcReader.readBytes()).thenReturn(getUidTimesBytes(uids, times3));
-        mReader.readDelta(mCallback);
-        for (int i = 0; i < uids.length; ++i) {
-            verify(mCallback).onUidCpuActiveTime(uids[i], getTotal(subtract(times3[i], times2[i])));
-        }
-        verifyNoMoreInteractions(mCallback);
-    }
-
-    @Test
-    public void testReadAbsolute() {
-        final int cores = 8;
-        final int[] uids = {1, 22, 333, 4444, 5555};
-
-        final long[][] times = increaseTime(new long[uids.length][cores]);
-        when(mProcReader.readBytes()).thenReturn(getUidTimesBytes(uids, times));
-        mReader.readAbsolute(mCallback);
-        for (int i = 0; i < uids.length; i++) {
-            verify(mCallback).onUidCpuActiveTime(uids[i], getTotal(times[i]));
-        }
-        verifyNoMoreInteractions(mCallback);
-
-        // Verify that a second call still returns absolute values
-        Mockito.reset(mCallback);
-        final long[][] times1 = increaseTime(times);
-        when(mProcReader.readBytes()).thenReturn(getUidTimesBytes(uids, times1));
-        mReader.readAbsolute(mCallback);
-        for (int i = 0; i < uids.length; i++) {
-            verify(mCallback).onUidCpuActiveTime(uids[i], getTotal(times1[i]));
-        }
-        verifyNoMoreInteractions(mCallback);
-    }
-
-    @Test
-    public void testReadDelta_malformedData() {
-        final int cores = 8;
-        final int[] uids = {1, 22, 333, 4444, 5555};
-        final long[][] times = increaseTime(new long[uids.length][cores]);
-        when(mProcReader.readBytes()).thenReturn(getUidTimesBytes(uids, times));
-        mReader.readDelta(mCallback);
-        for (int i = 0; i < uids.length; i++) {
-            verify(mCallback).onUidCpuActiveTime(uids[i], getTotal(times[i]));
-        }
-        verifyNoMoreInteractions(mCallback);
-
-        // Verify that there is no callback if subsequent call is in wrong format.
-        Mockito.reset(mCallback);
-        final long[][] times1 = increaseTime(times);
-        when(mProcReader.readBytes()).thenReturn(getUidTimesBytes(uids, times1).putInt(0, 5));
-        mReader.readDelta(mCallback);
-        verifyNoMoreInteractions(mCallback);
-
-        // Verify that the internal state was not modified if the given core count does not match
-        // the following # of entries.
-        Mockito.reset(mCallback);
-        final long[][] times2 = increaseTime(times);
-        when(mProcReader.readBytes()).thenReturn(getUidTimesBytes(uids, times2));
-        mReader.readDelta(mCallback);
-        for (int i = 0; i < uids.length; i++) {
-            verify(mCallback).onUidCpuActiveTime(uids[i], getTotal(subtract(times2[i], times[i])));
-        }
-        verifyNoMoreInteractions(mCallback);
-
-        // Verify that there is no callback if any value in the proc file is -ve.
-        Mockito.reset(mCallback);
-        final long[][] times3 = increaseTime(times2);
-        times3[uids.length - 1][cores - 1] *= -1;
-        when(mProcReader.readBytes()).thenReturn(getUidTimesBytes(uids, times3));
-        mReader.readDelta(mCallback);
-        for (int i = 0; i < uids.length - 1; ++i) {
-            verify(mCallback).onUidCpuActiveTime(uids[i], getTotal(subtract(times3[i], times2[i])));
-        }
-        verifyNoMoreInteractions(mCallback);
-
-        // Verify that the internal state was not modified when the proc file had -ve value.
-        Mockito.reset(mCallback);
-        for (int i = 0; i < cores; i++) {
-            times3[uids.length - 1][i] = times2[uids.length - 1][i] + uids[uids.length - 1] * 2520;
-        }
-        when(mProcReader.readBytes()).thenReturn(getUidTimesBytes(uids, times3));
-        mReader.readDelta(mCallback);
-        verify(mCallback).onUidCpuActiveTime(uids[uids.length - 1],
-                getTotal(subtract(times3[uids.length - 1], times2[uids.length - 1])));
-        verifyNoMoreInteractions(mCallback);
-
-        // Verify that there is no callback if the values in the proc file are decreased.
-        Mockito.reset(mCallback);
-        final long[][] times4 = increaseTime(times3);
-        System.arraycopy(times3[uids.length - 1], 0, times4[uids.length - 1], 0, cores);
-        times4[uids.length - 1][cores - 1] -= 100;
-        when(mProcReader.readBytes()).thenReturn(getUidTimesBytes(uids, times4));
-        mReader.readDelta(mCallback);
-        for (int i = 0; i < uids.length - 1; ++i) {
-            verify(mCallback).onUidCpuActiveTime(uids[i], getTotal(subtract(times4[i], times3[i])));
-        }
-        verifyNoMoreInteractions(mCallback);
-
-        // Verify that the internal state was not modified when the proc file had decreased values.
-        Mockito.reset(mCallback);
-        for (int i = 0; i < cores; i++) {
-            times4[uids.length - 1][i] = times3[uids.length - 1][i] + uids[uids.length - 1] * 2520;
-        }
-        when(mProcReader.readBytes()).thenReturn(getUidTimesBytes(uids, times4));
-        mReader.readDelta(mCallback);
-        verify(mCallback).onUidCpuActiveTime(uids[uids.length - 1],
-                getTotal(subtract(times4[uids.length - 1], times3[uids.length - 1])));
-        verifyNoMoreInteractions(mCallback);
-    }
-
-    private long[] subtract(long[] a1, long[] a2) {
-        long[] val = new long[a1.length];
-        for (int i = 0; i < val.length; ++i) {
-            val[i] = a1[i] - a2[i];
-        }
-        return val;
-    }
-
-    /**
-     * Unit of original and return value is 10ms. What's special about 2520? 2520 is LCM of 1, 2, 3,
-     * ..., 10. So that when wedivide shared cpu time by concurrent thread count, we always get a
-     * nice integer, avoiding rounding errors.
-     */
-    private long[][] increaseTime(long[][] original) {
-        long[][] newTime = new long[original.length][original[0].length];
-        Random rand = new Random();
-        for (int i = 0; i < original.length; i++) {
-            for (int j = 0; j < original[0].length; j++) {
-                newTime[i][j] = original[i][j] + rand.nextInt(1000) * 2520 + 2520;
-            }
-        }
-        return newTime;
-    }
-
-    // Unit of times is 10ms
-    private long getTotal(long[] times) {
-        long sum = 0;
-        for (int i = 0; i < times.length; i++) {
-            sum += times[i] * 10 / (i + 1);
-        }
-        return sum;
-    }
-
-    /**
-     * Format uids and times (in 10ms) into the following format:
-     * [n, uid0, time0a, time0b, ..., time0n,
-     * uid1, time1a, time1b, ..., time1n,
-     * uid2, time2a, time2b, ..., time2n, etc.]
-     * where n is the total number of cpus (num_possible_cpus)
-     */
-    private ByteBuffer getUidTimesBytes(int[] uids, long[][] times) {
-        int size = (1 + uids.length * (times[0].length + 1)) * 4;
-        ByteBuffer buf = ByteBuffer.allocate(size);
-        buf.order(ByteOrder.nativeOrder());
-        buf.putInt(times[0].length);
-        for (int i = 0; i < uids.length; i++) {
-            buf.putInt(uids[i]);
-            for (int j = 0; j < times[i].length; j++) {
-                buf.putInt((int) times[i][j]);
-            }
-        }
-        buf.flip();
-        return buf.order(ByteOrder.nativeOrder());
-    }
-}
diff --git a/core/tests/coretests/src/com/android/internal/os/KernelUidCpuClusterTimeReaderTest.java b/core/tests/coretests/src/com/android/internal/os/KernelUidCpuClusterTimeReaderTest.java
deleted file mode 100644
index 532f337..0000000
--- a/core/tests/coretests/src/com/android/internal/os/KernelUidCpuClusterTimeReaderTest.java
+++ /dev/null
@@ -1,326 +0,0 @@
-/*
- * Copyright (C) 2017 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.internal.os;
-
-import static org.junit.Assert.assertArrayEquals;
-import static org.junit.Assert.assertEquals;
-import static org.junit.Assert.assertNotNull;
-import static org.mockito.Mockito.when;
-
-import android.util.SparseArray;
-
-import androidx.test.filters.SmallTest;
-import androidx.test.runner.AndroidJUnit4;
-
-import org.junit.Before;
-import org.junit.Test;
-import org.junit.runner.RunWith;
-import org.mockito.Mock;
-import org.mockito.Mockito;
-import org.mockito.MockitoAnnotations;
-
-import java.nio.ByteBuffer;
-import java.nio.ByteOrder;
-import java.util.Arrays;
-import java.util.Random;
-
-/**
- * Test class for {@link KernelUidCpuClusterTimeReader}.
- *
- * To run it:
- * bit FrameworksCoreTests:com.android.internal.os.KernelUidCpuClusterTimeReaderTest
- */
-@SmallTest
-@RunWith(AndroidJUnit4.class)
-public class KernelUidCpuClusterTimeReaderTest {
-    @Mock
-    private KernelCpuProcReader mProcReader;
-    private KernelUidCpuClusterTimeReader mReader;
-    private VerifiableCallback mCallback;
-
-    @Before
-    public void setUp() {
-        MockitoAnnotations.initMocks(this);
-        mReader = new KernelUidCpuClusterTimeReader(mProcReader);
-        mCallback = new VerifiableCallback();
-        mReader.setThrottleInterval(0);
-    }
-
-    @Test
-    public void testReadDelta() throws Exception {
-        VerifiableCallback cb = new VerifiableCallback();
-        final int cores = 6;
-        final int[] clusters = {2, 4};
-        final int[] uids = {1, 22, 333, 4444, 5555};
-
-        // Verify initial call
-        final long[][] times = increaseTime(new long[uids.length][cores]);
-        when(mProcReader.readBytes()).thenReturn(getUidTimesBytes(uids, clusters, times));
-        mReader.readDelta(cb);
-        for (int i = 0; i < uids.length; i++) {
-            cb.verify(uids[i], getTotal(clusters, times[i]));
-        }
-        cb.verifyNoMoreInteractions();
-
-        // Verify that a second call will only return deltas.
-        cb.clear();
-        Mockito.reset(mProcReader);
-        final long[][] times1 = increaseTime(times);
-        when(mProcReader.readBytes()).thenReturn(getUidTimesBytes(uids, clusters, times1));
-        mReader.readDelta(cb);
-        for (int i = 0; i < uids.length; i++) {
-            cb.verify(uids[i], getTotal(clusters, subtract(times1[i], times[i])));
-        }
-        cb.verifyNoMoreInteractions();
-
-        // Verify that there won't be a callback if the proc file values didn't change.
-        cb.clear();
-        Mockito.reset(mProcReader);
-        when(mProcReader.readBytes()).thenReturn(getUidTimesBytes(uids, clusters, times1));
-        mReader.readDelta(cb);
-        cb.verifyNoMoreInteractions();
-
-        // Verify that calling with a null callback doesn't result in any crashes
-        Mockito.reset(mProcReader);
-        final long[][] times2 = increaseTime(times1);
-        when(mProcReader.readBytes()).thenReturn(getUidTimesBytes(uids, clusters, times2));
-        mReader.readDelta(null);
-
-        // Verify that the readDelta call will only return deltas when
-        // the previous call had null callback.
-        cb.clear();
-        Mockito.reset(mProcReader);
-        final long[][] times3 = increaseTime(times2);
-        when(mProcReader.readBytes()).thenReturn(getUidTimesBytes(uids, clusters, times3));
-        mReader.readDelta(cb);
-        for (int i = 0; i < uids.length; i++) {
-            cb.verify(uids[i], getTotal(clusters, subtract(times3[i], times2[i])));
-        }
-        cb.verifyNoMoreInteractions();
-
-    }
-
-    @Test
-    public void testReadAbsolute() throws Exception {
-        VerifiableCallback cb = new VerifiableCallback();
-        final int cores = 6;
-        final int[] clusters = {2, 4};
-        final int[] uids = {1, 22, 333, 4444, 5555};
-
-        // Verify return absolute value
-        final long[][] times = increaseTime(new long[uids.length][cores]);
-        when(mProcReader.readBytes()).thenReturn(getUidTimesBytes(uids, clusters, times));
-        mReader.readAbsolute(cb);
-        for (int i = 0; i < uids.length; i++) {
-            cb.verify(uids[i], getTotal(clusters, times[i]));
-        }
-        cb.verifyNoMoreInteractions();
-
-        // Verify that a second call should return the same absolute value
-        cb.clear();
-        Mockito.reset(mProcReader);
-        final long[][] times1 = increaseTime(times);
-        when(mProcReader.readBytes()).thenReturn(getUidTimesBytes(uids, clusters, times1));
-        mReader.readAbsolute(cb);
-        for (int i = 0; i < uids.length; i++) {
-            cb.verify(uids[i], getTotal(clusters, times1[i]));
-        }
-        cb.verifyNoMoreInteractions();
-    }
-
-    @Test
-    public void testReadDelta_malformedData() throws Exception {
-        final int cores = 6;
-        final int[] clusters = {2, 4};
-        final int[] uids = {1, 22, 333, 4444, 5555};
-
-        // Verify initial call
-        final long[][] times = increaseTime(new long[uids.length][cores]);
-        when(mProcReader.readBytes()).thenReturn(getUidTimesBytes(uids, clusters, times));
-        mReader.readDelta(mCallback);
-        for (int i = 0; i < uids.length; i++) {
-            mCallback.verify(uids[i], getTotal(clusters, times[i]));
-        }
-        mCallback.verifyNoMoreInteractions();
-
-        // Verify that there is no callback if a call has wrong format
-        mCallback.clear();
-        Mockito.reset(mProcReader);
-        final long[][] temp = increaseTime(times);
-        final long[][] times1 = new long[uids.length][];
-        for (int i = 0; i < temp.length; i++) {
-            times1[i] = Arrays.copyOfRange(temp[i], 0, 4);
-        }
-        when(mProcReader.readBytes()).thenReturn(getUidTimesBytes(uids, clusters, times1));
-        mReader.readDelta(mCallback);
-        mCallback.verifyNoMoreInteractions();
-
-        // Verify that the internal state was not modified if the given core count does not match
-        // the following # of entries.
-        mCallback.clear();
-        Mockito.reset(mProcReader);
-        final long[][] times2 = increaseTime(times);
-        when(mProcReader.readBytes()).thenReturn(getUidTimesBytes(uids, clusters, times2));
-        mReader.readDelta(mCallback);
-        for (int i = 0; i < uids.length; i++) {
-            mCallback.verify(uids[i], getTotal(clusters, subtract(times2[i], times[i])));
-        }
-        mCallback.verifyNoMoreInteractions();
-
-        // Verify that there is no callback if any value in the proc file is -ve.
-        mCallback.clear();
-        Mockito.reset(mProcReader);
-        final long[][] times3 = increaseTime(times2);
-        times3[uids.length - 1][cores - 1] *= -1;
-        when(mProcReader.readBytes()).thenReturn(getUidTimesBytes(uids, clusters, times3));
-        mReader.readDelta(mCallback);
-        for (int i = 0; i < uids.length - 1; i++) {
-            mCallback.verify(uids[i], getTotal(clusters, subtract(times3[i], times2[i])));
-        }
-        mCallback.verifyNoMoreInteractions();
-
-        // Verify that the internal state was not modified when the proc file had -ve value.
-        mCallback.clear();
-        Mockito.reset(mProcReader);
-        for (int i = 0; i < cores; i++) {
-            times3[uids.length - 1][i] = times2[uids.length - 1][i] + uids[uids.length - 1] * 2520;
-        }
-        when(mProcReader.readBytes()).thenReturn(getUidTimesBytes(uids, clusters, times3));
-        mReader.readDelta(mCallback);
-        mCallback.verify(uids[uids.length - 1],
-                getTotal(clusters, subtract(times3[uids.length - 1], times2[uids.length - 1])));
-
-        // Verify that there is no callback if the values in the proc file are decreased.
-        mCallback.clear();
-        Mockito.reset(mProcReader);
-        final long[][] times4 = increaseTime(times3);
-        System.arraycopy(times3[uids.length - 1], 0, times4[uids.length - 1], 0, cores);
-        times4[uids.length - 1][cores - 1] -= 100;
-        when(mProcReader.readBytes()).thenReturn(getUidTimesBytes(uids, clusters, times4));
-        mReader.readDelta(mCallback);
-        for (int i = 0; i < uids.length - 1; i++) {
-            mCallback.verify(uids[i], getTotal(clusters, subtract(times4[i], times3[i])));
-        }
-        mCallback.verifyNoMoreInteractions();
-
-        // Verify that the internal state was not modified when the proc file had decreased values.
-        mCallback.clear();
-        Mockito.reset(mProcReader);
-        for (int i = 0; i < cores; i++) {
-            times4[uids.length - 1][i] = times3[uids.length - 1][i] + uids[uids.length - 1] * 2520;
-        }
-        when(mProcReader.readBytes()).thenReturn(getUidTimesBytes(uids, clusters, times4));
-        mReader.readDelta(mCallback);
-        mCallback.verify(uids[uids.length - 1],
-                getTotal(clusters, subtract(times3[uids.length - 1], times2[uids.length - 1])));
-        mCallback.verifyNoMoreInteractions();
-    }
-
-
-    private long[] subtract(long[] a1, long[] a2) {
-        long[] val = new long[a1.length];
-        for (int i = 0; i < val.length; ++i) {
-            val[i] = a1[i] - a2[i];
-        }
-        return val;
-    }
-
-    /**
-     * Unit is 10ms. What's special about 2520? 2520 is LCM of 1, 2, 3, ..., 10. So that when we
-     * divide shared cpu time by concurrent thread count, we always get a nice integer, avoiding
-     * rounding errors.
-     */
-    private long[][] increaseTime(long[][] original) {
-        long[][] newTime = new long[original.length][original[0].length];
-        Random rand = new Random();
-        for (int i = 0; i < original.length; i++) {
-            for (int j = 0; j < original[0].length; j++) {
-                newTime[i][j] = original[i][j] + rand.nextInt(1000) * 2520 + 2520;
-            }
-        }
-        return newTime;
-    }
-
-    // Format an array of cluster times according to the algorithm in KernelUidCpuClusterTimeReader
-    private long[] getTotal(int[] cluster, long[] times) {
-        int core = 0;
-        long[] sumTimes = new long[cluster.length];
-        for (int i = 0; i < cluster.length; i++) {
-            double sum = 0;
-            for (int j = 0; j < cluster[i]; j++) {
-                sum += (double) times[core++] * 10 / (j + 1);
-            }
-            sumTimes[i] = (long) sum;
-        }
-        return sumTimes;
-    }
-
-    private class VerifiableCallback implements KernelUidCpuClusterTimeReader.Callback {
-
-        SparseArray<long[]> mData = new SparseArray<>();
-        int count = 0;
-
-        public void verify(int uid, long[] cpuClusterTimeMs) {
-            long[] array = mData.get(uid);
-            assertNotNull(array);
-            assertArrayEquals(cpuClusterTimeMs, array);
-            count++;
-        }
-
-        public void clear() {
-            mData.clear();
-            count = 0;
-        }
-
-        @Override
-        public void onUidCpuPolicyTime(int uid, long[] cpuClusterTimeMs) {
-            long[] array = new long[cpuClusterTimeMs.length];
-            System.arraycopy(cpuClusterTimeMs, 0, array, 0, array.length);
-            mData.put(uid, array);
-        }
-
-        public void verifyNoMoreInteractions() {
-            assertEquals(mData.size(), count);
-        }
-    }
-
-    /**
-     * Format uids and times (in 10ms) into the following format:
-     * [n, x0, ..., xn, uid0, time0a, time0b, ..., time0n,
-     * uid1, time1a, time1b, ..., time1n,
-     * uid2, time2a, time2b, ..., time2n, etc.]
-     * where n is the number of policies
-     * xi is the number cpus on a particular policy
-     */
-    private ByteBuffer getUidTimesBytes(int[] uids, int[] clusters, long[][] times) {
-        int size = (1 + clusters.length + uids.length * (times[0].length + 1)) * 4;
-        ByteBuffer buf = ByteBuffer.allocate(size);
-        buf.order(ByteOrder.nativeOrder());
-        buf.putInt(clusters.length);
-        for (int i = 0; i < clusters.length; i++) {
-            buf.putInt(clusters[i]);
-        }
-        for (int i = 0; i < uids.length; i++) {
-            buf.putInt(uids[i]);
-            for (int j = 0; j < times[i].length; j++) {
-                buf.putInt((int) (times[i][j]));
-            }
-        }
-        buf.flip();
-        return buf.order(ByteOrder.nativeOrder());
-    }
-}
diff --git a/core/tests/coretests/src/com/android/internal/os/KernelUidCpuFreqTimeReaderTest.java b/core/tests/coretests/src/com/android/internal/os/KernelUidCpuFreqTimeReaderTest.java
deleted file mode 100644
index 6d2980b..0000000
--- a/core/tests/coretests/src/com/android/internal/os/KernelUidCpuFreqTimeReaderTest.java
+++ /dev/null
@@ -1,331 +0,0 @@
-/*
- * Copyright (C) 2017 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.internal.os;
-
-import static org.junit.Assert.assertArrayEquals;
-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.mockito.Mockito.verifyZeroInteractions;
-import static org.mockito.Mockito.when;
-
-import android.util.SparseArray;
-
-import androidx.test.filters.SmallTest;
-import androidx.test.runner.AndroidJUnit4;
-
-import org.junit.Before;
-import org.junit.Test;
-import org.junit.runner.RunWith;
-import org.mockito.Mock;
-import org.mockito.Mockito;
-import org.mockito.MockitoAnnotations;
-
-import java.io.BufferedReader;
-import java.nio.ByteBuffer;
-import java.nio.ByteOrder;
-import java.util.Arrays;
-
-/**
- * Test class for {@link KernelUidCpuFreqTimeReader}.
- *
- * To run the tests, use
- *
- * runtest -c com.android.internal.os.KernelUidCpuFreqTimeReaderTest frameworks-core
- *
- * or the following steps:
- *
- * Build: m FrameworksCoreTests
- * Install: adb install -r \
- * ${ANDROID_PRODUCT_OUT}/data/app/FrameworksCoreTests/FrameworksCoreTests.apk
- * Run: adb shell am instrument -e class com.android.internal.os.KernelUidCpuFreqTimeReaderTest -w \
- * com.android.frameworks.coretests/androidx.test.runner.AndroidJUnitRunner
- *
- * or
- *
- * bit FrameworksCoreTests:com.android.internal.os.KernelUidCpuFreqTimeReaderTest
- */
-@SmallTest
-@RunWith(AndroidJUnit4.class)
-public class KernelUidCpuFreqTimeReaderTest {
-    @Mock
-    private BufferedReader mBufferedReader;
-    @Mock
-    private KernelUidCpuFreqTimeReader.Callback mCallback;
-    @Mock
-    private PowerProfile mPowerProfile;
-    @Mock
-    private KernelCpuProcReader mProcReader;
-
-    private KernelUidCpuFreqTimeReader mKernelUidCpuFreqTimeReader;
-
-    @Before
-    public void setUp() {
-        MockitoAnnotations.initMocks(this);
-        mKernelUidCpuFreqTimeReader = new KernelUidCpuFreqTimeReader(mProcReader);
-        mKernelUidCpuFreqTimeReader.setThrottleInterval(0);
-    }
-
-    @Test
-    public void testReadFreqs_perClusterTimesNotAvailable() throws Exception {
-        final long[][] freqs = {
-                {1, 12, 123, 1234},
-                {1, 12, 123, 23, 123, 1234, 12345, 123456},
-                {1, 12, 123, 23, 123, 1234, 12345, 123456, 12, 123, 12345},
-                {1, 12, 123, 23, 2345, 234567}
-        };
-        final int[] numClusters = {2, 2, 3, 1};
-        final int[][] numFreqs = {{3, 6}, {4, 5}, {3, 5, 4}, {3}};
-        for (int i = 0; i < freqs.length; ++i) {
-            setCpuClusterFreqs(numClusters[i], numFreqs[i]);
-            when(mBufferedReader.readLine()).thenReturn(getFreqsLine(freqs[i]));
-            long[] actualFreqs = mKernelUidCpuFreqTimeReader.readFreqs(
-                    mBufferedReader, mPowerProfile);
-            assertArrayEquals(freqs[i], actualFreqs);
-            verifyZeroInteractions(mCallback);
-            final String errMsg = String.format("Freqs=%s, nClusters=%d, nFreqs=%s",
-                    Arrays.toString(freqs[i]), numClusters[i], Arrays.toString(numFreqs[i]));
-            assertFalse(errMsg, mKernelUidCpuFreqTimeReader.perClusterTimesAvailable());
-
-            // Verify that a second call won't read the proc file again
-            Mockito.reset(mBufferedReader);
-            actualFreqs = mKernelUidCpuFreqTimeReader.readFreqs(mPowerProfile);
-            assertArrayEquals(freqs[i], actualFreqs);
-            assertFalse(errMsg, mKernelUidCpuFreqTimeReader.perClusterTimesAvailable());
-
-            // Prepare for next iteration
-            Mockito.reset(mBufferedReader, mPowerProfile);
-        }
-    }
-
-    @Test
-    public void testReadFreqs_perClusterTimesAvailable() throws Exception {
-        final long[][] freqs = {
-                {1, 12, 123, 1234},
-                {1, 12, 123, 23, 123, 1234, 12345, 123456},
-                {1, 12, 123, 23, 123, 1234, 12345, 123456, 12, 123, 12345, 1234567}
-        };
-        final int[] numClusters = {1, 2, 3};
-        final int[][] numFreqs = {{4}, {3, 5}, {3, 5, 4}};
-        for (int i = 0; i < freqs.length; ++i) {
-            setCpuClusterFreqs(numClusters[i], numFreqs[i]);
-            when(mBufferedReader.readLine()).thenReturn(getFreqsLine(freqs[i]));
-            long[] actualFreqs = mKernelUidCpuFreqTimeReader.readFreqs(
-                    mBufferedReader, mPowerProfile);
-            assertArrayEquals(freqs[i], actualFreqs);
-            verifyZeroInteractions(mCallback);
-            final String errMsg = String.format("Freqs=%s, nClusters=%d, nFreqs=%s",
-                    Arrays.toString(freqs[i]), numClusters[i], Arrays.toString(numFreqs[i]));
-            assertTrue(errMsg, mKernelUidCpuFreqTimeReader.perClusterTimesAvailable());
-
-            // Verify that a second call won't read the proc file again
-            Mockito.reset(mBufferedReader);
-            actualFreqs = mKernelUidCpuFreqTimeReader.readFreqs(mPowerProfile);
-            assertArrayEquals(freqs[i], actualFreqs);
-            assertTrue(errMsg, mKernelUidCpuFreqTimeReader.perClusterTimesAvailable());
-
-            // Prepare for next iteration
-            Mockito.reset(mBufferedReader, mPowerProfile);
-        }
-    }
-
-    @Test
-    public void testReadDelta_Binary() throws Exception {
-        VerifiableCallback cb = new VerifiableCallback();
-        final long[] freqs = {110, 123, 145, 167, 289, 997};
-        final int[] uids = {1, 22, 333, 444, 555};
-        final long[][] times = new long[uids.length][freqs.length];
-        for (int i = 0; i < uids.length; ++i) {
-            for (int j = 0; j < freqs.length; ++j) {
-                times[i][j] = uids[i] * freqs[j] * 10;
-            }
-        }
-        when(mBufferedReader.readLine()).thenReturn(getFreqsLine(freqs));
-        long[] actualFreqs = mKernelUidCpuFreqTimeReader.readFreqs(mBufferedReader, mPowerProfile);
-
-        assertArrayEquals(freqs, actualFreqs);
-        when(mProcReader.readBytes()).thenReturn(getUidTimesBytes(uids, times));
-        mKernelUidCpuFreqTimeReader.readDeltaImpl(cb);
-        for (int i = 0; i < uids.length; ++i) {
-            cb.verify(uids[i], times[i]);
-        }
-        cb.verifyNoMoreInteractions();
-
-        // Verify that a second call will only return deltas.
-        cb.clear();
-        Mockito.reset(mProcReader);
-        final long[][] newTimes1 = new long[uids.length][freqs.length];
-        for (int i = 0; i < uids.length; ++i) {
-            for (int j = 0; j < freqs.length; ++j) {
-                newTimes1[i][j] = times[i][j] + (uids[i] + freqs[j]) * 50;
-            }
-        }
-        when(mProcReader.readBytes()).thenReturn(getUidTimesBytes(uids, newTimes1));
-        mKernelUidCpuFreqTimeReader.readDeltaImpl(cb);
-        for (int i = 0; i < uids.length; ++i) {
-            cb.verify(uids[i], subtract(newTimes1[i], times[i]));
-        }
-        cb.verifyNoMoreInteractions();
-
-        // Verify that there won't be a callback if the proc file values didn't change.
-        cb.clear();
-        Mockito.reset(mProcReader);
-        when(mProcReader.readBytes()).thenReturn(getUidTimesBytes(uids, newTimes1));
-        mKernelUidCpuFreqTimeReader.readDeltaImpl(cb);
-        cb.verifyNoMoreInteractions();
-
-        // Verify that calling with a null callback doesn't result in any crashes
-        cb.clear();
-        Mockito.reset(mProcReader);
-        final long[][] newTimes2 = new long[uids.length][freqs.length];
-        for (int i = 0; i < uids.length; ++i) {
-            for (int j = 0; j < freqs.length; ++j) {
-                newTimes2[i][j] = newTimes1[i][j] + (uids[i] * freqs[j]) * 30;
-            }
-        }
-        when(mProcReader.readBytes()).thenReturn(getUidTimesBytes(uids, newTimes2));
-        mKernelUidCpuFreqTimeReader.readDeltaImpl(null);
-        cb.verifyNoMoreInteractions();
-
-        // Verify that the readDelta call will only return deltas when
-        // the previous call had null callback.
-        cb.clear();
-        Mockito.reset(mProcReader);
-        final long[][] newTimes3 = new long[uids.length][freqs.length];
-        for (int i = 0; i < uids.length; ++i) {
-            for (int j = 0; j < freqs.length; ++j) {
-                newTimes3[i][j] = newTimes2[i][j] + (uids[i] + freqs[j]) * 40;
-            }
-        }
-        when(mProcReader.readBytes()).thenReturn(getUidTimesBytes(uids, newTimes3));
-        mKernelUidCpuFreqTimeReader.readDeltaImpl(cb);
-        for (int i = 0; i < uids.length; ++i) {
-            cb.verify(uids[i], subtract(newTimes3[i], newTimes2[i]));
-        }
-        cb.verifyNoMoreInteractions();
-    }
-
-    @Test
-    public void testReadAbsolute() throws Exception {
-        VerifiableCallback cb = new VerifiableCallback();
-        final long[] freqs = {110, 123, 145, 167, 289, 997};
-        final int[] uids = {1, 22, 333, 444, 555};
-        final long[][] times = new long[uids.length][freqs.length];
-        for (int i = 0; i < uids.length; ++i) {
-            for (int j = 0; j < freqs.length; ++j) {
-                times[i][j] = uids[i] * freqs[j] * 10;
-            }
-        }
-        when(mBufferedReader.readLine()).thenReturn(getFreqsLine(freqs));
-        long[] actualFreqs = mKernelUidCpuFreqTimeReader.readFreqs(mBufferedReader, mPowerProfile);
-
-        assertArrayEquals(freqs, actualFreqs);
-        // Verify that the absolute values are returned
-        when(mProcReader.readBytes()).thenReturn(getUidTimesBytes(uids, times));
-        mKernelUidCpuFreqTimeReader.readAbsolute(cb);
-        for (int i = 0; i < uids.length; ++i) {
-            cb.verify(uids[i], times[i]);
-        }
-        cb.verifyNoMoreInteractions();
-
-        // Verify that a second call should still return absolute values
-        cb.clear();
-        Mockito.reset(mProcReader);
-        final long[][] newTimes1 = new long[uids.length][freqs.length];
-        for (int i = 0; i < uids.length; ++i) {
-            for (int j = 0; j < freqs.length; ++j) {
-                newTimes1[i][j] = times[i][j] + (uids[i] + freqs[j]) * 50;
-            }
-        }
-        when(mProcReader.readBytes()).thenReturn(getUidTimesBytes(uids, newTimes1));
-        mKernelUidCpuFreqTimeReader.readAbsolute(cb);
-        for (int i = 0; i < uids.length; ++i) {
-            cb.verify(uids[i], newTimes1[i]);
-        }
-        cb.verifyNoMoreInteractions();
-    }
-
-    private long[] subtract(long[] a1, long[] a2) {
-        long[] val = new long[a1.length];
-        for (int i = 0; i < val.length; ++i) {
-            val[i] = a1[i] - a2[i];
-        }
-        return val;
-    }
-
-    private String getFreqsLine(long[] freqs) {
-        final StringBuilder sb = new StringBuilder();
-        sb.append("uid:");
-        for (int i = 0; i < freqs.length; ++i) {
-            sb.append(" " + freqs[i]);
-        }
-        return sb.toString();
-    }
-
-    private ByteBuffer getUidTimesBytes(int[] uids, long[][] times) {
-        int size = (1 + uids.length + uids.length * times[0].length) * 4;
-        ByteBuffer buf = ByteBuffer.allocate(size);
-        buf.order(ByteOrder.nativeOrder());
-        buf.putInt(times[0].length);
-        for (int i = 0; i < uids.length; i++) {
-            buf.putInt(uids[i]);
-            for (int j = 0; j < times[i].length; j++) {
-                buf.putInt((int) (times[i][j] / 10));
-            }
-        }
-        buf.flip();
-        return buf.asReadOnlyBuffer().order(ByteOrder.nativeOrder());
-    }
-
-    private void setCpuClusterFreqs(int numClusters, int... clusterFreqs) {
-        assertEquals(numClusters, clusterFreqs.length);
-        when(mPowerProfile.getNumCpuClusters()).thenReturn(numClusters);
-        for (int i = 0; i < numClusters; ++i) {
-            when(mPowerProfile.getNumSpeedStepsInCpuCluster(i)).thenReturn(clusterFreqs[i]);
-        }
-    }
-
-    private class VerifiableCallback implements KernelUidCpuFreqTimeReader.Callback {
-
-        SparseArray<long[]> mData = new SparseArray<>();
-        int count = 0;
-
-        public void verify(int uid, long[] cpuFreqTimeMs) {
-            long[] array = mData.get(uid);
-            assertNotNull(array);
-            assertArrayEquals(cpuFreqTimeMs, array);
-            count++;
-        }
-
-        public void clear() {
-            mData.clear();
-            count = 0;
-        }
-
-        @Override
-        public void onUidCpuFreqTime(int uid, long[] cpuFreqTimeMs) {
-            long[] array = new long[cpuFreqTimeMs.length];
-            System.arraycopy(cpuFreqTimeMs, 0, array, 0, array.length);
-            mData.put(uid, array);
-        }
-
-        public void verifyNoMoreInteractions() {
-            assertEquals(mData.size(), count);
-        }
-    }
-}
diff --git a/core/tests/coretests/src/com/android/internal/os/MockBatteryStatsImpl.java b/core/tests/coretests/src/com/android/internal/os/MockBatteryStatsImpl.java
index c18445e..bc0e0a4 100644
--- a/core/tests/coretests/src/com/android/internal/os/MockBatteryStatsImpl.java
+++ b/core/tests/coretests/src/com/android/internal/os/MockBatteryStatsImpl.java
@@ -21,6 +21,10 @@
 import android.util.SparseIntArray;
 
 import com.android.internal.location.gnssmetrics.GnssMetrics;
+import com.android.internal.os.KernelCpuUidTimeReader.KernelCpuUidActiveTimeReader;
+import com.android.internal.os.KernelCpuUidTimeReader.KernelCpuUidClusterTimeReader;
+import com.android.internal.os.KernelCpuUidTimeReader.KernelCpuUidFreqTimeReader;
+import com.android.internal.os.KernelCpuUidTimeReader.KernelCpuUidUserSysTimeReader;
 
 import java.util.ArrayList;
 import java.util.Queue;
@@ -43,13 +47,14 @@
         mBluetoothScanTimer = new StopwatchTimer(mClocks, null, -14, null, mOnBatteryTimeBase);
         setExternalStatsSyncLocked(new DummyExternalStatsSync());
 
-        for (int i=0; i< GnssMetrics.NUM_GPS_SIGNAL_QUALITY_LEVELS; i++) {
-            mGpsSignalQualityTimer[i] = new StopwatchTimer(clocks, null, -1000-i, null,
-                mOnBatteryTimeBase);
+        for (int i = 0; i < GnssMetrics.NUM_GPS_SIGNAL_QUALITY_LEVELS; i++) {
+            mGpsSignalQualityTimer[i] = new StopwatchTimer(clocks, null, -1000 - i, null,
+                    mOnBatteryTimeBase);
         }
 
         // A no-op handler.
-        mHandler = new Handler(Looper.getMainLooper()) {};
+        mHandler = new Handler(Looper.getMainLooper()) {
+        };
     }
 
     MockBatteryStatsImpl() {
@@ -95,23 +100,26 @@
         return this;
     }
 
-    public MockBatteryStatsImpl setKernelUidCpuFreqTimeReader(KernelUidCpuFreqTimeReader reader) {
-        mKernelUidCpuFreqTimeReader = reader;
+    public MockBatteryStatsImpl setKernelCpuUidFreqTimeReader(KernelCpuUidFreqTimeReader reader) {
+        mCpuUidFreqTimeReader = reader;
         return this;
     }
 
-    public MockBatteryStatsImpl setKernelUidCpuActiveTimeReader(KernelUidCpuActiveTimeReader reader) {
-        mKernelUidCpuActiveTimeReader = reader;
+    public MockBatteryStatsImpl setKernelCpuUidActiveTimeReader(
+            KernelCpuUidActiveTimeReader reader) {
+        mCpuUidActiveTimeReader = reader;
         return this;
     }
 
-    public MockBatteryStatsImpl setKernelUidCpuClusterTimeReader(KernelUidCpuClusterTimeReader reader) {
-        mKernelUidCpuClusterTimeReader = reader;
+    public MockBatteryStatsImpl setKernelCpuUidClusterTimeReader(
+            KernelCpuUidClusterTimeReader reader) {
+        mCpuUidClusterTimeReader = reader;
         return this;
     }
 
-    public MockBatteryStatsImpl setKernelUidCpuTimeReader(KernelUidCpuTimeReader reader) {
-        mKernelUidCpuTimeReader = reader;
+    public MockBatteryStatsImpl setKernelCpuUidUserSysTimeReader(
+            KernelCpuUidUserSysTimeReader reader) {
+        mCpuUidUserSysTimeReader = reader;
         return this;
     }
 
diff --git a/core/tests/coretests/src/com/android/internal/statusbar/NotificationVisibilityTest.java b/core/tests/coretests/src/com/android/internal/statusbar/NotificationVisibilityTest.java
new file mode 100644
index 0000000..b740ecc
--- /dev/null
+++ b/core/tests/coretests/src/com/android/internal/statusbar/NotificationVisibilityTest.java
@@ -0,0 +1,45 @@
+/*
+ * 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.internal.statusbar;
+
+import static org.hamcrest.Matchers.is;
+import static org.junit.Assert.assertThat;
+
+import android.support.test.filters.SmallTest;
+import android.support.test.runner.AndroidJUnit4;
+
+import com.android.internal.logging.nano.MetricsProto.MetricsEvent;
+
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+import java.lang.reflect.Field;
+
+@RunWith(AndroidJUnit4.class)
+@SmallTest
+public final class NotificationVisibilityTest {
+
+    @Test
+    public void testNotificationLocation_sameValuesAsMetricsProto() throws Exception {
+        for (NotificationVisibility.NotificationLocation location :
+                NotificationVisibility.NotificationLocation.values()) {
+            Field locationField = MetricsEvent.class.getField(location.name());
+            int metricsValue = locationField.getInt(null);
+            assertThat(metricsValue, is(location.toMetricsEventEnum()));
+        }
+    }
+}
diff --git a/core/tests/coretests/src/com/android/server/wm/test/filters/CoreTestsFilter.java b/core/tests/coretests/src/com/android/server/wm/test/filters/CoreTestsFilter.java
new file mode 100644
index 0000000..1a81c2c
--- /dev/null
+++ b/core/tests/coretests/src/com/android/server/wm/test/filters/CoreTestsFilter.java
@@ -0,0 +1,48 @@
+/*
+ * 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.test.filters;
+
+import android.os.Bundle;
+
+import com.android.test.filters.SelectTest;
+
+/**
+ * JUnit test filter that select Window Manager Service related tests from FrameworksCoreTests.
+ *
+ * <p>Use this filter when running FrameworksCoreTests as
+ * <pre>
+ * adb shell am instrument -w \
+ *     -e filter com.android.server.wm.test.filters.CoreTestsFilter  \
+ *     -e selectTest_verbose true \
+ *     com.android.frameworks.coretests/androidx.test.runner.AndroidJUnitRunner
+ * </pre>
+ */
+public final class CoreTestsFilter extends SelectTest {
+
+    private static final String[] SELECTED_CORE_TESTS = {
+            "android.app.servertransaction.", // all tests under the package.
+            "android.view.DisplayCutoutTest",
+            "android.view.InsetsControllerTest",
+            "android.view.InsetsSourceTest",
+            "android.view.InsetsSourceConsumerTest",
+            "android.view.InsetsStateTest",
+    };
+
+    public CoreTestsFilter(Bundle testArgs) {
+        super(addSelectTest(testArgs, SELECTED_CORE_TESTS));
+    }
+}
diff --git a/core/tests/hdmitests/Android.mk b/core/tests/hdmitests/Android.mk
index 2ca31a6..f155feb 100644
--- a/core/tests/hdmitests/Android.mk
+++ b/core/tests/hdmitests/Android.mk
@@ -20,7 +20,7 @@
 # Include all test java files
 LOCAL_SRC_FILES := $(call all-java-files-under, src)
 
-LOCAL_STATIC_JAVA_LIBRARIES := androidx.test.rules frameworks-base-testutils
+LOCAL_STATIC_JAVA_LIBRARIES := androidx.test.rules frameworks-base-testutils truth-prebuilt
 
 LOCAL_JAVA_LIBRARIES := android.test.runner
 LOCAL_PACKAGE_NAME := HdmiCecTests
diff --git a/core/tests/hdmitests/src/android/hardware/hdmi/HdmiUtilsTest.java b/core/tests/hdmitests/src/android/hardware/hdmi/HdmiUtilsTest.java
new file mode 100644
index 0000000..fdc6b84
--- /dev/null
+++ b/core/tests/hdmitests/src/android/hardware/hdmi/HdmiUtilsTest.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.hardware.hdmi;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import androidx.test.filters.SmallTest;
+
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.JUnit4;
+/**
+ * Tests for {@link HdmiUtils}.
+ */
+@RunWith(JUnit4.class)
+@SmallTest
+public class HdmiUtilsTest {
+    @Test
+    public void testInvalidAddress() {
+        assertThat(HdmiUtils.getHdmiAddressRelativePosition(0, -1))
+                .isEqualTo(HdmiUtils.HDMI_RELATIVE_POSITION_UNKNOWN);
+        assertThat(HdmiUtils.getHdmiAddressRelativePosition(0xFFFF, 0xFFFF))
+                .isEqualTo(HdmiUtils.HDMI_RELATIVE_POSITION_UNKNOWN);
+        assertThat(HdmiUtils.getHdmiAddressRelativePosition(0xFFFFF, 0))
+                .isEqualTo(HdmiUtils.HDMI_RELATIVE_POSITION_UNKNOWN);
+    }
+
+    @Test
+    public void testSameAddress() {
+        assertThat(HdmiUtils.getHdmiAddressRelativePosition(0x1000, 0x1000))
+                .isEqualTo(HdmiUtils.HDMI_RELATIVE_POSITION_SAME);
+    }
+
+    @Test
+    public void testDirectlyAbove() {
+        assertThat(HdmiUtils.getHdmiAddressRelativePosition(0x1000, 0x1200))
+                .isEqualTo(HdmiUtils.HDMI_RELATIVE_POSITION_DIRECTLY_ABOVE);
+    }
+
+    @Test
+    public void testDirectlyAbove_rootDevice() {
+        assertThat(HdmiUtils.getHdmiAddressRelativePosition(0x0000, 0x2000))
+                .isEqualTo(HdmiUtils.HDMI_RELATIVE_POSITION_DIRECTLY_ABOVE);
+    }
+
+    @Test
+    public void testDirectlyAbove_leafDevice() {
+        assertThat(HdmiUtils.getHdmiAddressRelativePosition(0x1240, 0x1245))
+                .isEqualTo(HdmiUtils.HDMI_RELATIVE_POSITION_DIRECTLY_ABOVE);
+    }
+
+    @Test
+    public void testAbove() {
+        assertThat(HdmiUtils.getHdmiAddressRelativePosition(0x1000, 0x1210))
+                .isEqualTo(HdmiUtils.HDMI_RELATIVE_POSITION_ABOVE);
+    }
+
+    @Test
+    public void testAbove_rootDevice() {
+        assertThat(HdmiUtils.getHdmiAddressRelativePosition(0x0000, 0x1200))
+                .isEqualTo(HdmiUtils.HDMI_RELATIVE_POSITION_ABOVE);
+    }
+
+    @Test
+    public void testDirectlyBelow() {
+        assertThat(HdmiUtils.getHdmiAddressRelativePosition(0x2250, 0x2200))
+                .isEqualTo(HdmiUtils.HDMI_RELATIVE_POSITION_DIRECTLY_BELOW);
+    }
+
+    @Test
+    public void testDirectlyBelow_rootDevice() {
+        assertThat(HdmiUtils.getHdmiAddressRelativePosition(0x5000, 0x0000))
+                .isEqualTo(HdmiUtils.HDMI_RELATIVE_POSITION_DIRECTLY_BELOW);
+    }
+
+    @Test
+    public void testDirectlyBelow_leafDevice() {
+        assertThat(HdmiUtils.getHdmiAddressRelativePosition(0x3249, 0x3240))
+                .isEqualTo(HdmiUtils.HDMI_RELATIVE_POSITION_DIRECTLY_BELOW);
+    }
+
+    @Test
+    public void testBelow() {
+        assertThat(HdmiUtils.getHdmiAddressRelativePosition(0x5143, 0x5100))
+                .isEqualTo(HdmiUtils.HDMI_RELATIVE_POSITION_BELOW);
+    }
+
+    @Test
+    public void testBelow_rootDevice() {
+        assertThat(HdmiUtils.getHdmiAddressRelativePosition(0x3420, 0x0000))
+                .isEqualTo(HdmiUtils.HDMI_RELATIVE_POSITION_BELOW);
+    }
+
+    @Test
+    public void testSibling() {
+        assertThat(HdmiUtils.getHdmiAddressRelativePosition(0x4000, 0x5000))
+                .isEqualTo(HdmiUtils.HDMI_RELATIVE_POSITION_SIBLING);
+    }
+
+    @Test
+    public void testSibling_leafDevice() {
+        assertThat(HdmiUtils.getHdmiAddressRelativePosition(0x798A, 0x798F))
+                .isEqualTo(HdmiUtils.HDMI_RELATIVE_POSITION_SIBLING);
+    }
+
+    @Test
+    public void testDifferentBranch() {
+        assertThat(HdmiUtils.getHdmiAddressRelativePosition(0x798A, 0x7970))
+                .isEqualTo(HdmiUtils.HDMI_RELATIVE_POSITION_DIFFERENT_BRANCH);
+    }
+
+    @Test
+    public void isValidPysicalAddress_true() {
+        assertThat(HdmiUtils.isValidPhysicalAddress(0)).isTrue();
+        assertThat(HdmiUtils.isValidPhysicalAddress(0xFFFE)).isTrue();
+        assertThat(HdmiUtils.isValidPhysicalAddress(0x1200)).isTrue();
+    }
+
+    @Test
+    public void isValidPysicalAddress_outOfRange() {
+        assertThat(HdmiUtils.isValidPhysicalAddress(-1)).isFalse();
+        assertThat(HdmiUtils.isValidPhysicalAddress(0xFFFF)).isFalse();
+        assertThat(HdmiUtils.isValidPhysicalAddress(0x10000)).isFalse();
+    }
+
+    @Test
+    public void isValidPysicalAddress_nonTrailingZeros() {
+        assertThat(HdmiUtils.isValidPhysicalAddress(0x0001)).isFalse();
+        assertThat(HdmiUtils.isValidPhysicalAddress(0x0213)).isFalse();
+    }
+}
diff --git a/core/tests/utiltests/src/com/android/internal/util/LockPatternUtilsTest.java b/core/tests/utiltests/src/com/android/internal/util/LockPatternUtilsTest.java
index 03cf3eb..9913531 100644
--- a/core/tests/utiltests/src/com/android/internal/util/LockPatternUtilsTest.java
+++ b/core/tests/utiltests/src/com/android/internal/util/LockPatternUtilsTest.java
@@ -21,6 +21,7 @@
 
 import static org.junit.Assert.assertFalse;
 import static org.junit.Assert.assertTrue;
+import static org.mockito.Mockito.doReturn;
 import static org.mockito.Mockito.spy;
 import static org.mockito.Mockito.when;
 
@@ -67,6 +68,7 @@
         // TODO(b/63758238): stop spying the class under test
         mLockPatternUtils = spy(new LockPatternUtils(context));
         when(mLockPatternUtils.getLockSettings()).thenReturn(ils);
+        doReturn(true).when(mLockPatternUtils).hasSecureLockScreen();
 
         final UserInfo userInfo = Mockito.mock(UserInfo.class);
         when(userInfo.isDemo()).thenReturn(isDemoUser);
diff --git a/data/etc/privapp-permissions-platform.xml b/data/etc/privapp-permissions-platform.xml
index 597d14a..904c3fb 100644
--- a/data/etc/privapp-permissions-platform.xml
+++ b/data/etc/privapp-permissions-platform.xml
@@ -180,6 +180,7 @@
         <permission name="android.permission.WRITE_APN_SETTINGS"/>
         <permission name="android.permission.WRITE_SECURE_SETTINGS"/>
         <permission name="android.permission.WRITE_EMBEDDED_SUBSCRIPTIONS"/>
+        <permission name="android.permission.READ_PRECISE_PHONE_STATE"/>
         <permission name="com.android.voicemail.permission.READ_VOICEMAIL"/>
         <permission name="com.android.voicemail.permission.WRITE_VOICEMAIL"/>
     </privapp-permissions>
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/HardwareRenderer.java b/graphics/java/android/graphics/HardwareRenderer.java
index e402055..c4ddd50 100644
--- a/graphics/java/android/graphics/HardwareRenderer.java
+++ b/graphics/java/android/graphics/HardwareRenderer.java
@@ -20,6 +20,7 @@
 import android.annotation.IntDef;
 import android.annotation.NonNull;
 import android.annotation.Nullable;
+import android.annotation.TestApi;
 import android.app.Activity;
 import android.app.ActivityManager;
 import android.os.IBinder;
@@ -667,6 +668,17 @@
         nSetContentDrawBounds(mNativeProxy, left, top, right, bottom);
     }
 
+    /** @hide */
+    public void setPictureCaptureCallback(@Nullable PictureCapturedCallback callback) {
+        nSetPictureCaptureCallback(mNativeProxy, callback);
+    }
+
+    /** called by native */
+    static void invokePictureCapturedCallback(long picturePtr, PictureCapturedCallback callback) {
+        Picture picture = new Picture(picturePtr);
+        callback.onPictureCaptured(picture);
+    }
+
     /**
      * Interface used to receive callbacks when a frame is being drawn.
      *
@@ -695,6 +707,17 @@
         void onFrameComplete(long frameNr);
     }
 
+    /**
+     * Interface for listening to picture captures
+     * @hide
+     */
+    @TestApi
+    public interface PictureCapturedCallback {
+        /** @hide */
+        @TestApi
+        void onPictureCaptured(Picture picture);
+    }
+
     private static void validateAlpha(float alpha, String argumentName) {
         if (!(alpha >= 0.0f && alpha <= 1.0f)) {
             throw new IllegalArgumentException(argumentName + " must be a valid alpha, "
@@ -998,6 +1021,9 @@
     private static native void nSetContentDrawBounds(long nativeProxy, int left,
             int top, int right, int bottom);
 
+    private static native void nSetPictureCaptureCallback(long nativeProxy,
+            PictureCapturedCallback callback);
+
     private static native void nSetFrameCallback(long nativeProxy, FrameDrawingCallback callback);
 
     private static native void nSetFrameCompleteCallback(long nativeProxy,
diff --git a/graphics/java/android/graphics/Picture.java b/graphics/java/android/graphics/Picture.java
index f6d801b..8d12cbf 100644
--- a/graphics/java/android/graphics/Picture.java
+++ b/graphics/java/android/graphics/Picture.java
@@ -16,6 +16,7 @@
 
 package android.graphics;
 
+import android.annotation.NonNull;
 import android.annotation.UnsupportedAppUsage;
 
 import java.io.InputStream;
@@ -34,7 +35,8 @@
  */
 public class Picture {
     private PictureCanvas mRecordingCanvas;
-    @UnsupportedAppUsage
+    // TODO: Figure out if this was a false-positive
+    @UnsupportedAppUsage(maxTargetSdk = 28)
     private long mNativePicture;
     private boolean mRequiresHwAcceleration;
 
@@ -56,23 +58,43 @@
         this(nativeConstructor(src != null ? src.mNativePicture : 0));
     }
 
-    private Picture(long nativePicture) {
+    /** @hide */
+    public Picture(long nativePicture) {
         if (nativePicture == 0) {
-            throw new RuntimeException();
+            throw new IllegalArgumentException();
         }
         mNativePicture = nativePicture;
     }
 
+    /**
+     * Immediately releases the backing data of the Picture. This object will no longer
+     * be usable after calling this, and any further calls on the Picture will throw an
+     * IllegalStateException.
+     * // TODO: Support?
+     * @hide
+     */
+    public void close() {
+        if (mNativePicture != 0) {
+            nativeDestructor(mNativePicture);
+            mNativePicture = 0;
+        }
+    }
+
     @Override
     protected void finalize() throws Throwable {
         try {
-            nativeDestructor(mNativePicture);
-            mNativePicture = 0;
+            close();
         } finally {
             super.finalize();
         }
     }
 
+    private void verifyValid() {
+        if (mNativePicture == 0) {
+            throw new IllegalStateException("Picture is destroyed");
+        }
+    }
+
     /**
      * To record a picture, call beginRecording() and then draw into the Canvas
      * that is returned. Nothing we appear on screen, but all of the draw
@@ -81,7 +103,9 @@
      * that was returned must no longer be used, and nothing should be drawn
      * into it.
      */
+    @NonNull
     public Canvas beginRecording(int width, int height) {
+        verifyValid();
         if (mRecordingCanvas != null) {
             throw new IllegalStateException("Picture already recording, must call #endRecording()");
         }
@@ -98,6 +122,7 @@
      * or {@link Canvas#drawPicture(Picture)} is called.
      */
     public void endRecording() {
+        verifyValid();
         if (mRecordingCanvas != null) {
             mRequiresHwAcceleration = mRecordingCanvas.mHoldsHwBitmap;
             mRecordingCanvas = null;
@@ -110,7 +135,8 @@
      * does not reflect (per se) the content of the picture.
      */
     public int getWidth() {
-      return nativeGetWidth(mNativePicture);
+        verifyValid();
+        return nativeGetWidth(mNativePicture);
     }
 
     /**
@@ -118,7 +144,8 @@
      * does not reflect (per se) the content of the picture.
      */
     public int getHeight() {
-      return nativeGetHeight(mNativePicture);
+        verifyValid();
+        return nativeGetHeight(mNativePicture);
     }
 
     /**
@@ -133,6 +160,7 @@
      *         false otherwise.
      */
     public boolean requiresHardwareAcceleration() {
+        verifyValid();
         return mRequiresHwAcceleration;
     }
 
@@ -149,7 +177,8 @@
      *
      * @param canvas  The picture is drawn to this canvas
      */
-    public void draw(Canvas canvas) {
+    public void draw(@NonNull Canvas canvas) {
+        verifyValid();
         if (mRecordingCanvas != null) {
             endRecording();
         }
@@ -172,7 +201,7 @@
      * raw or compressed pixels.
      */
     @Deprecated
-    public static Picture createFromStream(InputStream stream) {
+    public static Picture createFromStream(@NonNull InputStream stream) {
         return new Picture(nativeCreateFromStream(stream, new byte[WORKING_STREAM_STORAGE]));
     }
 
@@ -188,10 +217,11 @@
      * Bitmap from which you can persist it as raw or compressed pixels.
      */
     @Deprecated
-    public void writeToStream(OutputStream stream) {
+    public void writeToStream(@NonNull OutputStream stream) {
+        verifyValid();
         // do explicit check before calling the native method
         if (stream == null) {
-            throw new NullPointerException();
+            throw new IllegalArgumentException("stream cannot be null");
         }
         if (!nativeWriteToStream(mNativePicture, stream, new byte[WORKING_STREAM_STORAGE])) {
             throw new RuntimeException();
diff --git a/graphics/java/android/graphics/RenderNode.java b/graphics/java/android/graphics/RenderNode.java
index 3b1d44b..09b18b7 100644
--- a/graphics/java/android/graphics/RenderNode.java
+++ b/graphics/java/android/graphics/RenderNode.java
@@ -184,7 +184,7 @@
      *
      * @param name The name of the RenderNode, used for debugging purpose. May be null.
      */
-    public RenderNode(String name) {
+    public RenderNode(@Nullable String name) {
         this(name, null);
     }
 
diff --git a/graphics/proto/Android.bp b/graphics/proto/Android.bp
new file mode 100644
index 0000000..1d06348
--- /dev/null
+++ b/graphics/proto/Android.bp
@@ -0,0 +1,11 @@
+java_library_static {
+    name: "game-driver-protos",
+    host_supported: true,
+    proto: {
+        type: "lite",
+    },
+    srcs: ["game_driver.proto"],
+    no_framework_libs: true,
+    jarjar_rules: "jarjar-rules.txt",
+    sdk_version: "28",
+}
diff --git a/tests/utils/SleepUtils/AlarmService/src/com/android/testing/alarmservice/Alarm.aidl b/graphics/proto/game_driver.proto
similarity index 61%
copy from tests/utils/SleepUtils/AlarmService/src/com/android/testing/alarmservice/Alarm.aidl
copy to graphics/proto/game_driver.proto
index 62a8c48..fd7ffcc 100644
--- a/tests/utils/SleepUtils/AlarmService/src/com/android/testing/alarmservice/Alarm.aidl
+++ b/graphics/proto/game_driver.proto
@@ -1,5 +1,5 @@
 /*
- * Copyright (C) 2013 The Android Open Source Project
+ * 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.
@@ -14,10 +14,18 @@
  * limitations under the License.
  */
 
-package com.android.testing.alarmservice;
+syntax = "proto2";
 
-interface Alarm {
-    int prepare();
-    int setAlarmAndWait(long timeoutMills);
-    int done();
-}
\ No newline at end of file
+package android.gamedriver;
+
+option java_package = "android.gamedriver";
+option java_outer_classname = "GameDriverProto";
+
+message Blacklist {
+    optional int64 version_code = 1;
+    repeated string package_names = 2;
+}
+
+message Blacklists {
+    repeated Blacklist blacklists = 1;
+}
diff --git a/graphics/proto/jarjar-rules.txt b/graphics/proto/jarjar-rules.txt
new file mode 100644
index 0000000..4e40637
--- /dev/null
+++ b/graphics/proto/jarjar-rules.txt
@@ -0,0 +1 @@
+rule com.google.protobuf.** com.android.framework.protobuf.@1
diff --git a/jarjar_rules_hidl.txt b/jarjar_rules_hidl.txt
new file mode 100644
index 0000000..4b2331d
--- /dev/null
+++ b/jarjar_rules_hidl.txt
@@ -0,0 +1 @@
+rule android.hidl.** android.internal.hidl.@1
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/VectorDrawable.cpp b/libs/hwui/VectorDrawable.cpp
index dd62bbb..7265692 100644
--- a/libs/hwui/VectorDrawable.cpp
+++ b/libs/hwui/VectorDrawable.cpp
@@ -551,6 +551,19 @@
     SkPaint paint = inPaint;
     paint.setAlpha(mProperties.getRootAlpha() * 255);
 
+    if (canvas->getGrContext() == nullptr) {
+        // Recording to picture, don't use the SkSurface which won't work off of renderthread.
+        Bitmap& bitmap = getBitmapUpdateIfDirty();
+        SkBitmap skiaBitmap;
+        bitmap.getSkBitmap(&skiaBitmap);
+
+        int scaledWidth = SkScalarCeilToInt(mProperties.getScaledWidth());
+        int scaledHeight = SkScalarCeilToInt(mProperties.getScaledHeight());
+        canvas->drawBitmapRect(skiaBitmap, SkRect::MakeWH(scaledWidth, scaledHeight), bounds,
+                               &paint, SkCanvas::kFast_SrcRectConstraint);
+        return;
+    }
+
     SkRect src;
     sk_sp<SkSurface> vdSurface = mCache.getSurface(&src);
     if (vdSurface) {
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 240efb4..a1b2b18 100644
--- a/libs/hwui/pipeline/skia/GLFunctorDrawable.cpp
+++ b/libs/hwui/pipeline/skia/GLFunctorDrawable.cpp
@@ -74,7 +74,13 @@
 
 void GLFunctorDrawable::onDraw(SkCanvas* canvas) {
     if (canvas->getGrContext() == nullptr) {
-        SkDEBUGF(("Attempting to draw GLFunctor into an unsupported surface"));
+        // We're dumping a picture, render a light-blue rectangle instead
+        // TODO: Draw the WebView text on top? Seemingly complicated as SkPaint doesn't
+        // seem to have a default typeface that works. We only ever use drawGlyphs, which
+        // requires going through minikin & hwui's canvas which we don't have here.
+        SkPaint paint;
+        paint.setColor(0xFF81D4FA);
+        canvas->drawRect(mBounds, paint);
         return;
     }
 
@@ -132,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 df82243..a00a36f 100644
--- a/libs/hwui/pipeline/skia/SkiaPipeline.cpp
+++ b/libs/hwui/pipeline/skia/SkiaPipeline.cpp
@@ -111,7 +111,7 @@
 
             const Rect& layerDamage = layers.entries()[i].damage;
 
-            SkCanvas* layerCanvas = tryCapture(layerNode->getLayerSurface());
+            SkCanvas* layerCanvas = layerNode->getLayerSurface()->getCanvas();
 
             int saveCount = layerCanvas->save();
             SkASSERT(saveCount == 1);
@@ -139,8 +139,6 @@
             layerCanvas->restoreToCount(saveCount);
             mLightCenter = savedLightCenter;
 
-            endCapture(layerNode->getLayerSurface());
-
             // cache the current context so that we can defer flushing it until
             // either all the layers have been rendered or the context changes
             GrContext* currentContext = layerNode->getLayerSurface()->getCanvas()->getGrContext();
@@ -244,6 +242,7 @@
     }
 
     virtual void onProcess(const sp<Task<bool>>& task) override {
+        ATRACE_NAME("SavePictureTask");
         SavePictureTask* t = static_cast<SavePictureTask*>(task.get());
 
         if (0 == access(t->filename.c_str(), F_OK)) {
@@ -265,46 +264,56 @@
 
 SkCanvas* SkiaPipeline::tryCapture(SkSurface* surface) {
     if (CC_UNLIKELY(Properties::skpCaptureEnabled)) {
-        bool recordingPicture = mCaptureSequence > 0;
         char prop[PROPERTY_VALUE_MAX] = {'\0'};
-        if (!recordingPicture) {
+        if (mCaptureSequence <= 0) {
             property_get(PROPERTY_CAPTURE_SKP_FILENAME, prop, "0");
-            recordingPicture = prop[0] != '0' &&
-                               mCapturedFile != prop;  // ensure we capture only once per filename
-            if (recordingPicture) {
+            if (prop[0] != '0' && mCapturedFile != prop) {
                 mCapturedFile = prop;
                 mCaptureSequence = property_get_int32(PROPERTY_CAPTURE_SKP_FRAMES, 1);
             }
         }
-        if (recordingPicture) {
+        if (mCaptureSequence > 0 || mPictureCapturedCallback) {
             mRecorder.reset(new SkPictureRecorder());
-            return mRecorder->beginRecording(surface->width(), surface->height(), nullptr,
-                                             SkPictureRecorder::kPlaybackDrawPicture_RecordFlag);
+            SkCanvas* pictureCanvas = mRecorder->beginRecording(surface->width(), surface->height(), nullptr,
+                                                                SkPictureRecorder::kPlaybackDrawPicture_RecordFlag);
+            mNwayCanvas = std::make_unique<SkNWayCanvas>(surface->width(), surface->height());
+            mNwayCanvas->addCanvas(surface->getCanvas());
+            mNwayCanvas->addCanvas(pictureCanvas);
+            return mNwayCanvas.get();
         }
     }
     return surface->getCanvas();
 }
 
 void SkiaPipeline::endCapture(SkSurface* surface) {
+    mNwayCanvas.reset();
     if (CC_UNLIKELY(mRecorder.get())) {
+        ATRACE_CALL();
         sk_sp<SkPicture> picture = mRecorder->finishRecordingAsPicture();
-        surface->getCanvas()->drawPicture(picture);
         if (picture->approximateOpCount() > 0) {
-            auto data = picture->serialize();
+            if (mCaptureSequence > 0) {
+                ATRACE_BEGIN("picture->serialize");
+                auto data = picture->serialize();
+                ATRACE_END();
 
-            // offload saving to file in a different thread
-            if (!mSavePictureProcessor.get()) {
-                TaskManager* taskManager = getTaskManager();
-                mSavePictureProcessor = new SavePictureProcessor(
-                        taskManager->canRunTasks() ? taskManager : nullptr);
+                // offload saving to file in a different thread
+                if (!mSavePictureProcessor.get()) {
+                    TaskManager* taskManager = getTaskManager();
+                    mSavePictureProcessor = new SavePictureProcessor(
+                            taskManager->canRunTasks() ? taskManager : nullptr);
+                }
+                if (1 == mCaptureSequence) {
+                    mSavePictureProcessor->savePicture(data, mCapturedFile);
+                } else {
+                    mSavePictureProcessor->savePicture(
+                            data,
+                            mCapturedFile + "_" + std::to_string(mCaptureSequence));
+                }
+                mCaptureSequence--;
             }
-            if (1 == mCaptureSequence) {
-                mSavePictureProcessor->savePicture(data, mCapturedFile);
-            } else {
-                mSavePictureProcessor->savePicture(
-                        data, mCapturedFile + "_" + std::to_string(mCaptureSequence));
+            if (mPictureCapturedCallback) {
+                std::invoke(mPictureCapturedCallback, std::move(picture));
             }
-            mCaptureSequence--;
         }
         mRecorder.reset();
     }
@@ -314,6 +323,11 @@
                                const std::vector<sp<RenderNode>>& nodes, bool opaque,
                                const Rect& contentDrawBounds, sk_sp<SkSurface> surface,
                                const SkMatrix& preTransform) {
+    bool previousSkpEnabled = Properties::skpCaptureEnabled;
+    if (mPictureCapturedCallback) {
+        Properties::skpCaptureEnabled = true;
+    }
+
     renderVectorDrawableCache();
 
     // draw all layers up front
@@ -334,6 +348,8 @@
 
     ATRACE_NAME("flush commands");
     surface->getCanvas()->flush();
+
+    Properties::skpCaptureEnabled = previousSkpEnabled;
 }
 
 namespace {
@@ -460,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 cf6f5b2..7381e04 100644
--- a/libs/hwui/pipeline/skia/SkiaPipeline.h
+++ b/libs/hwui/pipeline/skia/SkiaPipeline.h
@@ -105,13 +105,17 @@
         mLightCenter = lightGeometry.center;
     }
 
+    void setPictureCapturedCallback(
+            const std::function<void(sk_sp<SkPicture>&&)>& callback) override {
+        mPictureCapturedCallback = callback;
+    }
+
 protected:
     void dumpResourceCacheUsage() const;
     void setSurfaceColorProperties(renderthread::ColorMode colorMode);
 
     renderthread::RenderThread& mRenderThread;
     SkColorType mSurfaceColorType;
-    SkColorSpace::Gamut mSurfaceColorGamut;
     sk_sp<SkColorSpace> mSurfaceColorSpace;
 
 private:
@@ -163,6 +167,8 @@
      *  parallel tryCapture calls (not really needed).
      */
     std::unique_ptr<SkPictureRecorder> mRecorder;
+    std::unique_ptr<SkNWayCanvas> mNwayCanvas;
+    std::function<void(sk_sp<SkPicture>&&)> mPictureCapturedCallback;
 
     static float mLightRadius;
     static uint8_t mAmbientShadowAlpha;
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/CanvasContext.h b/libs/hwui/renderthread/CanvasContext.h
index 9e7abf4..db97763 100644
--- a/libs/hwui/renderthread/CanvasContext.h
+++ b/libs/hwui/renderthread/CanvasContext.h
@@ -184,6 +184,10 @@
         mFrameCompleteCallbacks.push_back(std::move(func));
     }
 
+    void setPictureCapturedCallback(const std::function<void(sk_sp<SkPicture>&&)>& callback) {
+        mRenderPipeline->setPictureCapturedCallback(callback);
+    }
+
     void setForceDark(bool enable) {
         mUseForceDark = enable;
     }
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/IRenderPipeline.h b/libs/hwui/renderthread/IRenderPipeline.h
index d4dd629..2cfc8df 100644
--- a/libs/hwui/renderthread/IRenderPipeline.h
+++ b/libs/hwui/renderthread/IRenderPipeline.h
@@ -59,15 +59,15 @@
     virtual MakeCurrentResult makeCurrent() = 0;
     virtual Frame getFrame() = 0;
     virtual bool draw(const Frame& frame, const SkRect& screenDirty, const SkRect& dirty,
-                      const LightGeometry& lightGeometry,
-                      LayerUpdateQueue* layerUpdateQueue, const Rect& contentDrawBounds,
-                      bool opaque, const LightInfo& lightInfo,
+                      const LightGeometry& lightGeometry, LayerUpdateQueue* layerUpdateQueue,
+                      const Rect& contentDrawBounds, bool opaque, const LightInfo& lightInfo,
                       const std::vector<sp<RenderNode>>& renderNodes,
                       FrameInfoVisualizer* profiler) = 0;
     virtual bool swapBuffers(const Frame& frame, bool drew, const SkRect& screenDirty,
                              FrameInfo* currentFrameInfo, bool* requireSwap) = 0;
     virtual DeferredLayerUpdater* createTextureLayer() = 0;
-    virtual bool setSurface(ANativeWindow* window, SwapBehavior swapBehavior, ColorMode colorMode) = 0;
+    virtual bool setSurface(ANativeWindow* window, SwapBehavior swapBehavior,
+                            ColorMode colorMode) = 0;
     virtual void onStop() = 0;
     virtual bool isSurfaceReady() = 0;
     virtual bool isContextReady() = 0;
@@ -85,6 +85,8 @@
     virtual SkColorType getSurfaceColorType() const = 0;
     virtual sk_sp<SkColorSpace> getSurfaceColorSpace() = 0;
     virtual GrSurfaceOrigin getSurfaceOrigin() = 0;
+    virtual void setPictureCapturedCallback(
+            const std::function<void(sk_sp<SkPicture>&&)>& callback) = 0;
 
     virtual ~IRenderPipeline() {}
 };
diff --git a/libs/hwui/renderthread/RenderProxy.cpp b/libs/hwui/renderthread/RenderProxy.cpp
index aa6af23..720c603 100644
--- a/libs/hwui/renderthread/RenderProxy.cpp
+++ b/libs/hwui/renderthread/RenderProxy.cpp
@@ -21,6 +21,7 @@
 #include "Properties.h"
 #include "Readback.h"
 #include "Rect.h"
+#include "WebViewFunctorManager.h"
 #include "pipeline/skia/SkiaOpenGLPipeline.h"
 #include "pipeline/skia/VectorDrawableAtlas.h"
 #include "renderstate/RenderState.h"
@@ -30,7 +31,6 @@
 #include "renderthread/RenderThread.h"
 #include "utils/Macros.h"
 #include "utils/TimeUtils.h"
-#include "WebViewFunctorManager.h"
 
 #include <ui/GraphicBuffer.h>
 
@@ -147,9 +147,7 @@
 void RenderProxy::destroyFunctor(int functor) {
     ATRACE_CALL();
     RenderThread& thread = RenderThread::getInstance();
-    thread.queue().post([=]() {
-        WebViewFunctorManager::instance().destroyFunctor(functor);
-    });
+    thread.queue().post([=]() { WebViewFunctorManager::instance().destroyFunctor(functor); });
 }
 
 DeferredLayerUpdater* RenderProxy::createTextureLayer() {
@@ -164,9 +162,9 @@
 
 bool RenderProxy::copyLayerInto(DeferredLayerUpdater* layer, SkBitmap& bitmap) {
     auto& thread = RenderThread::getInstance();
-    return thread.queue().runSync(
-            [&]() -> bool { return thread.readback().copyLayerInto(layer, &bitmap)
-                                   == CopyResult::Success; });
+    return thread.queue().runSync([&]() -> bool {
+        return thread.readback().copyLayerInto(layer, &bitmap) == CopyResult::Success;
+    });
 }
 
 void RenderProxy::pushLayerUpdate(DeferredLayerUpdater* layer) {
@@ -204,9 +202,8 @@
 }
 
 int RenderProxy::maxTextureSize() {
-    static int maxTextureSize = RenderThread::getInstance().queue().runSync([]() {
-        return DeviceInfo::get()->maxTextureSize();
-    });
+    static int maxTextureSize = RenderThread::getInstance().queue().runSync(
+            []() { return DeviceInfo::get()->maxTextureSize(); });
     return maxTextureSize;
 }
 
@@ -244,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) {
@@ -281,6 +280,12 @@
     mDrawFrameTask.setContentDrawBounds(left, top, right, bottom);
 }
 
+void RenderProxy::setPictureCapturedCallback(
+        const std::function<void(sk_sp<SkPicture>&&)>& callback) {
+    mRenderThread.queue().post(
+            [ this, cb = callback ]() { mContext->setPictureCapturedCallback(cb); });
+}
+
 void RenderProxy::setFrameCallback(std::function<void(int64_t)>&& callback) {
     mDrawFrameTask.setFrameCallback(std::move(callback));
 }
@@ -302,9 +307,7 @@
 }
 
 void RenderProxy::setForceDark(bool enable) {
-    mRenderThread.queue().post([this, enable]() {
-        mContext->setForceDark(enable);
-    });
+    mRenderThread.queue().post([this, enable]() { mContext->setForceDark(enable); });
 }
 
 int RenderProxy::copySurfaceInto(sp<Surface>& surface, int left, int top, int right, int bottom,
@@ -348,9 +351,8 @@
         // TODO: fix everything that hits this. We should never be triggering a readback ourselves.
         return (int)thread.readback().copyHWBitmapInto(hwBitmap, bitmap);
     } else {
-        return thread.queue().runSync([&]() -> int {
-            return (int)thread.readback().copyHWBitmapInto(hwBitmap, bitmap);
-        });
+        return thread.queue().runSync(
+                [&]() -> int { return (int)thread.readback().copyHWBitmapInto(hwBitmap, bitmap); });
     }
 }
 
diff --git a/libs/hwui/renderthread/RenderProxy.h b/libs/hwui/renderthread/RenderProxy.h
index 9dc9181..6e1bfd7 100644
--- a/libs/hwui/renderthread/RenderProxy.h
+++ b/libs/hwui/renderthread/RenderProxy.h
@@ -114,6 +114,8 @@
     ANDROID_API void removeRenderNode(RenderNode* node);
     ANDROID_API void drawRenderNode(RenderNode* node);
     ANDROID_API void setContentDrawBounds(int left, int top, int right, int bottom);
+    ANDROID_API void setPictureCapturedCallback(
+            const std::function<void(sk_sp<SkPicture>&&)>& callback);
     ANDROID_API void setFrameCallback(std::function<void(int64_t)>&& callback);
     ANDROID_API void setFrameCompleteCallback(std::function<void(int64_t)>&& callback);
 
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/location/java/android/location/LocationRequest.java b/location/java/android/location/LocationRequest.java
index 154bd56..3d0afb09 100644
--- a/location/java/android/location/LocationRequest.java
+++ b/location/java/android/location/LocationRequest.java
@@ -16,6 +16,8 @@
 
 package android.location;
 
+import android.Manifest;
+import android.annotation.RequiresPermission;
 import android.annotation.SystemApi;
 import android.annotation.UnsupportedAppUsage;
 import android.os.Build;
@@ -161,6 +163,7 @@
     private WorkSource mWorkSource = null;
     @UnsupportedAppUsage
     private boolean mHideFromAppOps = false; // True if this request shouldn't be counted by AppOps
+    private boolean mLocationSettingsIgnored = false;
 
     @UnsupportedAppUsage
     private String mProvider = LocationManager.FUSED_PROVIDER;
@@ -261,6 +264,7 @@
         mWorkSource = src.mWorkSource;
         mHideFromAppOps = src.mHideFromAppOps;
         mLowPowerMode = src.mLowPowerMode;
+        mLocationSettingsIgnored = src.mLocationSettingsIgnored;
     }
 
     /**
@@ -375,6 +379,32 @@
     }
 
     /**
+     * Requests that user location settings be ignored in order to satisfy this request. This API
+     * is only for use in extremely rare scenarios where it is appropriate to ignore user location
+     * settings, such as a user initiated emergency (dialing 911 for instance).
+     *
+     * @param locationSettingsIgnored Whether to ignore location settings
+     * @return the same object, so that setters can be chained
+     * @hide
+     */
+    @RequiresPermission(Manifest.permission.WRITE_SECURE_SETTINGS)
+    @SystemApi
+    public LocationRequest setLocationSettingsIgnored(boolean locationSettingsIgnored) {
+        mLocationSettingsIgnored = locationSettingsIgnored;
+        return this;
+    }
+
+    /**
+     * Returns true if location settings will be ignored in order to satisfy this request.
+     *
+     * @hide
+     */
+    @SystemApi
+    public boolean isLocationSettingsIgnored() {
+        return mLocationSettingsIgnored;
+    }
+
+    /**
      * Explicitly set the fastest interval for location updates, in
      * milliseconds.
      *
diff --git a/location/lib/java/com/android/location/provider/ActivityChangedEvent.java b/location/lib/java/com/android/location/provider/ActivityChangedEvent.java
new file mode 100644
index 0000000..843dd67
--- /dev/null
+++ b/location/lib/java/com/android/location/provider/ActivityChangedEvent.java
@@ -0,0 +1,57 @@
+/*
+ * Copyright (C) 2014 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License
+ */
+
+package com.android.location.provider;
+
+import android.annotation.NonNull;
+
+import java.security.InvalidParameterException;
+import java.util.List;
+
+/**
+ * A class representing an event for Activity changes.
+ * @hide
+ */
+public class ActivityChangedEvent {
+    private final List<ActivityRecognitionEvent> mActivityRecognitionEvents;
+
+    public ActivityChangedEvent(List<ActivityRecognitionEvent> activityRecognitionEvents) {
+        if (activityRecognitionEvents == null) {
+            throw new InvalidParameterException(
+                    "Parameter 'activityRecognitionEvents' must not be null.");
+        }
+
+        mActivityRecognitionEvents = activityRecognitionEvents;
+    }
+
+    @NonNull
+    public Iterable<ActivityRecognitionEvent> getActivityRecognitionEvents() {
+        return mActivityRecognitionEvents;
+    }
+
+    @Override
+    public String toString() {
+        StringBuilder builder = new StringBuilder("[ ActivityChangedEvent:");
+
+        for (ActivityRecognitionEvent event : mActivityRecognitionEvents) {
+            builder.append("\n    ");
+            builder.append(event.toString());
+        }
+        builder.append("\n]");
+
+        return builder.toString();
+    }
+}
diff --git a/location/lib/java/com/android/location/provider/ActivityRecognitionEvent.java b/location/lib/java/com/android/location/provider/ActivityRecognitionEvent.java
new file mode 100644
index 0000000..e54dea4
--- /dev/null
+++ b/location/lib/java/com/android/location/provider/ActivityRecognitionEvent.java
@@ -0,0 +1,71 @@
+/*
+ * Copyright (C) 2014 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.location.provider;
+
+/**
+ * A class that represents an Activity Recognition Event.
+ * @hide
+ */
+public class ActivityRecognitionEvent {
+    private final String mActivity;
+    private final int mEventType;
+    private final long mTimestampNs;
+
+    public ActivityRecognitionEvent(String activity, int eventType, long timestampNs) {
+        mActivity = activity;
+        mEventType = eventType;
+        mTimestampNs = timestampNs;
+    }
+
+    public String getActivity() {
+        return mActivity;
+    }
+
+    public int getEventType() {
+        return mEventType;
+    }
+
+    public long getTimestampNs() {
+        return mTimestampNs;
+    }
+
+    @Override
+    public String toString() {
+        String eventString;
+        switch (mEventType) {
+            case ActivityRecognitionProvider.EVENT_TYPE_ENTER:
+                eventString = "Enter";
+                break;
+            case ActivityRecognitionProvider.EVENT_TYPE_EXIT:
+                eventString = "Exit";
+                break;
+            case ActivityRecognitionProvider.EVENT_TYPE_FLUSH_COMPLETE:
+                eventString = "FlushComplete";
+                break;
+            default:
+                eventString = "<Invalid>";
+                break;
+        }
+
+        return String.format(
+                "Activity='%s', EventType=%s(%s), TimestampNs=%s",
+                mActivity,
+                eventString,
+                mEventType,
+                mTimestampNs);
+    }
+}
diff --git a/location/lib/java/com/android/location/provider/ActivityRecognitionProvider.java b/location/lib/java/com/android/location/provider/ActivityRecognitionProvider.java
new file mode 100644
index 0000000..0eff7d3
--- /dev/null
+++ b/location/lib/java/com/android/location/provider/ActivityRecognitionProvider.java
@@ -0,0 +1,133 @@
+/*
+ * Copyright (C) 2014 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License
+ */
+
+package com.android.location.provider;
+
+import com.android.internal.util.Preconditions;
+
+import android.hardware.location.IActivityRecognitionHardware;
+import android.hardware.location.IActivityRecognitionHardwareSink;
+import android.os.RemoteException;
+
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.HashSet;
+
+/**
+ * A class that exposes {@link IActivityRecognitionHardware} functionality to unbundled services.
+ * @hide
+ */
+public final class ActivityRecognitionProvider {
+    private final IActivityRecognitionHardware mService;
+    private final HashSet<Sink> mSinkSet = new HashSet<>();
+
+    // the following constants must remain in sync with activity_recognition.h
+
+    public static final String ACTIVITY_IN_VEHICLE = "android.activity_recognition.in_vehicle";
+    public static final String ACTIVITY_ON_BICYCLE = "android.activity_recognition.on_bicycle";
+    public static final String ACTIVITY_WALKING = "android.activity_recognition.walking";
+    public static final String ACTIVITY_RUNNING = "android.activity_recognition.running";
+    public static final String ACTIVITY_STILL = "android.activity_recognition.still";
+    public static final String ACTIVITY_TILTING = "android.activity_recognition.tilting";
+
+    // NOTE: when adding an additional EVENT_TYPE_, EVENT_TYPE_COUNT needs to be updated in
+    // android.hardware.location.ActivityRecognitionHardware
+    public static final int EVENT_TYPE_FLUSH_COMPLETE = 0;
+    public static final int EVENT_TYPE_ENTER = 1;
+    public static final int EVENT_TYPE_EXIT = 2;
+
+    // end constants activity_recognition.h
+
+    /**
+     * Used to receive Activity-Recognition events.
+     */
+    public interface Sink {
+        void onActivityChanged(ActivityChangedEvent event);
+    }
+
+    public ActivityRecognitionProvider(IActivityRecognitionHardware service)
+            throws RemoteException {
+        Preconditions.checkNotNull(service);
+        mService = service;
+        mService.registerSink(new SinkTransport());
+    }
+
+    public String[] getSupportedActivities() throws RemoteException {
+        return mService.getSupportedActivities();
+    }
+
+    public boolean isActivitySupported(String activity) throws RemoteException {
+        return mService.isActivitySupported(activity);
+    }
+
+    public void registerSink(Sink sink) {
+        Preconditions.checkNotNull(sink);
+        synchronized (mSinkSet) {
+            mSinkSet.add(sink);
+        }
+    }
+
+    // TODO: if this functionality is exposed to 3rd party developers, handle unregistration (here
+    // and in the service) of all sinks while failing to disable all events
+    public void unregisterSink(Sink sink) {
+        Preconditions.checkNotNull(sink);
+        synchronized (mSinkSet) {
+            mSinkSet.remove(sink);
+        }
+    }
+
+    public boolean enableActivityEvent(String activity, int eventType, long reportLatencyNs)
+            throws RemoteException {
+        return mService.enableActivityEvent(activity, eventType, reportLatencyNs);
+    }
+
+    public boolean disableActivityEvent(String activity, int eventType) throws RemoteException {
+        return mService.disableActivityEvent(activity, eventType);
+    }
+
+    public boolean flush() throws RemoteException {
+        return mService.flush();
+    }
+
+    private final class SinkTransport extends IActivityRecognitionHardwareSink.Stub {
+        @Override
+        public void onActivityChanged(android.hardware.location.ActivityChangedEvent event) {
+            Collection<Sink> sinks;
+            synchronized (mSinkSet) {
+                if (mSinkSet.isEmpty()) {
+                    return;
+                }
+                sinks = new ArrayList<>(mSinkSet);
+            }
+
+            // translate the event from platform internal and GmsCore types
+            ArrayList<ActivityRecognitionEvent> gmsEvents = new ArrayList<>();
+            for (android.hardware.location.ActivityRecognitionEvent reportingEvent
+                    : event.getActivityRecognitionEvents()) {
+                ActivityRecognitionEvent gmsEvent = new ActivityRecognitionEvent(
+                        reportingEvent.getActivity(),
+                        reportingEvent.getEventType(),
+                        reportingEvent.getTimestampNs());
+                gmsEvents.add(gmsEvent);
+            }
+            ActivityChangedEvent gmsEvent = new ActivityChangedEvent(gmsEvents);
+
+            for (Sink sink : sinks) {
+                sink.onActivityChanged(gmsEvent);
+            }
+        }
+    }
+}
diff --git a/location/lib/java/com/android/location/provider/ActivityRecognitionProviderClient.java b/location/lib/java/com/android/location/provider/ActivityRecognitionProviderClient.java
new file mode 100644
index 0000000..326d901
--- /dev/null
+++ b/location/lib/java/com/android/location/provider/ActivityRecognitionProviderClient.java
@@ -0,0 +1,76 @@
+/*
+ * Copyright (C) 2015 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.location.provider;
+
+import android.annotation.NonNull;
+import android.hardware.location.IActivityRecognitionHardware;
+import android.hardware.location.IActivityRecognitionHardwareClient;
+import android.os.Binder;
+import android.os.IBinder;
+import android.os.Process;
+import android.os.RemoteException;
+import android.util.Log;
+
+/**
+ * A client class for interaction with an Activity-Recognition provider.
+ * @hide
+ */
+public abstract class ActivityRecognitionProviderClient {
+    private static final String TAG = "ArProviderClient";
+
+    protected ActivityRecognitionProviderClient() {}
+
+    private IActivityRecognitionHardwareClient.Stub mClient =
+            new IActivityRecognitionHardwareClient.Stub() {
+                @Override
+                public void onAvailabilityChanged(
+                        boolean isSupported,
+                        IActivityRecognitionHardware instance) {
+                    int callingUid = Binder.getCallingUid();
+                    if (callingUid != Process.SYSTEM_UID) {
+                        Log.d(TAG, "Ignoring calls from non-system server. Uid: " + callingUid);
+                        return;
+                    }
+                    ActivityRecognitionProvider provider;
+                    try {
+                        provider = isSupported ? new ActivityRecognitionProvider(instance) : null;
+                    } catch (RemoteException e) {
+                        Log.e(TAG, "Error creating Hardware Activity-Recognition Provider.", e);
+                        return;
+                    }
+                    onProviderChanged(isSupported, provider);
+                }
+            };
+
+    /**
+     * Gets the binder needed to interact with proxy provider in the platform.
+     */
+    @NonNull
+    public IBinder getBinder() {
+        return mClient;
+    }
+
+    /**
+     * Called when a change in the availability of {@link ActivityRecognitionProvider} is detected.
+     *
+     * @param isSupported whether the platform supports the provider natively
+     * @param instance the available provider's instance
+     */
+    public abstract void onProviderChanged(
+            boolean isSupported,
+            ActivityRecognitionProvider instance);
+}
diff --git a/location/lib/java/com/android/location/provider/ActivityRecognitionProviderWatcher.java b/location/lib/java/com/android/location/provider/ActivityRecognitionProviderWatcher.java
new file mode 100644
index 0000000..42f77b4
--- /dev/null
+++ b/location/lib/java/com/android/location/provider/ActivityRecognitionProviderWatcher.java
@@ -0,0 +1,90 @@
+/*
+ * Copyright (C) 2014 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License
+ */
+
+package com.android.location.provider;
+
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.hardware.location.IActivityRecognitionHardware;
+import android.hardware.location.IActivityRecognitionHardwareWatcher;
+import android.os.Binder;
+import android.os.IBinder;
+import android.os.Process;
+import android.os.RemoteException;
+import android.util.Log;
+
+/**
+ * A watcher class for Activity-Recognition instances.
+ *
+ * @deprecated use {@link ActivityRecognitionProviderClient} instead.
+ * @hide
+ */
+@Deprecated
+public class ActivityRecognitionProviderWatcher {
+    private static final String TAG = "ActivityRecognitionProviderWatcher";
+
+    private static ActivityRecognitionProviderWatcher sWatcher;
+    private static final Object sWatcherLock = new Object();
+
+    private ActivityRecognitionProvider mActivityRecognitionProvider;
+
+    private ActivityRecognitionProviderWatcher() {}
+
+    public static ActivityRecognitionProviderWatcher getInstance() {
+        synchronized (sWatcherLock) {
+            if (sWatcher == null) {
+                sWatcher = new ActivityRecognitionProviderWatcher();
+            }
+            return sWatcher;
+        }
+    }
+
+    private IActivityRecognitionHardwareWatcher.Stub mWatcherStub =
+            new IActivityRecognitionHardwareWatcher.Stub() {
+        @Override
+        public void onInstanceChanged(IActivityRecognitionHardware instance) {
+            int callingUid = Binder.getCallingUid();
+            if (callingUid != Process.SYSTEM_UID) {
+                Log.d(TAG, "Ignoring calls from non-system server. Uid: " + callingUid);
+                return;
+            }
+
+            try {
+                mActivityRecognitionProvider = new ActivityRecognitionProvider(instance);
+            } catch (RemoteException e) {
+                Log.e(TAG, "Error creating Hardware Activity-Recognition", e);
+            }
+        }
+    };
+
+    /**
+     * Gets the binder needed to interact with proxy provider in the platform.
+     */
+    @NonNull
+    public IBinder getBinder() {
+        return mWatcherStub;
+    }
+
+    /**
+     * Gets an object that supports the functionality of {@link ActivityRecognitionProvider}.
+     *
+     * @return Non-null value if the functionality is supported by the platform, false otherwise.
+     */
+    @Nullable
+    public ActivityRecognitionProvider getActivityRecognitionProvider() {
+        return mActivityRecognitionProvider;
+    }
+}
diff --git a/lowpan/tests/Android.mk b/lowpan/tests/Android.mk
index 67727a7..832ed2f 100644
--- a/lowpan/tests/Android.mk
+++ b/lowpan/tests/Android.mk
@@ -45,7 +45,7 @@
 LOCAL_JACK_COVERAGE_EXCLUDE_FILTER := $(jacoco_exclude)
 
 LOCAL_STATIC_JAVA_LIBRARIES := \
-	android-support-test \
+	androidx.test.rules \
 	guava \
 	mockito-target-minus-junit4 \
 	frameworks-base-testutils \
diff --git a/lowpan/tests/AndroidManifest.xml b/lowpan/tests/AndroidManifest.xml
index a216214..4225613 100644
--- a/lowpan/tests/AndroidManifest.xml
+++ b/lowpan/tests/AndroidManifest.xml
@@ -30,7 +30,7 @@
         </activity>
     </application>
 
-    <instrumentation android:name="android.support.test.runner.AndroidJUnitRunner"
+    <instrumentation android:name="androidx.test.runner.AndroidJUnitRunner"
         android:targetPackage="android.net.lowpan.test"
         android:label="Frameworks LoWPAN API Tests">
     </instrumentation>
diff --git a/lowpan/tests/AndroidTest.xml b/lowpan/tests/AndroidTest.xml
index 72ad050..978cc02 100644
--- a/lowpan/tests/AndroidTest.xml
+++ b/lowpan/tests/AndroidTest.xml
@@ -22,6 +22,6 @@
     <option name="test-tag" value="FrameworksLowpanApiTests" />
     <test class="com.android.tradefed.testtype.AndroidJUnitTest" >
         <option name="package" value="android.net.lowpan.test" />
-        <option name="runner" value="android.support.test.runner.AndroidJUnitRunner" />
+        <option name="runner" value="androidx.test.runner.AndroidJUnitRunner" />
     </test>
 </configuration>
diff --git a/lowpan/tests/README.md b/lowpan/tests/README.md
index d0eed95..cb5772e 100644
--- a/lowpan/tests/README.md
+++ b/lowpan/tests/README.md
@@ -37,7 +37,7 @@
 If you manually build and push the test APK to the device you can run tests using
 
 ```
-adb shell am instrument -w 'android.net.wifi.test/android.support.test.runner.AndroidJUnitRunner'
+adb shell am instrument -w 'android.net.wifi.test/androidx.test.runner.AndroidJUnitRunner'
 ```
 
 ## Adding Tests
diff --git a/lowpan/tests/runtests.sh b/lowpan/tests/runtests.sh
index 040f4f0..8267a79 100755
--- a/lowpan/tests/runtests.sh
+++ b/lowpan/tests/runtests.sh
@@ -21,4 +21,4 @@
 
 adb install -r -g "$OUT/data/app/FrameworksLowpanApiTests/FrameworksLowpanApiTests.apk"
 
-adb shell am instrument -w "$@" 'android.net.lowpan.test/android.support.test.runner.AndroidJUnitRunner'
+adb shell am instrument -w "$@" 'android.net.lowpan.test/androidx.test.runner.AndroidJUnitRunner'
diff --git a/lowpan/tests/src/android/net/lowpan/LowpanInterfaceTest.java b/lowpan/tests/src/android/net/lowpan/LowpanInterfaceTest.java
index a495d3d..86f9d0e 100644
--- a/lowpan/tests/src/android/net/lowpan/LowpanInterfaceTest.java
+++ b/lowpan/tests/src/android/net/lowpan/LowpanInterfaceTest.java
@@ -23,15 +23,18 @@
 import android.os.Handler;
 import android.os.IBinder;
 import android.os.test.TestLooper;
-import android.support.test.runner.AndroidJUnit4;
 import android.test.suitebuilder.annotation.SmallTest;
-import java.util.Map;
+
+import androidx.test.runner.AndroidJUnit4;
+
 import org.junit.Before;
 import org.junit.Test;
 import org.junit.runner.RunWith;
 import org.mockito.Mock;
 import org.mockito.MockitoAnnotations;
 
+import java.util.Map;
+
 /** Unit tests for android.net.lowpan.LowpanInterface. */
 @RunWith(AndroidJUnit4.class)
 @SmallTest
diff --git a/lowpan/tests/src/android/net/lowpan/LowpanManagerTest.java b/lowpan/tests/src/android/net/lowpan/LowpanManagerTest.java
index 3dd7504..998e8a5 100644
--- a/lowpan/tests/src/android/net/lowpan/LowpanManagerTest.java
+++ b/lowpan/tests/src/android/net/lowpan/LowpanManagerTest.java
@@ -26,8 +26,10 @@
 import android.os.Handler;
 import android.os.IBinder;
 import android.os.test.TestLooper;
-import android.support.test.runner.AndroidJUnit4;
 import android.test.suitebuilder.annotation.SmallTest;
+
+import androidx.test.runner.AndroidJUnit4;
+
 import org.junit.Before;
 import org.junit.Test;
 import org.junit.runner.RunWith;
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 93%
rename from media/java/android/media/MediaController2.java
rename to media/apex/java/android/media/MediaController2.java
index dd97195..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;
@@ -45,7 +47,7 @@
 
 /**
  * Allows an app to interact with an active {@link MediaSession2} or a
- * MediaSession2Service which would provide {@link MediaSession2}. Media buttons and other
+ * {@link MediaSession2Service} which would provide {@link MediaSession2}. Media buttons and other
  * commands can be sent to the session.
  * <p>
  * This API is not generally intended for third party application developers.
@@ -53,7 +55,6 @@
  * <a href="{@docRoot}reference/androidx/media2/package-summary.html">Media2 Library</a>
  * for consistent behavior across all devices.
  */
-// TODO: use @link for MediaSession2Service
 public class MediaController2 implements AutoCloseable {
     static final String TAG = "MediaController2";
     static final boolean DEBUG = Log.isLoggable(TAG, Log.DEBUG);
@@ -71,6 +72,8 @@
 
     private final Object mLock = new Object();
     //@GuardedBy("mLock")
+    private boolean mClosed;
+    //@GuardedBy("mLock")
     private int mNextSeqNumber;
     //@GuardedBy("mLock")
     private Session2Link mSessionBinder;
@@ -141,7 +144,14 @@
     @Override
     public void close() {
         synchronized (mLock) {
+            if (mClosed) {
+                // Already closed. Ignore rest of clean up code.
+                // Note: unbindService() throws IllegalArgumentException when it's called twice.
+                return;
+            }
+            mClosed = true;
             if (mServiceConnection != null) {
+                // Note: This should be called even when the bindService() has returned false.
                 mContext.unbindService(mServiceConnection);
             }
             if (mSessionBinder != null) {
@@ -167,7 +177,7 @@
      * If it is not connected yet, it returns {@code null}.
      * <p>
      * This may differ with the {@link Session2Token} from the constructor. For example, if the
-     * controller is created with the token for MediaSession2Service, this would return
+     * controller is created with the token for {@link MediaSession2Service}, this would return
      * token for the {@link MediaSession2} in the service.
      *
      * @return Session2Token of the connected session, or {@code null} if not connected
@@ -252,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);
@@ -273,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);
@@ -316,7 +326,7 @@
                     MediaController2.this, command, args);
             if (resultReceiver != null) {
                 if (result == null) {
-                    throw new RuntimeException("onSessionCommand shouldn't return null");
+                    resultReceiver.send(Session2Command.RESULT_INFO_SKIPPED, null);
                 } else {
                     resultReceiver.send(result.getResultCode(), result.getResultData());
                 }
@@ -345,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);
@@ -358,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
@@ -433,8 +443,8 @@
          * @param controller the controller for this event
          * @param command the session command
          * @param args optional arguments
-         * @return the result for the session command. A runtime exception will be thrown if null
-         *         is returned.
+         * @return the result for the session command. If {@code null}, RESULT_INFO_SKIPPED
+         *         will be sent to the session.
          */
         @Nullable
         public Session2Command.Result onSessionCommand(@NonNull MediaController2 controller,
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 93%
rename from media/java/android/media/MediaSession2.java
rename to media/apex/java/android/media/MediaSession2.java
index 3adac72..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;
@@ -90,6 +89,8 @@
     private boolean mClosed;
     //@GuardedBy("mLock")
     private boolean mPlaybackActive;
+    //@GuardedBy("mLock")
+    private ForegroundServiceEventCallback mForegroundServiceEventCallback;
 
     MediaSession2(@NonNull Context context, @NonNull String id, PendingIntent sessionActivity,
             @NonNull Executor callbackExecutor, @NonNull SessionCallback callback) {
@@ -106,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.
@@ -119,6 +122,7 @@
     public void close() {
         try {
             List<ControllerInfo> controllerInfos;
+            ForegroundServiceEventCallback callback;
             synchronized (mLock) {
                 if (mClosed) {
                     return;
@@ -126,14 +130,20 @@
                 mClosed = true;
                 controllerInfos = getConnectedControllers();
                 mConnectedControllers.clear();
-                mCallback.onSessionClosed(this);
+                callback = mForegroundServiceEventCallback;
+                mForegroundServiceEventCallback = null;
             }
             synchronized (MediaSession2.class) {
                 SESSION_ID_LIST.remove(mSessionId);
             }
+            if (callback != null) {
+                callback.onSessionClosed(this);
+            }
             for (ControllerInfo info : controllerInfos) {
                 info.notifyDisconnected();
             }
+            mSessionToken.destroy();
+            mSessionManager.notifySession2Destroyed(mSessionToken);
         } catch (Exception e) {
             // Should not be here.
         }
@@ -224,11 +234,16 @@
      * @param playbackActive {@code true} if the playback active, {@code false} otherwise.
      **/
     public void setPlaybackActive(boolean playbackActive) {
+        final ForegroundServiceEventCallback serviceCallback;
         synchronized (mLock) {
             if (mPlaybackActive == playbackActive) {
                 return;
             }
             mPlaybackActive = playbackActive;
+            serviceCallback = mForegroundServiceEventCallback;
+        }
+        if (serviceCallback != null) {
+            serviceCallback.onPlaybackActiveChanged(this, playbackActive);
         }
         List<ControllerInfo> controllerInfos = getConnectedControllers();
         for (ControllerInfo controller : controllerInfos) {
@@ -257,6 +272,18 @@
         return mCallback;
     }
 
+    void setForegroundServiceEventCallback(ForegroundServiceEventCallback callback) {
+        synchronized (mLock) {
+            if (mForegroundServiceEventCallback == callback) {
+                return;
+            }
+            if (mForegroundServiceEventCallback != null && callback != null) {
+                throw new IllegalStateException("A session cannot be added to multiple services");
+            }
+            mForegroundServiceEventCallback = callback;
+        }
+    }
+
     // Called by Session2Link.onConnect and MediaSession2Service.MediaSession2ServiceStub.connect
     void onConnect(final Controller2Link controller, int callingPid, int callingUid, int seq,
             Bundle connectionRequest) {
@@ -304,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());
@@ -373,7 +400,7 @@
                     MediaSession2.this, controllerInfo, command, args);
             if (resultReceiver != null) {
                 if (result == null) {
-                    throw new RuntimeException("onSessionCommand shouldn't return null");
+                    resultReceiver.send(Session2Command.RESULT_INFO_SKIPPED, null);
                 } else {
                     resultReceiver.send(result.getResultCode(), result.getResultData());
                 }
@@ -695,8 +722,6 @@
      * This API is not generally intended for third party application developers.
      */
     public abstract static class SessionCallback {
-        ForegroundServiceEventCallback mForegroundServiceEventCallback;
-
         /**
          * Called when a controller is created for this session. Return allowed commands for
          * controller. By default it returns {@code null}.
@@ -731,8 +756,8 @@
          * @param controller controller information
          * @param command the session command
          * @param args optional arguments
-         * @return the result for the session command. A runtime exception will be thrown if null
-         *         is returned.
+         * @return the result for the session command. If {@code null}, RESULT_INFO_SKIPPED
+         *         will be sent to the session.
          */
         @Nullable
         public Session2Command.Result onSessionCommand(@NonNull MediaSession2 session,
@@ -753,19 +778,10 @@
         public void onCommandResult(@NonNull MediaSession2 session,
                 @NonNull ControllerInfo controller, @NonNull Object token,
                 @NonNull Session2Command command, @NonNull Session2Command.Result result) {}
+    }
 
-        final void onSessionClosed(MediaSession2 session) {
-            if (mForegroundServiceEventCallback != null) {
-                mForegroundServiceEventCallback.onSessionClosed(session);
-            }
-        }
-
-        void setForegroundServiceEventCallback(ForegroundServiceEventCallback callback) {
-            mForegroundServiceEventCallback = callback;
-        }
-
-        abstract static class ForegroundServiceEventCallback {
-            public void onSessionClosed(MediaSession2 session) {}
-        }
+    abstract static class ForegroundServiceEventCallback {
+        public void onPlaybackActiveChanged(MediaSession2 session, boolean playbackActive) {}
+        public void onSessionClosed(MediaSession2 session) {}
     }
 }
diff --git a/media/java/android/media/MediaSession2Service.java b/media/apex/java/android/media/MediaSession2Service.java
similarity index 62%
rename from media/java/android/media/MediaSession2Service.java
rename to media/apex/java/android/media/MediaSession2Service.java
index 8fb00fe..f18cd31 100644
--- a/media/java/android/media/MediaSession2Service.java
+++ b/media/apex/java/android/media/MediaSession2Service.java
@@ -16,10 +16,15 @@
 
 package android.media;
 
+import static android.media.Session2Token.SESSION_SERVICE_INTERFACE;
+
 import android.annotation.CallSuper;
 import android.annotation.NonNull;
 import android.annotation.Nullable;
+import android.app.Notification;
+import android.app.NotificationManager;
 import android.app.Service;
+import android.content.Context;
 import android.content.Intent;
 import android.os.Binder;
 import android.os.Bundle;
@@ -28,8 +33,6 @@
 import android.util.ArrayMap;
 import android.util.Log;
 
-import com.android.internal.annotations.GuardedBy;
-
 import java.lang.ref.WeakReference;
 import java.util.ArrayList;
 import java.util.List;
@@ -42,24 +45,35 @@
  * Use the <a href="{@docRoot}jetpack/androidx.html">AndroidX</a>
  * <a href="{@docRoot}reference/androidx/media2/package-summary.html">Media2 Library</a>
  * for consistent behavior across all devices.
- * @hide
  */
-// TODO: Unhide
-// TODO: Add onUpdateNotification(), and calls it to get Notification for startForegroundService()
-//       when a session's player state becomes playing.
 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);
 
-    private final Object mLock = new Object();
-    @GuardedBy("mLock")
-    private Map<String, MediaSession2> mSessions = new ArrayMap<>();
+    private final MediaSession2.ForegroundServiceEventCallback mForegroundServiceEventCallback =
+            new MediaSession2.ForegroundServiceEventCallback() {
+                @Override
+                public void onPlaybackActiveChanged(MediaSession2 session, boolean playbackActive) {
+                    MediaSession2Service.this.onPlaybackActiveChanged(session, playbackActive);
+                }
 
+                @Override
+                public void onSessionClosed(MediaSession2 session) {
+                    removeSession(session);
+                }
+            };
+
+    private final Object mLock = new Object();
+    //@GuardedBy("mLock")
+    private NotificationManager mNotificationManager;
+    //@GuardedBy("mLock")
+    private Intent mStartSelfIntent;
+    //@GuardedBy("mLock")
+    private Map<String, MediaSession2> mSessions = new ArrayMap<>();
+    //@GuardedBy("mLock")
+    private Map<MediaSession2, MediaNotification> mNotifications = new ArrayMap<>();
+    //@GuardedBy("mLock")
     private MediaSession2ServiceStub mStub;
 
     /**
@@ -72,26 +86,26 @@
     @Override
     public void onCreate() {
         super.onCreate();
-        mStub = new MediaSession2ServiceStub(this);
+        synchronized (mLock) {
+            mStub = new MediaSession2ServiceStub(this);
+            mStartSelfIntent = new Intent(this, this.getClass());
+            mNotificationManager =
+                    (NotificationManager) getSystemService(Context.NOTIFICATION_SERVICE);
+        }
     }
 
     @CallSuper
     @Override
     @Nullable
     public IBinder onBind(@NonNull Intent intent) {
-        if (SERVICE_INTERFACE.equals(intent.getAction())) {
-            return mStub;
+        if (SESSION_SERVICE_INTERFACE.equals(intent.getAction())) {
+            synchronized (mLock) {
+                return mStub;
+            }
         }
         return null;
     }
 
-    @CallSuper
-    @Override
-    public int onStartCommand(Intent intent, int flags, int startId) {
-        // TODO: Dispatch media key events to the primary session.
-        return START_STICKY;
-    }
-
     /**
      * Called by the system to notify that it is no longer used and is being removed. Do not call
      * this method directly.
@@ -104,10 +118,12 @@
     public void onDestroy() {
         super.onDestroy();
         synchronized (mLock) {
-            for (MediaSession2 session : mSessions.values()) {
-                session.getCallback().setForegroundServiceEventCallback(null);
+            List<MediaSession2> sessions = getSessions();
+            for (MediaSession2 session : sessions) {
+                removeSession(session);
             }
             mSessions.clear();
+            mNotifications.clear();
         }
         mStub.close();
     }
@@ -144,6 +160,24 @@
     public abstract MediaSession2 onGetPrimarySession();
 
     /**
+     * Called when notification UI needs update. Override this method to show or cancel your own
+     * notification UI.
+     * <p>
+     * This would be called on {@link MediaSession2}'s callback executor when playback state is
+     * changed.
+     * <p>
+     * With the notification returned here, the service becomes foreground service when the playback
+     * is started. Apps must request the permission
+     * {@link android.Manifest.permission#FOREGROUND_SERVICE} in order to use this API. It becomes
+     * background service after the playback is stopped.
+     *
+     * @param session a session that needs notification update.
+     * @return a {@link MediaNotification}. Can be {@code null}.
+     */
+    @Nullable
+    public abstract MediaNotification onUpdateNotification(@NonNull MediaSession2 session);
+
+    /**
      * Adds a session to this service.
      * <p>
      * Added session will be removed automatically when it's closed, or removed when
@@ -161,21 +195,15 @@
         }
         synchronized (mLock) {
             MediaSession2 previousSession = mSessions.get(session.getSessionId());
-            if (previousSession != session) {
-                if (previousSession != null) {
+            if (previousSession != null) {
+                if (previousSession != session) {
                     Log.w(TAG, "Session ID should be unique, ID=" + session.getSessionId()
                             + ", previous=" + previousSession + ", session=" + session);
                 }
                 return;
             }
             mSessions.put(session.getSessionId(), session);
-            session.getCallback().setForegroundServiceEventCallback(
-                    new MediaSession2.SessionCallback.ForegroundServiceEventCallback() {
-                        @Override
-                        public void onSessionClosed(MediaSession2 session) {
-                            removeSession(session);
-                        }
-                    });
+            session.setForegroundServiceEventCallback(mForegroundServiceEventCallback);
         }
     }
 
@@ -189,8 +217,21 @@
         if (session == null) {
             throw new IllegalArgumentException("session shouldn't be null");
         }
+        MediaNotification notification;
         synchronized (mLock) {
+            if (mSessions.get(session.getSessionId()) != session) {
+                // Session isn't added or removed already.
+                return;
+            }
             mSessions.remove(session.getSessionId());
+            notification = mNotifications.remove(session);
+        }
+        session.setForegroundServiceEventCallback(null);
+        if (notification != null) {
+            mNotificationManager.cancel(notification.getNotificationId());
+        }
+        if (getSessions().isEmpty()) {
+            stopForeground(false);
         }
     }
 
@@ -207,6 +248,78 @@
         return list;
     }
 
+    /**
+     * Called by registered {@link MediaSession2.ForegroundServiceEventCallback}
+     *
+     * @param session session with change
+     * @param playbackActive {@code true} if playback is active.
+     */
+    void onPlaybackActiveChanged(MediaSession2 session, boolean playbackActive) {
+        MediaNotification mediaNotification = onUpdateNotification(session);
+        if (mediaNotification == null) {
+            // The service implementation doesn't want to use the automatic start/stopForeground
+            // feature.
+            return;
+        }
+        synchronized (mLock) {
+            mNotifications.put(session, mediaNotification);
+        }
+        int id = mediaNotification.getNotificationId();
+        Notification notification = mediaNotification.getNotification();
+        if (!playbackActive) {
+            mNotificationManager.notify(id, notification);
+            return;
+        }
+        // playbackActive == true
+        startForegroundService(mStartSelfIntent);
+        startForeground(id, notification);
+    }
+
+    /**
+     * Returned by {@link #onUpdateNotification(MediaSession2)} for making session service
+     * foreground service to keep playback running in the background. It's highly recommended to
+     * show media style notification here.
+     */
+    public static class MediaNotification {
+        private final int mNotificationId;
+        private final Notification mNotification;
+
+        /**
+         * Default constructor
+         *
+         * @param notificationId notification id to be used for
+         *        {@link NotificationManager#notify(int, Notification)}.
+         * @param notification a notification to make session service run in the foreground. Media
+         *        style notification is recommended here.
+         */
+        public MediaNotification(int notificationId, @NonNull Notification notification) {
+            if (notification == null) {
+                throw new IllegalArgumentException("notification shouldn't be null");
+            }
+            mNotificationId = notificationId;
+            mNotification = notification;
+        }
+
+        /**
+         * Gets the id of the notification.
+         *
+         * @return the notification id
+         */
+        public int getNotificationId() {
+            return mNotificationId;
+        }
+
+        /**
+         * Gets the notification.
+         *
+         * @return the notification
+         */
+        @NonNull
+        public Notification getNotification() {
+            return mNotification;
+        }
+    }
+
     private static final class MediaSession2ServiceStub extends IMediaSession2Service.Stub
             implements AutoCloseable {
         final WeakReference<MediaSession2Service> mService;
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 96%
rename from media/java/android/media/VolumeProvider.java
rename to media/apex/java/android/media/VolumeProvider.java
index 1c017c5..49202ee 100644
--- a/media/java/android/media/VolumeProvider.java
+++ b/media/apex/java/android/media/VolumeProvider.java
@@ -16,6 +16,7 @@
 package android.media;
 
 import android.annotation.IntDef;
+import android.annotation.SystemApi;
 import android.media.session.MediaSession;
 
 import java.lang.annotation.Retention;
@@ -147,6 +148,7 @@
      * Sets a callback to receive volume changes.
      * @hide
      */
+    @SystemApi
     public void setCallback(Callback callback) {
         mCallback = callback;
     }
@@ -155,7 +157,11 @@
      * Listens for changes to the volume.
      * @hide
      */
-    public static abstract class Callback {
+    @SystemApi
+    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 93%
rename from media/java/android/media/session/ControllerCallbackLink.java
rename to media/apex/java/android/media/session/ControllerCallbackLink.java
index 95e19d2..adc14a5 100644
--- a/media/java/android/media/session/ControllerCallbackLink.java
+++ b/media/apex/java/android/media/session/ControllerCallbackLink.java
@@ -24,6 +24,7 @@
 import android.content.Context;
 import android.content.pm.PackageManager;
 import android.media.MediaMetadata;
+import android.media.MediaParceledListSlice;
 import android.media.session.MediaController.PlaybackInfo;
 import android.media.session.MediaSession.QueueItem;
 import android.os.Binder;
@@ -127,7 +128,8 @@
     @RequiresPermission(Manifest.permission.MEDIA_CONTENT_CONTROL)
     public void notifyQueueChanged(@Nullable List<QueueItem> queue) {
         try {
-            mIControllerCallback.notifyQueueChanged(queue);
+            mIControllerCallback.notifyQueueChanged(queue == null ? null :
+                    new MediaParceledListSlice(queue));
         } catch (RemoteException e) {
             throw new RuntimeException(e);
         }
@@ -254,7 +256,7 @@
 
         @Override
         public void notifyPlaybackStateChanged(PlaybackState state) {
-            ensureMediasControlPermission();
+            ensureMediaControlPermission();
             final long token = Binder.clearCallingIdentity();
             try {
                 mCallbackStub.onPlaybackStateChanged(state);
@@ -265,7 +267,7 @@
 
         @Override
         public void notifyMetadataChanged(MediaMetadata metadata) {
-            ensureMediasControlPermission();
+            ensureMediaControlPermission();
             final long token = Binder.clearCallingIdentity();
             try {
                 mCallbackStub.onMetadataChanged(metadata);
@@ -275,11 +277,11 @@
         }
 
         @Override
-        public void notifyQueueChanged(List<QueueItem> queue) {
-            ensureMediasControlPermission();
+        public void notifyQueueChanged(MediaParceledListSlice queue) {
+            ensureMediaControlPermission();
             final long token = Binder.clearCallingIdentity();
             try {
-                mCallbackStub.onQueueChanged(queue);
+                mCallbackStub.onQueueChanged(queue == null ? null : queue.getList());
             } finally {
                 Binder.restoreCallingIdentity(token);
             }
@@ -287,7 +289,7 @@
 
         @Override
         public void notifyQueueTitleChanged(CharSequence title) {
-            ensureMediasControlPermission();
+            ensureMediaControlPermission();
             final long token = Binder.clearCallingIdentity();
             try {
                 mCallbackStub.onQueueTitleChanged(title);
@@ -303,7 +305,7 @@
 
         @Override
         public void notifyVolumeInfoChanged(PlaybackInfo info) {
-            ensureMediasControlPermission();
+            ensureMediaControlPermission();
             final long token = Binder.clearCallingIdentity();
             try {
                 mCallbackStub.onVolumeInfoChanged(info);
@@ -312,13 +314,7 @@
             }
         }
 
-        private void ensureMediasControlPermission() {
-            // Allow API calls from the System UI
-            if (mContext.checkCallingPermission(android.Manifest.permission.STATUS_BAR_SERVICE)
-                    == PackageManager.PERMISSION_GRANTED) {
-                return;
-            }
-
+        private void ensureMediaControlPermission() {
             // Check if it's system server or has MEDIA_CONTENT_CONTROL.
             // Note that system server doesn't have MEDIA_CONTENT_CONTROL, so we need extra
             // check here.
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 91%
rename from media/java/android/media/session/ISessionControllerCallback.aidl
rename to media/apex/java/android/media/session/ISessionControllerCallback.aidl
index 5c02e7c..56ae852 100644
--- a/media/java/android/media/session/ISessionControllerCallback.aidl
+++ b/media/apex/java/android/media/session/ISessionControllerCallback.aidl
@@ -16,8 +16,8 @@
 package android.media.session;
 
 import android.media.MediaMetadata;
+import android.media.MediaParceledListSlice;
 import android.media.session.MediaController;
-import android.media.session.MediaSession;
 import android.media.session.PlaybackState;
 import android.os.Bundle;
 
@@ -31,7 +31,7 @@
     // These callbacks are for the TransportController
     void notifyPlaybackStateChanged(in PlaybackState state);
     void notifyMetadataChanged(in MediaMetadata metadata);
-    void notifyQueueChanged(in List<MediaSession.QueueItem> queue);
+    void notifyQueueChanged(in MediaParceledListSlice queue);
     void notifyQueueTitleChanged(CharSequence title);
     void notifyExtrasChanged(in Bundle extras);
     void notifyVolumeInfoChanged(in MediaController.PlaybackInfo info);
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 98%
rename from media/java/android/media/session/MediaController.java
rename to media/apex/java/android/media/session/MediaController.java
index 9d537c8..d43acf4 100644
--- a/media/java/android/media/session/MediaController.java
+++ b/media/apex/java/android/media/session/MediaController.java
@@ -117,7 +117,7 @@
      * @param token The token for the session.
      */
     public MediaController(@NonNull Context context, @NonNull MediaSession.Token token) {
-        this(context, token.getBinder());
+        this(context, token.getControllerLink());
     }
 
     /**
@@ -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/apex/java/android/media/session/MediaSessionEngine.java b/media/apex/java/android/media/session/MediaSessionEngine.java
new file mode 100644
index 0000000..1f5fa5f
--- /dev/null
+++ b/media/apex/java/android/media/session/MediaSessionEngine.java
@@ -0,0 +1,1479 @@
+/*
+ * Copyright 2019 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.media.session;
+
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.annotation.SystemApi;
+import android.annotation.UnsupportedAppUsage;
+import android.app.Activity;
+import android.app.PendingIntent;
+import android.content.Context;
+import android.content.Intent;
+import android.media.AudioAttributes;
+import android.media.MediaDescription;
+import android.media.MediaMetadata;
+import android.media.Rating;
+import android.media.VolumeProvider;
+import android.media.session.MediaSessionManager.RemoteUserInfo;
+import android.net.Uri;
+import android.os.Bundle;
+import android.os.Handler;
+import android.os.Looper;
+import android.os.Message;
+import android.os.Parcel;
+import android.os.ResultReceiver;
+import android.service.media.MediaBrowserService;
+import android.text.TextUtils;
+import android.util.Log;
+import android.util.Pair;
+import android.view.KeyEvent;
+import android.view.ViewConfiguration;
+
+import java.lang.ref.WeakReference;
+import java.util.List;
+import java.util.Objects;
+
+/**
+ * @hide
+ */
+@SystemApi
+public final class MediaSessionEngine implements AutoCloseable {
+    private static final String TAG = "MediaSession";
+
+    private final Object mLock = new Object();
+    private final int mMaxBitmapSize;
+
+    private final MediaSession.Token mSessionToken;
+    private final MediaController mController;
+    private final SessionLink mSessionLink;
+    private final SessionCallbackLink mCbLink;
+
+    // Do not change the name of mCallbackWrapper. Support lib accesses this by using reflection.
+    @UnsupportedAppUsage
+    private CallbackMessageHandler mCallbackHandler;
+    private VolumeProvider mVolumeProvider;
+    private PlaybackState mPlaybackState;
+
+    private boolean mActive = false;
+
+    /**
+     * Creates a new session. The session will automatically be registered with
+     * the system but will not be published until {@link #setActive(boolean)
+     * setActive(true)} is called. You must call {@link #close()} when
+     * finished with the session.
+     *
+     * @param context The context to use to create the session.
+     * @param sessionLink A session link for the binder of MediaSessionRecord
+     * @param cbStub A callback link that handles incoming command to {@link MediaSession.Callback}.
+     */
+    public MediaSessionEngine(@NonNull Context context, @NonNull SessionLink sessionLink,
+            @NonNull SessionCallbackLink cbLink, @NonNull CallbackStub cbStub, int maxBitmapSize) {
+        mSessionLink = sessionLink;
+        mCbLink = cbLink;
+        mMaxBitmapSize = maxBitmapSize;
+
+        cbStub.setSessionImpl(this);
+        mSessionToken = new MediaSession.Token(mSessionLink.getController());
+        mController = new MediaController(context, mSessionToken);
+    }
+
+    /**
+     * Set the callback to receive updates for the MediaSession. This includes
+     * media button events and transport controls. The caller's thread will be
+     * used to post updates.
+     * <p>
+     * Set the callback to null to stop receiving updates.
+     *
+     * @param callback The callback object
+     */
+    public void setCallback(@Nullable MediaSession.Callback callback) {
+        setCallback(callback, new Handler());
+    }
+
+    /**
+     * Set the callback to receive updates for the MediaSession. This includes
+     * media button events and transport controls.
+     * <p>
+     * Set the callback to null to stop receiving updates.
+     *
+     * @param callback The callback to receive updates on.
+     * @param handler The handler that events should be posted on.
+     */
+    public void setCallback(@Nullable MediaSession.Callback callback, @NonNull Handler handler) {
+        setCallbackInternal(callback == null ? null : new CallbackWrapper(callback), handler);
+    }
+
+    private void setCallbackInternal(CallbackWrapper callback, Handler handler) {
+        synchronized (mLock) {
+            if (mCallbackHandler != null) {
+                // We're updating the callback, clear the session from the old one.
+                mCallbackHandler.mCallbackWrapper.mSessionImpl = null;
+                mCallbackHandler.removeCallbacksAndMessages(null);
+            }
+            if (callback == null) {
+                mCallbackHandler = null;
+                return;
+            }
+            callback.mSessionImpl = this;
+            CallbackMessageHandler msgHandler = new CallbackMessageHandler(handler.getLooper(),
+                    callback);
+            mCallbackHandler = msgHandler;
+        }
+    }
+
+    /**
+     * Set an intent for launching UI for this Session. This can be used as a
+     * quick link to an ongoing media screen. The intent should be for an
+     * activity that may be started using {@link Activity#startActivity(Intent)}.
+     *
+     * @param pi The intent to launch to show UI for this Session.
+     */
+    public void setSessionActivity(@Nullable PendingIntent pi) {
+        try {
+            mSessionLink.setLaunchPendingIntent(pi);
+        } catch (RuntimeException e) {
+            Log.wtf(TAG, "Failure in setLaunchPendingIntent.", e);
+        }
+    }
+
+    /**
+     * Set a pending intent for your media button receiver to allow restarting
+     * playback after the session has been stopped. If your app is started in
+     * this way an {@link Intent#ACTION_MEDIA_BUTTON} intent will be sent via
+     * the pending intent.
+     *
+     * @param mbr The {@link PendingIntent} to send the media button event to.
+     */
+    public void setMediaButtonReceiver(@Nullable PendingIntent mbr) {
+        try {
+            mSessionLink.setMediaButtonReceiver(mbr);
+        } catch (RuntimeException e) {
+            Log.wtf(TAG, "Failure in setMediaButtonReceiver.", e);
+        }
+    }
+
+    /**
+     * Set any flags for the session.
+     *
+     * @param flags The flags to set for this session.
+     */
+    public void setFlags(int flags) {
+        try {
+            mSessionLink.setFlags(flags);
+        } catch (RuntimeException e) {
+            Log.wtf(TAG, "Failure in setFlags.", e);
+        }
+    }
+
+    /**
+     * Set the attributes for this session's audio. This will affect the
+     * system's volume handling for this session. If
+     * {@link #setPlaybackToRemote} was previously called it will stop receiving
+     * volume commands and the system will begin sending volume changes to the
+     * appropriate stream.
+     * <p>
+     * By default sessions use attributes for media.
+     *
+     * @param attributes The {@link AudioAttributes} for this session's audio.
+     */
+    public void setPlaybackToLocal(AudioAttributes attributes) {
+        if (attributes == null) {
+            throw new IllegalArgumentException("Attributes cannot be null for local playback.");
+        }
+        try {
+            mSessionLink.setPlaybackToLocal(attributes);
+        } catch (RuntimeException e) {
+            Log.wtf(TAG, "Failure in setPlaybackToLocal.", e);
+        }
+    }
+
+    /**
+     * Configure this session to use remote volume handling. This must be called
+     * to receive volume button events, otherwise the system will adjust the
+     * appropriate stream volume for this session. If
+     * {@link #setPlaybackToLocal} was previously called the system will stop
+     * handling volume changes for this session and pass them to the volume
+     * provider instead.
+     *
+     * @param volumeProvider The provider that will handle volume changes. May
+     *            not be null.
+     */
+    public void setPlaybackToRemote(@NonNull VolumeProvider volumeProvider) {
+        if (volumeProvider == null) {
+            throw new IllegalArgumentException("volumeProvider may not be null!");
+        }
+        synchronized (mLock) {
+            mVolumeProvider = volumeProvider;
+        }
+        volumeProvider.setCallback(new VolumeProvider.Callback() {
+            @Override
+            public void onVolumeChanged(VolumeProvider volumeProvider) {
+                notifyRemoteVolumeChanged(volumeProvider);
+            }
+        });
+
+        try {
+            mSessionLink.setPlaybackToRemote(volumeProvider.getVolumeControl(),
+                    volumeProvider.getMaxVolume());
+            mSessionLink.setCurrentVolume(volumeProvider.getCurrentVolume());
+        } catch (RuntimeException e) {
+            Log.wtf(TAG, "Failure in setPlaybackToRemote.", e);
+        }
+    }
+
+    /**
+     * Set if this session is currently active and ready to receive commands. If
+     * set to false your session's controller may not be discoverable. You must
+     * set the session to active before it can start receiving media button
+     * events or transport commands.
+     *
+     * @param active Whether this session is active or not.
+     */
+    public void setActive(boolean active) {
+        if (mActive == active) {
+            return;
+        }
+        try {
+            mSessionLink.setActive(active);
+            mActive = active;
+        } catch (RuntimeException e) {
+            Log.wtf(TAG, "Failure in setActive.", e);
+        }
+    }
+
+    /**
+     * Get the current active state of this session.
+     *
+     * @return True if the session is active, false otherwise.
+     */
+    public boolean isActive() {
+        return mActive;
+    }
+
+    /**
+     * Send a proprietary event to all MediaControllers listening to this
+     * Session. It's up to the Controller/Session owner to determine the meaning
+     * of any events.
+     *
+     * @param event The name of the event to send
+     * @param extras Any extras included with the event
+     */
+    public void sendSessionEvent(@NonNull String event, @Nullable Bundle extras) {
+        if (TextUtils.isEmpty(event)) {
+            throw new IllegalArgumentException("event cannot be null or empty");
+        }
+        try {
+            mSessionLink.sendEvent(event, extras);
+        } catch (RuntimeException e) {
+            Log.wtf(TAG, "Error sending event", e);
+        }
+    }
+
+    /**
+     * This must be called when an app has finished performing playback. If
+     * playback is expected to start again shortly the session can be left open,
+     * but it must be released if your activity or service is being destroyed.
+     */
+    public void close() {
+        try {
+            mSessionLink.destroySession();
+        } catch (RuntimeException e) {
+            Log.wtf(TAG, "Error releasing session: ", e);
+        }
+    }
+
+    /**
+     * Retrieve a token object that can be used by apps to create a
+     * {@link MediaController} for interacting with this session. The owner of
+     * the session is responsible for deciding how to distribute these tokens.
+     *
+     * @return A token that can be used to create a MediaController for this
+     *         session
+     */
+    public @NonNull MediaSession.Token getSessionToken() {
+        return mSessionToken;
+    }
+
+    /**
+     * Get a controller for this session. This is a convenience method to avoid
+     * having to cache your own controller in process.
+     *
+     * @return A controller for this session.
+     */
+    public @NonNull MediaController getController() {
+        return mController;
+    }
+
+    /**
+     * Update the current playback state.
+     *
+     * @param state The current state of playback
+     */
+    public void setPlaybackState(@Nullable PlaybackState state) {
+        mPlaybackState = state;
+        try {
+            mSessionLink.setPlaybackState(state);
+        } catch (RuntimeException e) {
+            Log.wtf(TAG, "Dead object in setPlaybackState.", e);
+        }
+    }
+
+    /**
+     * Update the current metadata. New metadata can be created using
+     * {@link android.media.MediaMetadata.Builder}. This operation may take time proportional to
+     * the size of the bitmap to replace large bitmaps with a scaled down copy.
+     *
+     * @param metadata The new metadata
+     * @see android.media.MediaMetadata.Builder#putBitmap
+     */
+    public void setMetadata(@Nullable MediaMetadata metadata) {
+        long duration = -1;
+        int fields = 0;
+        MediaDescription description = null;
+        if (metadata != null) {
+            metadata = (new MediaMetadata.Builder(metadata, mMaxBitmapSize)).build();
+            if (metadata.containsKey(MediaMetadata.METADATA_KEY_DURATION)) {
+                duration = metadata.getLong(MediaMetadata.METADATA_KEY_DURATION);
+            }
+            fields = metadata.size();
+            description = metadata.getDescription();
+        }
+        String metadataDescription = "size=" + fields + ", description=" + description;
+
+        try {
+            mSessionLink.setMetadata(metadata, duration, metadataDescription);
+        } catch (RuntimeException e) {
+            Log.wtf(TAG, "Dead object in setPlaybackState.", e);
+        }
+    }
+
+    /**
+     * Update the list of items in the play queue. It is an ordered list and
+     * should contain the current item, and previous or upcoming items if they
+     * exist. Specify null if there is no current play queue.
+     * <p>
+     * The queue should be of reasonable size. If the play queue is unbounded
+     * within your app, it is better to send a reasonable amount in a sliding
+     * window instead.
+     *
+     * @param queue A list of items in the play queue.
+     */
+    public void setQueue(@Nullable List<MediaSession.QueueItem> queue) {
+        try {
+            mSessionLink.setQueue(queue);
+        } catch (RuntimeException e) {
+            Log.wtf("Dead object in setQueue.", e);
+        }
+    }
+
+    /**
+     * Set the title of the play queue. The UI should display this title along
+     * with the play queue itself.
+     * e.g. "Play Queue", "Now Playing", or an album name.
+     *
+     * @param title The title of the play queue.
+     */
+    public void setQueueTitle(@Nullable CharSequence title) {
+        try {
+            mSessionLink.setQueueTitle(title);
+        } catch (RuntimeException e) {
+            Log.wtf("Dead object in setQueueTitle.", e);
+        }
+    }
+
+    /**
+     * Set the style of rating used by this session. Apps trying to set the
+     * rating should use this style. Must be one of the following:
+     * <ul>
+     * <li>{@link Rating#RATING_NONE}</li>
+     * <li>{@link Rating#RATING_3_STARS}</li>
+     * <li>{@link Rating#RATING_4_STARS}</li>
+     * <li>{@link Rating#RATING_5_STARS}</li>
+     * <li>{@link Rating#RATING_HEART}</li>
+     * <li>{@link Rating#RATING_PERCENTAGE}</li>
+     * <li>{@link Rating#RATING_THUMB_UP_DOWN}</li>
+     * </ul>
+     */
+    public void setRatingType(int type) {
+        try {
+            mSessionLink.setRatingType(type);
+        } catch (RuntimeException e) {
+            Log.e(TAG, "Error in setRatingType.", e);
+        }
+    }
+
+    /**
+     * Set some extras that can be associated with the {@link MediaSession}. No assumptions should
+     * be made as to how a {@link MediaController} will handle these extras.
+     * Keys should be fully qualified (e.g. com.example.MY_EXTRA) to avoid conflicts.
+     *
+     * @param extras The extras associated with the {@link MediaSession}.
+     */
+    public void setExtras(@Nullable Bundle extras) {
+        try {
+            mSessionLink.setExtras(extras);
+        } catch (RuntimeException e) {
+            Log.wtf("Dead object in setExtras.", e);
+        }
+    }
+
+    /**
+     * Gets the controller information who sent the current request.
+     * <p>
+     * Note: This is only valid while in a request callback, such as
+     * {@link MediaSession.Callback#onPlay}.
+     *
+     * @throws IllegalStateException If this method is called outside of
+     * {@link MediaSession.Callback} methods.
+     * @see MediaSessionManager#isTrustedForMediaControl(RemoteUserInfo)
+     */
+    public @NonNull RemoteUserInfo getCurrentControllerInfo() {
+        if (mCallbackHandler == null || mCallbackHandler.mCurrentControllerInfo == null) {
+            throw new IllegalStateException(
+                    "This should be called inside of MediaSession.Callback methods");
+        }
+        return mCallbackHandler.mCurrentControllerInfo;
+    }
+
+    /**
+     * Notify the system that the remote volume changed.
+     *
+     * @param provider The provider that is handling volume changes.
+     * @hide
+     */
+    public void notifyRemoteVolumeChanged(VolumeProvider provider) {
+        synchronized (mLock) {
+            if (provider == null || provider != mVolumeProvider) {
+                Log.w(TAG, "Received update from stale volume provider");
+                return;
+            }
+        }
+        try {
+            mSessionLink.setCurrentVolume(provider.getCurrentVolume());
+        } catch (RuntimeException e) {
+            Log.e(TAG, "Error in notifyVolumeChanged", e);
+        }
+    }
+
+    /**
+     * Returns the name of the package that sent the last media button, transport control, or
+     * command from controllers and the system. This is only valid while in a request callback, such
+     * as {@link MediaSession.Callback#onPlay}.
+     */
+    public String getCallingPackage() {
+        if (mCallbackHandler != null && mCallbackHandler.mCurrentControllerInfo != null) {
+            return mCallbackHandler.mCurrentControllerInfo.getPackageName();
+        }
+        return null;
+    }
+
+    private void dispatchPrepare(RemoteUserInfo caller) {
+        postToCallback(caller, CallbackMessageHandler.MSG_PREPARE, null, null);
+    }
+
+    private void dispatchPrepareFromMediaId(RemoteUserInfo caller, String mediaId, Bundle extras) {
+        postToCallback(caller, CallbackMessageHandler.MSG_PREPARE_MEDIA_ID, mediaId, extras);
+    }
+
+    private void dispatchPrepareFromSearch(RemoteUserInfo caller, String query, Bundle extras) {
+        postToCallback(caller, CallbackMessageHandler.MSG_PREPARE_SEARCH, query, extras);
+    }
+
+    private void dispatchPrepareFromUri(RemoteUserInfo caller, Uri uri, Bundle extras) {
+        postToCallback(caller, CallbackMessageHandler.MSG_PREPARE_URI, uri, extras);
+    }
+
+    private void dispatchPlay(RemoteUserInfo caller) {
+        postToCallback(caller, CallbackMessageHandler.MSG_PLAY, null, null);
+    }
+
+    private void dispatchPlayFromMediaId(RemoteUserInfo caller, String mediaId, Bundle extras) {
+        postToCallback(caller, CallbackMessageHandler.MSG_PLAY_MEDIA_ID, mediaId, extras);
+    }
+
+    private void dispatchPlayFromSearch(RemoteUserInfo caller, String query, Bundle extras) {
+        postToCallback(caller, CallbackMessageHandler.MSG_PLAY_SEARCH, query, extras);
+    }
+
+    private void dispatchPlayFromUri(RemoteUserInfo caller, Uri uri, Bundle extras) {
+        postToCallback(caller, CallbackMessageHandler.MSG_PLAY_URI, uri, extras);
+    }
+
+    private void dispatchSkipToItem(RemoteUserInfo caller, long id) {
+        postToCallback(caller, CallbackMessageHandler.MSG_SKIP_TO_ITEM, id, null);
+    }
+
+    private void dispatchPause(RemoteUserInfo caller) {
+        postToCallback(caller, CallbackMessageHandler.MSG_PAUSE, null, null);
+    }
+
+    private void dispatchStop(RemoteUserInfo caller) {
+        postToCallback(caller, CallbackMessageHandler.MSG_STOP, null, null);
+    }
+
+    private void dispatchNext(RemoteUserInfo caller) {
+        postToCallback(caller, CallbackMessageHandler.MSG_NEXT, null, null);
+    }
+
+    private void dispatchPrevious(RemoteUserInfo caller) {
+        postToCallback(caller, CallbackMessageHandler.MSG_PREVIOUS, null, null);
+    }
+
+    private void dispatchFastForward(RemoteUserInfo caller) {
+        postToCallback(caller, CallbackMessageHandler.MSG_FAST_FORWARD, null, null);
+    }
+
+    private void dispatchRewind(RemoteUserInfo caller) {
+        postToCallback(caller, CallbackMessageHandler.MSG_REWIND, null, null);
+    }
+
+    private void dispatchSeekTo(RemoteUserInfo caller, long pos) {
+        postToCallback(caller, CallbackMessageHandler.MSG_SEEK_TO, pos, null);
+    }
+
+    private void dispatchRate(RemoteUserInfo caller, Rating rating) {
+        postToCallback(caller, CallbackMessageHandler.MSG_RATE, rating, null);
+    }
+
+    private void dispatchCustomAction(RemoteUserInfo caller, String action, Bundle args) {
+        postToCallback(caller, CallbackMessageHandler.MSG_CUSTOM_ACTION, action, args);
+    }
+
+    private void dispatchMediaButton(RemoteUserInfo caller, Intent mediaButtonIntent) {
+        postToCallback(caller, CallbackMessageHandler.MSG_MEDIA_BUTTON, mediaButtonIntent, null);
+    }
+
+    private void dispatchMediaButtonDelayed(RemoteUserInfo info, Intent mediaButtonIntent,
+            long delay) {
+        postToCallbackDelayed(info, CallbackMessageHandler.MSG_PLAY_PAUSE_KEY_DOUBLE_TAP_TIMEOUT,
+                mediaButtonIntent, null, delay);
+    }
+
+    private void dispatchAdjustVolume(RemoteUserInfo caller, int direction) {
+        postToCallback(caller, CallbackMessageHandler.MSG_ADJUST_VOLUME, direction, null);
+    }
+
+    private void dispatchSetVolumeTo(RemoteUserInfo caller, int volume) {
+        postToCallback(caller, CallbackMessageHandler.MSG_SET_VOLUME, volume, null);
+    }
+
+    private void dispatchCommand(RemoteUserInfo caller, String command, Bundle args,
+            ResultReceiver resultCb) {
+        Command cmd = new Command(command, args, resultCb);
+        postToCallback(caller, CallbackMessageHandler.MSG_COMMAND, cmd, null);
+    }
+
+    private void postToCallback(RemoteUserInfo caller, int what, Object obj, Bundle data) {
+        postToCallbackDelayed(caller, what, obj, data, 0);
+    }
+
+    private void postToCallbackDelayed(RemoteUserInfo caller, int what, Object obj, Bundle data,
+            long delay) {
+        synchronized (mLock) {
+            if (mCallbackHandler != null) {
+                mCallbackHandler.post(caller, what, obj, data, delay);
+            }
+        }
+    }
+
+    /**
+     * Return true if this is considered an active playback state.
+     */
+    public static boolean isActiveState(int state) {
+        switch (state) {
+            case PlaybackState.STATE_FAST_FORWARDING:
+            case PlaybackState.STATE_REWINDING:
+            case PlaybackState.STATE_SKIPPING_TO_PREVIOUS:
+            case PlaybackState.STATE_SKIPPING_TO_NEXT:
+            case PlaybackState.STATE_BUFFERING:
+            case PlaybackState.STATE_CONNECTING:
+            case PlaybackState.STATE_PLAYING:
+                return true;
+        }
+        return false;
+    }
+
+    /**
+     * Interface for handling MediaButtoneEvent
+     */
+    public interface MediaButtonEventDelegate {
+        /**
+         * Called when a media button is pressed and this session has the
+         * highest priority or a controller sends a media button event to the
+         * session.
+         *
+         * @param mediaButtonIntent an intent containing the KeyEvent as an extra
+         * @return True if the event was handled, false otherwise.
+         */
+        boolean onMediaButtonIntent(Intent mediaButtonIntent);
+    }
+
+    /**
+     * Receives media buttons, transport controls, and commands from controllers
+     * and the system. A callback may be set using {@link #setCallback}.
+     * @hide
+     */
+    public static class CallbackWrapper implements MediaButtonEventDelegate {
+
+        private final MediaSession.Callback mCallback;
+
+        @SuppressWarnings("WeakerAccess") /* synthetic access */
+                MediaSessionEngine mSessionImpl;
+        @SuppressWarnings("WeakerAccess") /* synthetic access */
+        CallbackMessageHandler mHandler;
+        private boolean mMediaPlayPauseKeyPending;
+
+        public CallbackWrapper(MediaSession.Callback callback) {
+            mCallback = callback;
+            if (mCallback != null) {
+                mCallback.onSetMediaButtonEventDelegate(this);
+            }
+        }
+
+        /**
+         * Called when a controller has sent a command to this session.
+         * The owner of the session may handle custom commands but is not
+         * required to.
+         *
+         * @param command The command name.
+         * @param args Optional parameters for the command, may be null.
+         * @param cb A result receiver to which a result may be sent by the command, may be null.
+         */
+        public void onCommand(@NonNull String command, @Nullable Bundle args,
+                @Nullable ResultReceiver cb) {
+            if (mCallback != null) {
+                mCallback.onCommand(command, args, cb);
+            }
+        }
+
+        /**
+         * Called when a media button is pressed and this session has the
+         * highest priority or a controller sends a media button event to the
+         * session. The default behavior will call the relevant method if the
+         * action for it was set.
+         * <p>
+         * The intent will be of type {@link Intent#ACTION_MEDIA_BUTTON} with a
+         * KeyEvent in {@link Intent#EXTRA_KEY_EVENT}
+         *
+         * @param mediaButtonIntent an intent containing the KeyEvent as an
+         *            extra
+         * @return True if the event was handled, false otherwise.
+         */
+        public boolean onMediaButtonEvent(@NonNull Intent mediaButtonIntent) {
+            return mCallback == null ? false : mCallback.onMediaButtonEvent(mediaButtonIntent);
+        }
+
+        private void handleMediaPlayPauseKeySingleTapIfPending() {
+            if (!mMediaPlayPauseKeyPending) {
+                return;
+            }
+            mMediaPlayPauseKeyPending = false;
+            mHandler.removeMessages(CallbackMessageHandler.MSG_PLAY_PAUSE_KEY_DOUBLE_TAP_TIMEOUT);
+            PlaybackState state = mSessionImpl.mPlaybackState;
+            long validActions = state == null ? 0 : state.getActions();
+            boolean isPlaying = state != null
+                    && state.getState() == PlaybackState.STATE_PLAYING;
+            boolean canPlay = (validActions & (PlaybackState.ACTION_PLAY_PAUSE
+                    | PlaybackState.ACTION_PLAY)) != 0;
+            boolean canPause = (validActions & (PlaybackState.ACTION_PLAY_PAUSE
+                    | PlaybackState.ACTION_PAUSE)) != 0;
+            if (isPlaying && canPause) {
+                onPause();
+            } else if (!isPlaying && canPlay) {
+                onPlay();
+            }
+        }
+
+        /**
+         * Override to handle requests to prepare playback. During the preparation, a session should
+         * not hold audio focus in order to allow other sessions play seamlessly. The state of
+         * playback should be updated to {@link PlaybackState#STATE_PAUSED} after the preparation is
+         * done.
+         */
+        public void onPrepare() {
+            if (mCallback != null) {
+                mCallback.onPrepare();
+            }
+        }
+
+        /**
+         * Override to handle requests to prepare for playing a specific mediaId that was provided
+         * by your app's {@link MediaBrowserService}. During the preparation, a session should not
+         * hold audio focus in order to allow other sessions play seamlessly. The state of playback
+         * should be updated to {@link PlaybackState#STATE_PAUSED} after the preparation is done.
+         * The playback of the prepared content should start in the implementation of
+         * {@link #onPlay}. Override {@link #onPlayFromMediaId} to handle requests for starting
+         * playback without preparation.
+         */
+        public void onPrepareFromMediaId(String mediaId, Bundle extras) {
+            if (mCallback != null) {
+                mCallback.onPrepareFromMediaId(mediaId, extras);
+            }
+        }
+
+        /**
+         * Override to handle requests to prepare playback from a search query. An empty query
+         * indicates that the app may prepare any music. The implementation should attempt to make a
+         * smart choice about what to play. During the preparation, a session should not hold audio
+         * focus in order to allow other sessions play seamlessly. The state of playback should be
+         * updated to {@link PlaybackState#STATE_PAUSED} after the preparation is done. The playback
+         * of the prepared content should start in the implementation of {@link #onPlay}. Override
+         * {@link #onPlayFromSearch} to handle requests for starting playback without preparation.
+         */
+        public void onPrepareFromSearch(String query, Bundle extras) {
+            if (mCallback != null) {
+                mCallback.onPrepareFromSearch(query, extras);
+            }
+        }
+
+        /**
+         * Override to handle requests to prepare a specific media item represented by a URI.
+         * During the preparation, a session should not hold audio focus in order to allow
+         * other sessions play seamlessly. The state of playback should be updated to
+         * {@link PlaybackState#STATE_PAUSED} after the preparation is done.
+         * The playback of the prepared content should start in the implementation of
+         * {@link #onPlay}. Override {@link #onPlayFromUri} to handle requests
+         * for starting playback without preparation.
+         */
+        public void onPrepareFromUri(Uri uri, Bundle extras) {
+            if (mCallback != null) {
+                mCallback.onPrepareFromUri(uri, extras);
+            }
+        }
+
+        /**
+         * Override to handle requests to begin playback.
+         */
+        public void onPlay() {
+            if (mCallback != null) {
+                mCallback.onPlay();
+            }
+        }
+
+        /**
+         * Override to handle requests to begin playback from a search query. An
+         * empty query indicates that the app may play any music. The
+         * implementation should attempt to make a smart choice about what to
+         * play.
+         */
+        public void onPlayFromSearch(String query, Bundle extras) {
+            if (mCallback != null) {
+                mCallback.onPlayFromSearch(query, extras);
+            }
+        }
+
+        /**
+         * Override to handle requests to play a specific mediaId that was
+         * provided by your app's {@link MediaBrowserService}.
+         */
+        public void onPlayFromMediaId(String mediaId, Bundle extras) {
+            if (mCallback != null) {
+                mCallback.onPlayFromMediaId(mediaId, extras);
+            }
+        }
+
+        /**
+         * Override to handle requests to play a specific media item represented by a URI.
+         */
+        public void onPlayFromUri(Uri uri, Bundle extras) {
+            if (mCallback != null) {
+                mCallback.onPlayFromUri(uri, extras);
+            }
+        }
+
+        /**
+         * Override to handle requests to play an item with a given id from the
+         * play queue.
+         */
+        public void onSkipToQueueItem(long id) {
+            if (mCallback != null) {
+                mCallback.onSkipToQueueItem(id);
+            }
+        }
+
+        /**
+         * Override to handle requests to pause playback.
+         */
+        public void onPause() {
+            if (mCallback != null) {
+                mCallback.onPause();
+            }
+        }
+
+        /**
+         * Override to handle requests to skip to the next media item.
+         */
+        public void onSkipToNext() {
+            if (mCallback != null) {
+                mCallback.onSkipToNext();
+            }
+        }
+
+        /**
+         * Override to handle requests to skip to the previous media item.
+         */
+        public void onSkipToPrevious() {
+            if (mCallback != null) {
+                mCallback.onSkipToPrevious();
+            }
+        }
+
+        /**
+         * Override to handle requests to fast forward.
+         */
+        public void onFastForward() {
+            if (mCallback != null) {
+                mCallback.onFastForward();
+            }
+        }
+
+        /**
+         * Override to handle requests to rewind.
+         */
+        public void onRewind() {
+            if (mCallback != null) {
+                mCallback.onRewind();
+            }
+        }
+
+        /**
+         * Override to handle requests to stop playback.
+         */
+        public void onStop() {
+            if (mCallback != null) {
+                mCallback.onStop();
+            }
+        }
+
+        /**
+         * Override to handle requests to seek to a specific position in ms.
+         *
+         * @param pos New position to move to, in milliseconds.
+         */
+        public void onSeekTo(long pos) {
+            if (mCallback != null) {
+                mCallback.onSeekTo(pos);
+            }
+        }
+
+        /**
+         * Override to handle the item being rated.
+         *
+         * @param rating
+         */
+        public void onSetRating(@NonNull Rating rating) {
+            if (mCallback != null) {
+                mCallback.onSetRating(rating);
+            }
+        }
+
+        /**
+         * Called when a {@link MediaController} wants a {@link PlaybackState.CustomAction} to be
+         * performed.
+         *
+         * @param action The action that was originally sent in the
+         *               {@link PlaybackState.CustomAction}.
+         * @param extras Optional extras specified by the {@link MediaController}.
+         */
+        public void onCustomAction(@NonNull String action, @Nullable Bundle extras) {
+            if (mCallback != null) {
+                mCallback.onCustomAction(action, extras);
+            }
+        }
+
+        @Override
+        public boolean onMediaButtonIntent(Intent mediaButtonIntent) {
+            if (mSessionImpl != null && mHandler != null
+                    && Intent.ACTION_MEDIA_BUTTON.equals(mediaButtonIntent.getAction())) {
+                KeyEvent ke = mediaButtonIntent.getParcelableExtra(Intent.EXTRA_KEY_EVENT);
+                if (ke != null && ke.getAction() == KeyEvent.ACTION_DOWN) {
+                    PlaybackState state = mSessionImpl.mPlaybackState;
+                    long validActions = state == null ? 0 : state.getActions();
+                    switch (ke.getKeyCode()) {
+                        case KeyEvent.KEYCODE_MEDIA_PLAY_PAUSE:
+                        case KeyEvent.KEYCODE_HEADSETHOOK:
+                            if (ke.getRepeatCount() > 0) {
+                                // Consider long-press as a single tap.
+                                handleMediaPlayPauseKeySingleTapIfPending();
+                            } else if (mMediaPlayPauseKeyPending) {
+                                // Consider double tap as the next.
+                                mHandler.removeMessages(CallbackMessageHandler
+                                        .MSG_PLAY_PAUSE_KEY_DOUBLE_TAP_TIMEOUT);
+                                mMediaPlayPauseKeyPending = false;
+                                if ((validActions & PlaybackState.ACTION_SKIP_TO_NEXT) != 0) {
+                                    onSkipToNext();
+                                }
+                            } else {
+                                mMediaPlayPauseKeyPending = true;
+                                mSessionImpl.dispatchMediaButtonDelayed(
+                                        mSessionImpl.getCurrentControllerInfo(),
+                                        mediaButtonIntent, ViewConfiguration.getDoubleTapTimeout());
+                            }
+                            return true;
+                        default:
+                            // If another key is pressed within double tap timeout, consider the
+                            // pending play/pause as a single tap to handle media keys in order.
+                            handleMediaPlayPauseKeySingleTapIfPending();
+                            break;
+                    }
+
+                    switch (ke.getKeyCode()) {
+                        case KeyEvent.KEYCODE_MEDIA_PLAY:
+                            if ((validActions & PlaybackState.ACTION_PLAY) != 0) {
+                                onPlay();
+                                return true;
+                            }
+                            break;
+                        case KeyEvent.KEYCODE_MEDIA_PAUSE:
+                            if ((validActions & PlaybackState.ACTION_PAUSE) != 0) {
+                                onPause();
+                                return true;
+                            }
+                            break;
+                        case KeyEvent.KEYCODE_MEDIA_NEXT:
+                            if ((validActions & PlaybackState.ACTION_SKIP_TO_NEXT) != 0) {
+                                onSkipToNext();
+                                return true;
+                            }
+                            break;
+                        case KeyEvent.KEYCODE_MEDIA_PREVIOUS:
+                            if ((validActions & PlaybackState.ACTION_SKIP_TO_PREVIOUS) != 0) {
+                                onSkipToPrevious();
+                                return true;
+                            }
+                            break;
+                        case KeyEvent.KEYCODE_MEDIA_STOP:
+                            if ((validActions & PlaybackState.ACTION_STOP) != 0) {
+                                onStop();
+                                return true;
+                            }
+                            break;
+                        case KeyEvent.KEYCODE_MEDIA_FAST_FORWARD:
+                            if ((validActions & PlaybackState.ACTION_FAST_FORWARD) != 0) {
+                                onFastForward();
+                                return true;
+                            }
+                            break;
+                        case KeyEvent.KEYCODE_MEDIA_REWIND:
+                            if ((validActions & PlaybackState.ACTION_REWIND) != 0) {
+                                onRewind();
+                                return true;
+                            }
+                            break;
+                    }
+                }
+            }
+            return false;
+        }
+    }
+
+    /**
+     * @hide
+     */
+    @SystemApi
+    public static final class CallbackStub extends SessionCallbackLink.CallbackStub {
+        private WeakReference<MediaSessionEngine> mSessionImpl;
+
+        private static RemoteUserInfo createRemoteUserInfo(String packageName, int pid, int uid) {
+            return new RemoteUserInfo(packageName, pid, uid);
+        }
+
+        public CallbackStub() {
+        }
+
+        @Override
+        public void onCommand(String packageName, int pid, int uid,
+                ControllerCallbackLink caller, String command, Bundle args, ResultReceiver cb) {
+            MediaSessionEngine sessionImpl = mSessionImpl.get();
+            if (sessionImpl != null) {
+                sessionImpl.dispatchCommand(createRemoteUserInfo(packageName, pid, uid),
+                        command, args, cb);
+            }
+        }
+
+        @Override
+        public void onMediaButton(String packageName, int pid, int uid, Intent mediaButtonIntent,
+                int sequenceNumber, ResultReceiver cb) {
+            MediaSessionEngine sessionImpl = mSessionImpl.get();
+            try {
+                if (sessionImpl != null) {
+                    sessionImpl.dispatchMediaButton(
+                            createRemoteUserInfo(packageName, pid, uid), mediaButtonIntent);
+                }
+            } finally {
+                if (cb != null) {
+                    cb.send(sequenceNumber, null);
+                }
+            }
+        }
+
+        @Override
+        public void onMediaButtonFromController(String packageName, int pid, int uid,
+                ControllerCallbackLink caller, Intent mediaButtonIntent) {
+            MediaSessionEngine sessionImpl = mSessionImpl.get();
+            if (sessionImpl != null) {
+                sessionImpl.dispatchMediaButton(createRemoteUserInfo(packageName, pid, uid),
+                        mediaButtonIntent);
+            }
+        }
+
+        @Override
+        public void onPrepare(String packageName, int pid, int uid,
+                ControllerCallbackLink caller) {
+            MediaSessionEngine sessionImpl = mSessionImpl.get();
+            if (sessionImpl != null) {
+                sessionImpl.dispatchPrepare(createRemoteUserInfo(packageName, pid, uid));
+            }
+        }
+
+        @Override
+        public void onPrepareFromMediaId(String packageName, int pid, int uid,
+                ControllerCallbackLink caller, String mediaId,
+                Bundle extras) {
+            MediaSessionEngine sessionImpl = mSessionImpl.get();
+            if (sessionImpl != null) {
+                sessionImpl.dispatchPrepareFromMediaId(
+                        createRemoteUserInfo(packageName, pid, uid), mediaId, extras);
+            }
+        }
+
+        @Override
+        public void onPrepareFromSearch(String packageName, int pid, int uid,
+                ControllerCallbackLink caller, String query,
+                Bundle extras) {
+            MediaSessionEngine sessionImpl = mSessionImpl.get();
+            if (sessionImpl != null) {
+                sessionImpl.dispatchPrepareFromSearch(
+                        createRemoteUserInfo(packageName, pid, uid), query, extras);
+            }
+        }
+
+        @Override
+        public void onPrepareFromUri(String packageName, int pid, int uid,
+                ControllerCallbackLink caller, Uri uri, Bundle extras) {
+            MediaSessionEngine sessionImpl = mSessionImpl.get();
+            if (sessionImpl != null) {
+                sessionImpl.dispatchPrepareFromUri(
+                        createRemoteUserInfo(packageName, pid, uid), uri, extras);
+            }
+        }
+
+        @Override
+        public void onPlay(String packageName, int pid, int uid,
+                ControllerCallbackLink caller) {
+            MediaSessionEngine sessionImpl = mSessionImpl.get();
+            if (sessionImpl != null) {
+                sessionImpl.dispatchPlay(createRemoteUserInfo(packageName, pid, uid));
+            }
+        }
+
+        @Override
+        public void onPlayFromMediaId(String packageName, int pid, int uid,
+                ControllerCallbackLink caller, String mediaId,
+                Bundle extras) {
+            MediaSessionEngine sessionImpl = mSessionImpl.get();
+            if (sessionImpl != null) {
+                sessionImpl.dispatchPlayFromMediaId(
+                        createRemoteUserInfo(packageName, pid, uid), mediaId, extras);
+            }
+        }
+
+        @Override
+        public void onPlayFromSearch(String packageName, int pid, int uid,
+                ControllerCallbackLink caller, String query,
+                Bundle extras) {
+            MediaSessionEngine sessionImpl = mSessionImpl.get();
+            if (sessionImpl != null) {
+                sessionImpl.dispatchPlayFromSearch(
+                        createRemoteUserInfo(packageName, pid, uid), query, extras);
+            }
+        }
+
+        @Override
+        public void onPlayFromUri(String packageName, int pid, int uid,
+                ControllerCallbackLink caller, Uri uri, Bundle extras) {
+            MediaSessionEngine sessionImpl = mSessionImpl.get();
+            if (sessionImpl != null) {
+                sessionImpl.dispatchPlayFromUri(
+                        createRemoteUserInfo(packageName, pid, uid), uri, extras);
+            }
+        }
+
+        @Override
+        public void onSkipToTrack(String packageName, int pid, int uid,
+                ControllerCallbackLink caller, long id) {
+            MediaSessionEngine sessionImpl = mSessionImpl.get();
+            if (sessionImpl != null) {
+                sessionImpl.dispatchSkipToItem(
+                        createRemoteUserInfo(packageName, pid, uid), id);
+            }
+        }
+
+        @Override
+        public void onPause(String packageName, int pid, int uid,
+                ControllerCallbackLink caller) {
+            MediaSessionEngine sessionImpl = mSessionImpl.get();
+            if (sessionImpl != null) {
+                sessionImpl.dispatchPause(createRemoteUserInfo(packageName, pid, uid));
+            }
+        }
+
+        @Override
+        public void onStop(String packageName, int pid, int uid,
+                ControllerCallbackLink caller) {
+            MediaSessionEngine sessionImpl = mSessionImpl.get();
+            if (sessionImpl != null) {
+                sessionImpl.dispatchStop(createRemoteUserInfo(packageName, pid, uid));
+            }
+        }
+
+        @Override
+        public void onNext(String packageName, int pid, int uid,
+                ControllerCallbackLink caller) {
+            MediaSessionEngine sessionImpl = mSessionImpl.get();
+            if (sessionImpl != null) {
+                sessionImpl.dispatchNext(createRemoteUserInfo(packageName, pid, uid));
+            }
+        }
+
+        @Override
+        public void onPrevious(String packageName, int pid, int uid,
+                ControllerCallbackLink caller) {
+            MediaSessionEngine sessionImpl = mSessionImpl.get();
+            if (sessionImpl != null) {
+                sessionImpl.dispatchPrevious(createRemoteUserInfo(packageName, pid, uid));
+            }
+        }
+
+        @Override
+        public void onFastForward(String packageName, int pid, int uid,
+                ControllerCallbackLink caller) {
+            MediaSessionEngine sessionImpl = mSessionImpl.get();
+            if (sessionImpl != null) {
+                sessionImpl.dispatchFastForward(
+                        createRemoteUserInfo(packageName, pid, uid));
+            }
+        }
+
+        @Override
+        public void onRewind(String packageName, int pid, int uid,
+                ControllerCallbackLink caller) {
+            MediaSessionEngine sessionImpl = mSessionImpl.get();
+            if (sessionImpl != null) {
+                sessionImpl.dispatchRewind(createRemoteUserInfo(packageName, pid, uid));
+            }
+        }
+
+        @Override
+        public void onSeekTo(String packageName, int pid, int uid,
+                ControllerCallbackLink caller, long pos) {
+            MediaSessionEngine sessionImpl = mSessionImpl.get();
+            if (sessionImpl != null) {
+                sessionImpl.dispatchSeekTo(
+                        createRemoteUserInfo(packageName, pid, uid), pos);
+            }
+        }
+
+        @Override
+        public void onRate(String packageName, int pid, int uid, ControllerCallbackLink caller,
+                Rating rating) {
+            MediaSessionEngine sessionImpl = mSessionImpl.get();
+            if (sessionImpl != null) {
+                sessionImpl.dispatchRate(
+                        createRemoteUserInfo(packageName, pid, uid), rating);
+            }
+        }
+
+        @Override
+        public void onCustomAction(String packageName, int pid, int uid,
+                ControllerCallbackLink caller, String action, Bundle args) {
+            MediaSessionEngine sessionImpl = mSessionImpl.get();
+            if (sessionImpl != null) {
+                sessionImpl.dispatchCustomAction(
+                        createRemoteUserInfo(packageName, pid, uid), action, args);
+            }
+        }
+
+        @Override
+        public void onAdjustVolume(String packageName, int pid, int uid,
+                ControllerCallbackLink caller, int direction) {
+            MediaSessionEngine sessionImpl = mSessionImpl.get();
+            if (sessionImpl != null) {
+                sessionImpl.dispatchAdjustVolume(
+                        createRemoteUserInfo(packageName, pid, uid), direction);
+            }
+        }
+
+        @Override
+        public void onSetVolumeTo(String packageName, int pid, int uid,
+                ControllerCallbackLink caller, int value) {
+            MediaSessionEngine sessionImpl = mSessionImpl.get();
+            if (sessionImpl != null) {
+                sessionImpl.dispatchSetVolumeTo(
+                        createRemoteUserInfo(packageName, pid, uid), value);
+            }
+        }
+
+        void setSessionImpl(MediaSessionEngine sessionImpl) {
+            mSessionImpl = new WeakReference<>(sessionImpl);
+        }
+    }
+
+    /**
+     * A single item that is part of the play queue. It contains a description
+     * of the item and its id in the queue.
+     */
+    public static final class QueueItem {
+        /**
+         * This id is reserved. No items can be explicitly assigned this id.
+         */
+        public static final int UNKNOWN_ID = -1;
+
+        private final MediaDescription mDescription;
+        private final long mId;
+
+        /**
+         * Create a new {@link MediaSession.QueueItem}.
+         *
+         * @param description The {@link MediaDescription} for this item.
+         * @param id An identifier for this item. It must be unique within the
+         *            play queue and cannot be {@link #UNKNOWN_ID}.
+         */
+        public QueueItem(MediaDescription description, long id) {
+            if (description == null) {
+                throw new IllegalArgumentException("Description cannot be null.");
+            }
+            if (id == UNKNOWN_ID) {
+                throw new IllegalArgumentException("Id cannot be QueueItem.UNKNOWN_ID");
+            }
+            mDescription = description;
+            mId = id;
+        }
+
+        public QueueItem(Parcel in) {
+            mDescription = MediaDescription.CREATOR.createFromParcel(in);
+            mId = in.readLong();
+        }
+
+        /**
+         * Get the description for this item.
+         */
+        public MediaDescription getDescription() {
+            return mDescription;
+        }
+
+        /**
+         * Get the queue id for this item.
+         */
+        public long getQueueId() {
+            return mId;
+        }
+
+        /**
+         * Flatten this object in to a Parcel.
+         *
+         * @param dest The Parcel in which the object should be written.
+         * @param flags Additional flags about how the object should be written.
+         */
+        public void writeToParcel(Parcel dest, int flags) {
+            mDescription.writeToParcel(dest, flags);
+            dest.writeLong(mId);
+        }
+
+        @Override
+        public String toString() {
+            return "MediaSession.QueueItem {" + "Description=" + mDescription + ", Id=" + mId
+                    + " }";
+        }
+
+        @Override
+        public boolean equals(Object o) {
+            if (o == null) {
+                return false;
+            }
+
+            if (!(o instanceof QueueItem)) {
+                return false;
+            }
+
+            final QueueItem item = (QueueItem) o;
+            if (mId != item.mId) {
+                return false;
+            }
+
+            if (!Objects.equals(mDescription, item.mDescription)) {
+                return false;
+            }
+
+            return true;
+        }
+    }
+
+    private static final class Command {
+        public final String command;
+        public final Bundle extras;
+        public final ResultReceiver stub;
+
+        Command(String command, Bundle extras, ResultReceiver stub) {
+            this.command = command;
+            this.extras = extras;
+            this.stub = stub;
+        }
+    }
+
+    private class CallbackMessageHandler extends Handler {
+        private static final int MSG_COMMAND = 1;
+        private static final int MSG_MEDIA_BUTTON = 2;
+        private static final int MSG_PREPARE = 3;
+        private static final int MSG_PREPARE_MEDIA_ID = 4;
+        private static final int MSG_PREPARE_SEARCH = 5;
+        private static final int MSG_PREPARE_URI = 6;
+        private static final int MSG_PLAY = 7;
+        private static final int MSG_PLAY_MEDIA_ID = 8;
+        private static final int MSG_PLAY_SEARCH = 9;
+        private static final int MSG_PLAY_URI = 10;
+        private static final int MSG_SKIP_TO_ITEM = 11;
+        private static final int MSG_PAUSE = 12;
+        private static final int MSG_STOP = 13;
+        private static final int MSG_NEXT = 14;
+        private static final int MSG_PREVIOUS = 15;
+        private static final int MSG_FAST_FORWARD = 16;
+        private static final int MSG_REWIND = 17;
+        private static final int MSG_SEEK_TO = 18;
+        private static final int MSG_RATE = 19;
+        private static final int MSG_CUSTOM_ACTION = 20;
+        private static final int MSG_ADJUST_VOLUME = 21;
+        private static final int MSG_SET_VOLUME = 22;
+        private static final int MSG_PLAY_PAUSE_KEY_DOUBLE_TAP_TIMEOUT = 23;
+
+        @SuppressWarnings("WeakerAccess") /* synthetic access */
+        CallbackWrapper mCallbackWrapper;
+        @SuppressWarnings("WeakerAccess") /* synthetic access */
+        RemoteUserInfo mCurrentControllerInfo;
+
+        CallbackMessageHandler(Looper looper, CallbackWrapper callbackWrapper) {
+            super(looper);
+            mCallbackWrapper = callbackWrapper;
+            mCallbackWrapper.mHandler = this;
+        }
+
+        void post(RemoteUserInfo caller, int what, Object obj, Bundle data, long delayMs) {
+            Pair<RemoteUserInfo, Object> objWithCaller = Pair.create(caller, obj);
+            Message msg = obtainMessage(what, objWithCaller);
+            msg.setAsynchronous(true);
+            msg.setData(data);
+            if (delayMs > 0) {
+                sendMessageDelayed(msg, delayMs);
+            } else {
+                sendMessage(msg);
+            }
+        }
+
+        @Override
+        public void handleMessage(Message msg) {
+            mCurrentControllerInfo = ((Pair<RemoteUserInfo, Object>) msg.obj).first;
+
+            VolumeProvider vp;
+            Object obj = ((Pair<RemoteUserInfo, Object>) msg.obj).second;
+
+            switch (msg.what) {
+                case MSG_COMMAND:
+                    Command cmd = (Command) obj;
+                    mCallbackWrapper.onCommand(cmd.command, cmd.extras, cmd.stub);
+                    break;
+                case MSG_MEDIA_BUTTON:
+                    mCallbackWrapper.onMediaButtonEvent((Intent) obj);
+                    break;
+                case MSG_PREPARE:
+                    mCallbackWrapper.onPrepare();
+                    break;
+                case MSG_PREPARE_MEDIA_ID:
+                    mCallbackWrapper.onPrepareFromMediaId((String) obj, msg.getData());
+                    break;
+                case MSG_PREPARE_SEARCH:
+                    mCallbackWrapper.onPrepareFromSearch((String) obj, msg.getData());
+                    break;
+                case MSG_PREPARE_URI:
+                    mCallbackWrapper.onPrepareFromUri((Uri) obj, msg.getData());
+                    break;
+                case MSG_PLAY:
+                    mCallbackWrapper.onPlay();
+                    break;
+                case MSG_PLAY_MEDIA_ID:
+                    mCallbackWrapper.onPlayFromMediaId((String) obj, msg.getData());
+                    break;
+                case MSG_PLAY_SEARCH:
+                    mCallbackWrapper.onPlayFromSearch((String) obj, msg.getData());
+                    break;
+                case MSG_PLAY_URI:
+                    mCallbackWrapper.onPlayFromUri((Uri) obj, msg.getData());
+                    break;
+                case MSG_SKIP_TO_ITEM:
+                    mCallbackWrapper.onSkipToQueueItem((Long) obj);
+                    break;
+                case MSG_PAUSE:
+                    mCallbackWrapper.onPause();
+                    break;
+                case MSG_STOP:
+                    mCallbackWrapper.onStop();
+                    break;
+                case MSG_NEXT:
+                    mCallbackWrapper.onSkipToNext();
+                    break;
+                case MSG_PREVIOUS:
+                    mCallbackWrapper.onSkipToPrevious();
+                    break;
+                case MSG_FAST_FORWARD:
+                    mCallbackWrapper.onFastForward();
+                    break;
+                case MSG_REWIND:
+                    mCallbackWrapper.onRewind();
+                    break;
+                case MSG_SEEK_TO:
+                    mCallbackWrapper.onSeekTo((Long) obj);
+                    break;
+                case MSG_RATE:
+                    mCallbackWrapper.onSetRating((Rating) obj);
+                    break;
+                case MSG_CUSTOM_ACTION:
+                    mCallbackWrapper.onCustomAction((String) obj, msg.getData());
+                    break;
+                case MSG_ADJUST_VOLUME:
+                    synchronized (mLock) {
+                        vp = mVolumeProvider;
+                    }
+                    if (vp != null) {
+                        vp.onAdjustVolume((int) obj);
+                    }
+                    break;
+                case MSG_SET_VOLUME:
+                    synchronized (mLock) {
+                        vp = mVolumeProvider;
+                    }
+                    if (vp != null) {
+                        vp.onSetVolumeTo((int) obj);
+                    }
+                    break;
+                case MSG_PLAY_PAUSE_KEY_DOUBLE_TAP_TIMEOUT:
+                    mCallbackWrapper.handleMediaPlayPauseKeySingleTapIfPending();
+                    break;
+            }
+            mCurrentControllerInfo = null;
+        }
+    }
+}
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 80%
rename from media/java/android/media/session/PlaybackState.java
rename to media/apex/java/android/media/session/PlaybackState.java
index 2c57d1f..6b28c97 100644
--- a/media/java/android/media/session/PlaybackState.java
+++ b/media/apex/java/android/media/session/PlaybackState.java
@@ -19,7 +19,6 @@
 import android.annotation.IntDef;
 import android.annotation.LongDef;
 import android.annotation.Nullable;
-import android.media.RemoteControlClient;
 import android.os.Bundle;
 import android.os.Parcel;
 import android.os.Parcelable;
@@ -42,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,
@@ -192,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
@@ -235,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
@@ -243,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
@@ -253,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
@@ -275,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;
@@ -481,161 +480,6 @@
         return mExtras;
     }
 
-    /**
-     * Get the {@link PlaybackState} state for the given
-     * {@link RemoteControlClient} state.
-     *
-     * @param rccState The state used by {@link RemoteControlClient}.
-     * @return The equivalent state used by {@link PlaybackState}.
-     * @hide
-     */
-    public static int getStateFromRccState(int rccState) {
-        switch (rccState) {
-            case RemoteControlClient.PLAYSTATE_BUFFERING:
-                return STATE_BUFFERING;
-            case RemoteControlClient.PLAYSTATE_ERROR:
-                return STATE_ERROR;
-            case RemoteControlClient.PLAYSTATE_FAST_FORWARDING:
-                return STATE_FAST_FORWARDING;
-            case RemoteControlClient.PLAYSTATE_NONE:
-                return STATE_NONE;
-            case RemoteControlClient.PLAYSTATE_PAUSED:
-                return STATE_PAUSED;
-            case RemoteControlClient.PLAYSTATE_PLAYING:
-                return STATE_PLAYING;
-            case RemoteControlClient.PLAYSTATE_REWINDING:
-                return STATE_REWINDING;
-            case RemoteControlClient.PLAYSTATE_SKIPPING_BACKWARDS:
-                return STATE_SKIPPING_TO_PREVIOUS;
-            case RemoteControlClient.PLAYSTATE_SKIPPING_FORWARDS:
-                return STATE_SKIPPING_TO_NEXT;
-            case RemoteControlClient.PLAYSTATE_STOPPED:
-                return STATE_STOPPED;
-            default:
-                return -1;
-        }
-    }
-
-    /**
-     * Get the {@link RemoteControlClient} state for the given
-     * {@link PlaybackState} state.
-     *
-     * @param state The state used by {@link PlaybackState}.
-     * @return The equivalent state used by {@link RemoteControlClient}.
-     * @hide
-     */
-    public static int getRccStateFromState(int state) {
-        switch (state) {
-            case STATE_BUFFERING:
-                return RemoteControlClient.PLAYSTATE_BUFFERING;
-            case STATE_ERROR:
-                return RemoteControlClient.PLAYSTATE_ERROR;
-            case STATE_FAST_FORWARDING:
-                return RemoteControlClient.PLAYSTATE_FAST_FORWARDING;
-            case STATE_NONE:
-                return RemoteControlClient.PLAYSTATE_NONE;
-            case STATE_PAUSED:
-                return RemoteControlClient.PLAYSTATE_PAUSED;
-            case STATE_PLAYING:
-                return RemoteControlClient.PLAYSTATE_PLAYING;
-            case STATE_REWINDING:
-                return RemoteControlClient.PLAYSTATE_REWINDING;
-            case STATE_SKIPPING_TO_PREVIOUS:
-                return RemoteControlClient.PLAYSTATE_SKIPPING_BACKWARDS;
-            case STATE_SKIPPING_TO_NEXT:
-                return RemoteControlClient.PLAYSTATE_SKIPPING_FORWARDS;
-            case STATE_STOPPED:
-                return RemoteControlClient.PLAYSTATE_STOPPED;
-            default:
-                return -1;
-        }
-    }
-
-    /**
-     * @hide
-     */
-    public static long getActionsFromRccControlFlags(int rccFlags) {
-        long actions = 0;
-        long flag = 1;
-        while (flag <= rccFlags) {
-            if ((flag & rccFlags) != 0) {
-                actions |= getActionForRccFlag((int) flag);
-            }
-            flag = flag << 1;
-        }
-        return actions;
-    }
-
-    /**
-     * @hide
-     */
-    public static int getRccControlFlagsFromActions(long actions) {
-        int rccFlags = 0;
-        long action = 1;
-        while (action <= actions && action < Integer.MAX_VALUE) {
-            if ((action & actions) != 0) {
-                rccFlags |= getRccFlagForAction(action);
-            }
-            action = action << 1;
-        }
-        return rccFlags;
-    }
-
-    private static long getActionForRccFlag(int flag) {
-        switch (flag) {
-            case RemoteControlClient.FLAG_KEY_MEDIA_PREVIOUS:
-                return ACTION_SKIP_TO_PREVIOUS;
-            case RemoteControlClient.FLAG_KEY_MEDIA_REWIND:
-                return ACTION_REWIND;
-            case RemoteControlClient.FLAG_KEY_MEDIA_PLAY:
-                return ACTION_PLAY;
-            case RemoteControlClient.FLAG_KEY_MEDIA_PLAY_PAUSE:
-                return ACTION_PLAY_PAUSE;
-            case RemoteControlClient.FLAG_KEY_MEDIA_PAUSE:
-                return ACTION_PAUSE;
-            case RemoteControlClient.FLAG_KEY_MEDIA_STOP:
-                return ACTION_STOP;
-            case RemoteControlClient.FLAG_KEY_MEDIA_FAST_FORWARD:
-                return ACTION_FAST_FORWARD;
-            case RemoteControlClient.FLAG_KEY_MEDIA_NEXT:
-                return ACTION_SKIP_TO_NEXT;
-            case RemoteControlClient.FLAG_KEY_MEDIA_POSITION_UPDATE:
-                return ACTION_SEEK_TO;
-            case RemoteControlClient.FLAG_KEY_MEDIA_RATING:
-                return ACTION_SET_RATING;
-        }
-        return 0;
-    }
-
-    private static int getRccFlagForAction(long action) {
-        // We only care about the lower set of actions that can map to rcc
-        // flags.
-        int testAction = action < Integer.MAX_VALUE ? (int) action : 0;
-        switch (testAction) {
-            case (int) ACTION_SKIP_TO_PREVIOUS:
-                return RemoteControlClient.FLAG_KEY_MEDIA_PREVIOUS;
-            case (int) ACTION_REWIND:
-                return RemoteControlClient.FLAG_KEY_MEDIA_REWIND;
-            case (int) ACTION_PLAY:
-                return RemoteControlClient.FLAG_KEY_MEDIA_PLAY;
-            case (int) ACTION_PLAY_PAUSE:
-                return RemoteControlClient.FLAG_KEY_MEDIA_PLAY_PAUSE;
-            case (int) ACTION_PAUSE:
-                return RemoteControlClient.FLAG_KEY_MEDIA_PAUSE;
-            case (int) ACTION_STOP:
-                return RemoteControlClient.FLAG_KEY_MEDIA_STOP;
-            case (int) ACTION_FAST_FORWARD:
-                return RemoteControlClient.FLAG_KEY_MEDIA_FAST_FORWARD;
-            case (int) ACTION_SKIP_TO_NEXT:
-                return RemoteControlClient.FLAG_KEY_MEDIA_NEXT;
-            case (int) ACTION_SEEK_TO:
-                return RemoteControlClient.FLAG_KEY_MEDIA_POSITION_UPDATE;
-            case (int) ACTION_SET_RATING:
-                return RemoteControlClient.FLAG_KEY_MEDIA_RATING;
-        }
-        return 0;
-    }
-
     public static final Parcelable.Creator<PlaybackState> CREATOR =
             new Parcelable.Creator<PlaybackState>() {
         @Override
@@ -690,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}.
@@ -744,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 96%
rename from media/java/android/media/session/SessionCallbackLink.java
rename to media/apex/java/android/media/session/SessionCallbackLink.java
index 0265687b..3bcb65c 100644
--- a/media/java/android/media/session/SessionCallbackLink.java
+++ b/media/apex/java/android/media/session/SessionCallbackLink.java
@@ -669,7 +669,7 @@
         @Override
         public void notifyCommand(String packageName, int pid, int uid,
                 ControllerCallbackLink caller, String command, Bundle args, ResultReceiver cb) {
-            ensureMediasControlPermission();
+            ensureMediaControlPermission();
             final long token = Binder.clearCallingIdentity();
             try {
                 mCallbackStub.onCommand(packageName, pid, uid, caller, command, args, cb);
@@ -681,7 +681,7 @@
         @Override
         public void notifyMediaButton(String packageName, int pid, int uid,
                 Intent mediaButtonIntent, int sequenceNumber, ResultReceiver cb) {
-            ensureMediasControlPermission();
+            ensureMediaControlPermission();
             final long token = Binder.clearCallingIdentity();
             try {
                 mCallbackStub.onMediaButton(packageName, pid, uid, mediaButtonIntent,
@@ -694,7 +694,7 @@
         @Override
         public void notifyMediaButtonFromController(String packageName, int pid, int uid,
                 ControllerCallbackLink caller, Intent mediaButtonIntent) {
-            ensureMediasControlPermission();
+            ensureMediaControlPermission();
             final long token = Binder.clearCallingIdentity();
             try {
                 mCallbackStub.onMediaButtonFromController(packageName, pid, uid, caller,
@@ -707,7 +707,7 @@
         @Override
         public void notifyPrepare(String packageName, int pid, int uid,
                 ControllerCallbackLink caller) {
-            ensureMediasControlPermission();
+            ensureMediaControlPermission();
             final long token = Binder.clearCallingIdentity();
             try {
                 mCallbackStub.onPrepare(packageName, pid, uid, caller);
@@ -719,7 +719,7 @@
         @Override
         public void notifyPrepareFromMediaId(String packageName, int pid, int uid,
                 ControllerCallbackLink caller, String mediaId, Bundle extras) {
-            ensureMediasControlPermission();
+            ensureMediaControlPermission();
             final long token = Binder.clearCallingIdentity();
             try {
                 mCallbackStub.onPrepareFromMediaId(packageName, pid, uid, caller, mediaId, extras);
@@ -731,7 +731,7 @@
         @Override
         public void notifyPrepareFromSearch(String packageName, int pid, int uid,
                 ControllerCallbackLink caller, String query, Bundle extras) {
-            ensureMediasControlPermission();
+            ensureMediaControlPermission();
             final long token = Binder.clearCallingIdentity();
             try {
                 mCallbackStub.onPrepareFromSearch(packageName, pid, uid, caller, query, extras);
@@ -743,7 +743,7 @@
         @Override
         public void notifyPrepareFromUri(String packageName, int pid, int uid,
                 ControllerCallbackLink caller, Uri uri, Bundle extras) {
-            ensureMediasControlPermission();
+            ensureMediaControlPermission();
             final long token = Binder.clearCallingIdentity();
             try {
                 mCallbackStub.onPrepareFromUri(packageName, pid, uid, caller, uri, extras);
@@ -755,7 +755,7 @@
         @Override
         public void notifyPlay(String packageName, int pid, int uid,
                 ControllerCallbackLink caller) {
-            ensureMediasControlPermission();
+            ensureMediaControlPermission();
             final long token = Binder.clearCallingIdentity();
             try {
                 mCallbackStub.onPlay(packageName, pid, uid, caller);
@@ -767,7 +767,7 @@
         @Override
         public void notifyPlayFromMediaId(String packageName, int pid, int uid,
                 ControllerCallbackLink caller, String mediaId, Bundle extras) {
-            ensureMediasControlPermission();
+            ensureMediaControlPermission();
             final long token = Binder.clearCallingIdentity();
             try {
                 mCallbackStub.onPlayFromMediaId(packageName, pid, uid, caller, mediaId, extras);
@@ -779,7 +779,7 @@
         @Override
         public void notifyPlayFromSearch(String packageName, int pid, int uid,
                 ControllerCallbackLink caller, String query, Bundle extras) {
-            ensureMediasControlPermission();
+            ensureMediaControlPermission();
             final long token = Binder.clearCallingIdentity();
             try {
                 mCallbackStub.onPlayFromSearch(packageName, pid, uid, caller, query, extras);
@@ -791,7 +791,7 @@
         @Override
         public void notifyPlayFromUri(String packageName, int pid, int uid,
                 ControllerCallbackLink caller, Uri uri, Bundle extras) {
-            ensureMediasControlPermission();
+            ensureMediaControlPermission();
             final long token = Binder.clearCallingIdentity();
             try {
                 mCallbackStub.onPlayFromUri(packageName, pid, uid, caller, uri, extras);
@@ -803,7 +803,7 @@
         @Override
         public void notifySkipToTrack(String packageName, int pid, int uid,
                 ControllerCallbackLink caller, long id) {
-            ensureMediasControlPermission();
+            ensureMediaControlPermission();
             final long token = Binder.clearCallingIdentity();
             try {
                 mCallbackStub.onSkipToTrack(packageName, pid, uid, caller, id);
@@ -815,7 +815,7 @@
         @Override
         public void notifyPause(String packageName, int pid, int uid,
                 ControllerCallbackLink caller) {
-            ensureMediasControlPermission();
+            ensureMediaControlPermission();
             final long token = Binder.clearCallingIdentity();
             try {
                 mCallbackStub.onPause(packageName, pid, uid, caller);
@@ -827,7 +827,7 @@
         @Override
         public void notifyStop(String packageName, int pid, int uid,
                 ControllerCallbackLink caller) {
-            ensureMediasControlPermission();
+            ensureMediaControlPermission();
             final long token = Binder.clearCallingIdentity();
             try {
                 mCallbackStub.onStop(packageName, pid, uid, caller);
@@ -839,7 +839,7 @@
         @Override
         public void notifyNext(String packageName, int pid, int uid,
                 ControllerCallbackLink caller) {
-            ensureMediasControlPermission();
+            ensureMediaControlPermission();
             final long token = Binder.clearCallingIdentity();
             try {
                 mCallbackStub.onNext(packageName, pid, uid, caller);
@@ -851,7 +851,7 @@
         @Override
         public void notifyPrevious(String packageName, int pid, int uid,
                 ControllerCallbackLink caller) {
-            ensureMediasControlPermission();
+            ensureMediaControlPermission();
             final long token = Binder.clearCallingIdentity();
             try {
                 mCallbackStub.onPrevious(packageName, pid, uid, caller);
@@ -863,7 +863,7 @@
         @Override
         public void notifyFastForward(String packageName, int pid, int uid,
                 ControllerCallbackLink caller) {
-            ensureMediasControlPermission();
+            ensureMediaControlPermission();
             final long token = Binder.clearCallingIdentity();
             try {
                 mCallbackStub.onFastForward(packageName, pid, uid, caller);
@@ -875,7 +875,7 @@
         @Override
         public void notifyRewind(String packageName, int pid, int uid,
                 ControllerCallbackLink caller) {
-            ensureMediasControlPermission();
+            ensureMediaControlPermission();
             final long token = Binder.clearCallingIdentity();
             try {
                 mCallbackStub.onRewind(packageName, pid, uid, caller);
@@ -887,7 +887,7 @@
         @Override
         public void notifySeekTo(String packageName, int pid, int uid,
                 ControllerCallbackLink caller, long pos) {
-            ensureMediasControlPermission();
+            ensureMediaControlPermission();
             final long token = Binder.clearCallingIdentity();
             try {
                 mCallbackStub.onSeekTo(packageName, pid, uid, caller, pos);
@@ -899,7 +899,7 @@
         @Override
         public void notifyRate(String packageName, int pid, int uid, ControllerCallbackLink caller,
                 Rating rating) {
-            ensureMediasControlPermission();
+            ensureMediaControlPermission();
             final long token = Binder.clearCallingIdentity();
             try {
                 mCallbackStub.onRate(packageName, pid, uid, caller, rating);
@@ -910,7 +910,7 @@
 
         public void notifyCustomAction(String packageName, int pid, int uid,
                 ControllerCallbackLink caller, String action, Bundle args) {
-            ensureMediasControlPermission();
+            ensureMediaControlPermission();
             final long token = Binder.clearCallingIdentity();
             try {
                 mCallbackStub.onCustomAction(packageName, pid, uid, caller, action, args);
@@ -922,7 +922,7 @@
         @Override
         public void notifyAdjustVolume(String packageName, int pid, int uid,
                 ControllerCallbackLink caller, int direction) {
-            ensureMediasControlPermission();
+            ensureMediaControlPermission();
             final long token = Binder.clearCallingIdentity();
             try {
                 mCallbackStub.onAdjustVolume(packageName, pid, uid, caller, direction);
@@ -934,7 +934,7 @@
         @Override
         public void notifySetVolumeTo(String packageName, int pid, int uid,
                 ControllerCallbackLink caller, int value) {
-            ensureMediasControlPermission();
+            ensureMediaControlPermission();
             final long token = Binder.clearCallingIdentity();
             try {
                 mCallbackStub.onSetVolumeTo(packageName, pid, uid, caller, value);
@@ -943,13 +943,7 @@
             }
         }
 
-        private void ensureMediasControlPermission() {
-            // Allow API calls from the System UI
-            if (mContext.checkCallingPermission(android.Manifest.permission.STATUS_BAR_SERVICE)
-                    == PackageManager.PERMISSION_GRANTED) {
-                return;
-            }
-
+        private void ensureMediaControlPermission() {
             // Check if it's system server or has MEDIA_CONTENT_CONTROL.
             // Note that system server doesn't have MEDIA_CONTENT_CONTROL, so we need extra
             // check here.
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 d19d117..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,
@@ -541,8 +542,7 @@
             throw new IllegalStateException("This should be called inside of onGetRoot or"
                     + " onLoadChildren or onLoadItem methods");
         }
-        return new RemoteUserInfo(mCurConnection.pkg, mCurConnection.pid, mCurConnection.uid,
-                mCurConnection.callbacks.asBinder());
+        return new RemoteUserInfo(mCurConnection.pkg, mCurConnection.pid, mCurConnection.uid);
     }
 
     /**
@@ -608,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;
             }
@@ -649,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();
@@ -820,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.
@@ -830,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/AudioRecordingConfiguration.java b/media/java/android/media/AudioRecordingConfiguration.java
index 52771e4..1d763ce 100644
--- a/media/java/android/media/AudioRecordingConfiguration.java
+++ b/media/java/android/media/AudioRecordingConfiguration.java
@@ -356,11 +356,11 @@
         dest.writeInt(mDeviceSource);
         dest.writeInt(mClientEffects.length);
         for (int i = 0; i < mClientEffects.length; i++) {
-            mClientEffects[i].writeToParcel(dest, 0);
+            mClientEffects[i].writeToParcel(dest);
         }
         dest.writeInt(mDeviceEffects.length);
         for (int i = 0; i < mDeviceEffects.length; i++) {
-            mDeviceEffects[i].writeToParcel(dest, 0);
+            mDeviceEffects[i].writeToParcel(dest);
         }
     }
 
@@ -375,13 +375,13 @@
         mClientPortId = in.readInt();
         mClientSilenced = in.readBoolean();
         mDeviceSource = in.readInt();
-        mClientEffects = AudioEffect.Descriptor.CREATOR.newArray(in.readInt());
+        mClientEffects = new AudioEffect.Descriptor[in.readInt()];
         for (int i = 0; i < mClientEffects.length; i++) {
-            mClientEffects[i] = AudioEffect.Descriptor.CREATOR.createFromParcel(in);
+            mClientEffects[i] = new AudioEffect.Descriptor(in);
         }
-        mDeviceEffects = AudioEffect.Descriptor.CREATOR.newArray(in.readInt());
-        for (int i = 0; i < mClientEffects.length; i++) {
-            mDeviceEffects[i] = AudioEffect.Descriptor.CREATOR.createFromParcel(in);
+        mDeviceEffects = new AudioEffect.Descriptor[in.readInt()];
+        for (int i = 0; i < mDeviceEffects.length; i++) {
+            mDeviceEffects[i] = new AudioEffect.Descriptor(in);
         }
     }
 
diff --git a/media/java/android/media/AudioSystem.java b/media/java/android/media/AudioSystem.java
index 2848b89..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();
@@ -1100,7 +1111,8 @@
             (1 << STREAM_RING) |
             (1 << STREAM_NOTIFICATION) |
             (1 << STREAM_SYSTEM) |
-            (1 << STREAM_VOICE_CALL);
+            (1 << STREAM_VOICE_CALL) |
+            (1 << STREAM_BLUETOOTH_SCO);
 
     /**
      * Event posted by AudioTrack and AudioRecord JNI (JNIDeviceCallback) when routing changes.
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/IRemoteVolumeController.aidl b/media/java/android/media/IRemoteVolumeController.aidl
index a591c11..74c05c4 100644
--- a/media/java/android/media/IRemoteVolumeController.aidl
+++ b/media/java/android/media/IRemoteVolumeController.aidl
@@ -9,6 +9,7 @@
  *
  * 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.
@@ -16,7 +17,7 @@
 
 package android.media;
 
-import android.media.session.ISessionController;
+import android.media.session.MediaSession;
 
 /**
  * AIDL for the MediaSessionService to report interesting events on remote playback
@@ -25,8 +26,8 @@
  * @hide
  */
 oneway interface IRemoteVolumeController {
-    void remoteVolumeChanged(in ISessionController session, int flags);
+    void remoteVolumeChanged(in MediaSession.Token sessionToken, int flags);
     // sets the default session to use with the slider, replaces remoteSliderVisibility
     // on IVolumeController
-    void updateRemoteController(in ISessionController session);
+    void updateRemoteController(in MediaSession.Token sessionToken);
 }
diff --git a/media/java/android/media/MediaDrm.java b/media/java/android/media/MediaDrm.java
index bfc10da5..2d2c4a8 100644
--- a/media/java/android/media/MediaDrm.java
+++ b/media/java/android/media/MediaDrm.java
@@ -176,7 +176,8 @@
      * @param uuid The UUID of the crypto scheme.
      */
     public static final boolean isCryptoSchemeSupported(@NonNull UUID uuid) {
-        return isCryptoSchemeSupportedNative(getByteArrayFromUUID(uuid), null);
+        return isCryptoSchemeSupportedNative(getByteArrayFromUUID(uuid), null,
+                SECURITY_LEVEL_UNKNOWN);
     }
 
     /**
@@ -189,7 +190,25 @@
      */
     public static final boolean isCryptoSchemeSupported(
             @NonNull UUID uuid, @NonNull String mimeType) {
-        return isCryptoSchemeSupportedNative(getByteArrayFromUUID(uuid), mimeType);
+        return isCryptoSchemeSupportedNative(getByteArrayFromUUID(uuid),
+                mimeType, SECURITY_LEVEL_UNKNOWN);
+    }
+
+    /**
+     * Query if the given scheme identified by its UUID is supported on
+     * this device, and whether the DRM plugin is able to handle the
+     * media container format specified by mimeType at the requested
+     * security level.
+     *
+     * @param uuid The UUID of the crypto scheme.
+     * @param mimeType The MIME type of the media container, e.g. "video/mp4"
+     *   or "video/webm"
+     * @param securityLevel the security level requested
+     */
+    public static final boolean isCryptoSchemeSupported(
+            @NonNull UUID uuid, @NonNull String mimeType, @SecurityLevel int securityLevel) {
+        return isCryptoSchemeSupportedNative(getByteArrayFromUUID(uuid), mimeType,
+                securityLevel);
     }
 
     private static final byte[] getByteArrayFromUUID(@NonNull UUID uuid) {
@@ -206,7 +225,7 @@
     }
 
     private static final native boolean isCryptoSchemeSupportedNative(
-            @NonNull byte[] uuid, @Nullable String mimeType);
+            @NonNull byte[] uuid, @Nullable String mimeType, @SecurityLevel int securityLevel);
 
     private EventHandler createHandler() {
         Looper looper;
diff --git a/media/java/android/media/RemoteControlClient.java b/media/java/android/media/RemoteControlClient.java
index 3b51c82..325420b 100644
--- a/media/java/android/media/RemoteControlClient.java
+++ b/media/java/android/media/RemoteControlClient.java
@@ -21,18 +21,14 @@
 import android.content.ComponentName;
 import android.content.Intent;
 import android.graphics.Bitmap;
+import android.media.session.MediaSession;
 import android.media.session.MediaSessionLegacyHelper;
 import android.media.session.PlaybackState;
-import android.media.session.MediaSession;
 import android.os.Bundle;
-import android.os.Handler;
 import android.os.Looper;
-import android.os.Message;
 import android.os.SystemClock;
 import android.util.Log;
 
-import java.lang.IllegalArgumentException;
-
 /**
  * RemoteControlClient enables exposing information meant to be consumed by remote controls
  * capable of displaying metadata, artwork and media transport control buttons.
@@ -682,7 +678,7 @@
 
                 // USE_SESSIONS
                 if (mSession != null) {
-                    int pbState = PlaybackState.getStateFromRccState(state);
+                    int pbState = getStateFromRccState(state);
                     long position = hasPosition ? mPlaybackPositionMs
                             : PlaybackState.PLAYBACK_POSITION_UNKNOWN;
 
@@ -718,8 +714,7 @@
             // USE_SESSIONS
             if (mSession != null) {
                 PlaybackState.Builder bob = new PlaybackState.Builder(mSessionPlaybackState);
-                bob.setActions(
-                        PlaybackState.getActionsFromRccControlFlags(transportControlFlags));
+                bob.setActions(getActionsFromRccControlFlags(transportControlFlags));
                 mSessionPlaybackState = bob.build();
                 mSession.setPlaybackState(mSessionPlaybackState);
             }
@@ -1001,16 +996,19 @@
      * Period for playback position drift checks, 15s when playing at 1x or slower.
      */
     private final static long POSITION_REFRESH_PERIOD_PLAYING_MS = 15000;
+
     /**
      * Minimum period for playback position drift checks, never more often when every 2s, when
      * fast forwarding or rewinding.
      */
     private final static long POSITION_REFRESH_PERIOD_MIN_MS = 2000;
+
     /**
      * The value above which the difference between client-reported playback position and
      * estimated position is considered a drift.
      */
     private final static long POSITION_DRIFT_MAX_MS = 500;
+
     /**
      * Compute the period at which the estimated playback position should be compared against the
      * actual playback position. Is a funciton of playback speed.
@@ -1025,4 +1023,151 @@
                     POSITION_REFRESH_PERIOD_MIN_MS);
         }
     }
+
+    /**
+     * Get the {@link PlaybackState} state for the given
+     * {@link RemoteControlClient} state.
+     *
+     * @param rccState The state used by {@link RemoteControlClient}.
+     * @return The equivalent state used by {@link PlaybackState}.
+     */
+    private static int getStateFromRccState(int rccState) {
+        switch (rccState) {
+            case PLAYSTATE_BUFFERING:
+                return PlaybackState.STATE_BUFFERING;
+            case PLAYSTATE_ERROR:
+                return PlaybackState.STATE_ERROR;
+            case PLAYSTATE_FAST_FORWARDING:
+                return PlaybackState.STATE_FAST_FORWARDING;
+            case PLAYSTATE_NONE:
+                return PlaybackState.STATE_NONE;
+            case PLAYSTATE_PAUSED:
+                return PlaybackState.STATE_PAUSED;
+            case PLAYSTATE_PLAYING:
+                return PlaybackState.STATE_PLAYING;
+            case PLAYSTATE_REWINDING:
+                return PlaybackState.STATE_REWINDING;
+            case PLAYSTATE_SKIPPING_BACKWARDS:
+                return PlaybackState.STATE_SKIPPING_TO_PREVIOUS;
+            case PLAYSTATE_SKIPPING_FORWARDS:
+                return PlaybackState.STATE_SKIPPING_TO_NEXT;
+            case PLAYSTATE_STOPPED:
+                return PlaybackState.STATE_STOPPED;
+            default:
+                return -1;
+        }
+    }
+
+    /**
+     * Get the {@link RemoteControlClient} state for the given
+     * {@link PlaybackState} state.
+     *
+     * @param state The state used by {@link PlaybackState}.
+     * @return The equivalent state used by {@link RemoteControlClient}.
+     */
+    static int getRccStateFromState(int state) {
+        switch (state) {
+            case PlaybackState.STATE_BUFFERING:
+                return PLAYSTATE_BUFFERING;
+            case PlaybackState.STATE_ERROR:
+                return PLAYSTATE_ERROR;
+            case PlaybackState.STATE_FAST_FORWARDING:
+                return PLAYSTATE_FAST_FORWARDING;
+            case PlaybackState.STATE_NONE:
+                return PLAYSTATE_NONE;
+            case PlaybackState.STATE_PAUSED:
+                return PLAYSTATE_PAUSED;
+            case PlaybackState.STATE_PLAYING:
+                return PLAYSTATE_PLAYING;
+            case PlaybackState.STATE_REWINDING:
+                return PLAYSTATE_REWINDING;
+            case PlaybackState.STATE_SKIPPING_TO_PREVIOUS:
+                return PLAYSTATE_SKIPPING_BACKWARDS;
+            case PlaybackState.STATE_SKIPPING_TO_NEXT:
+                return PLAYSTATE_SKIPPING_FORWARDS;
+            case PlaybackState.STATE_STOPPED:
+                return PLAYSTATE_STOPPED;
+            default:
+                return -1;
+        }
+    }
+
+    private static long getActionsFromRccControlFlags(int rccFlags) {
+        long actions = 0;
+        long flag = 1;
+        while (flag <= rccFlags) {
+            if ((flag & rccFlags) != 0) {
+                actions |= getActionForRccFlag((int) flag);
+            }
+            flag = flag << 1;
+        }
+        return actions;
+    }
+
+    static int getRccControlFlagsFromActions(long actions) {
+        int rccFlags = 0;
+        long action = 1;
+        while (action <= actions && action < Integer.MAX_VALUE) {
+            if ((action & actions) != 0) {
+                rccFlags |= getRccFlagForAction(action);
+            }
+            action = action << 1;
+        }
+        return rccFlags;
+    }
+
+    private static long getActionForRccFlag(int flag) {
+        switch (flag) {
+            case FLAG_KEY_MEDIA_PREVIOUS:
+                return PlaybackState.ACTION_SKIP_TO_PREVIOUS;
+            case FLAG_KEY_MEDIA_REWIND:
+                return PlaybackState.ACTION_REWIND;
+            case FLAG_KEY_MEDIA_PLAY:
+                return PlaybackState.ACTION_PLAY;
+            case FLAG_KEY_MEDIA_PLAY_PAUSE:
+                return PlaybackState.ACTION_PLAY_PAUSE;
+            case FLAG_KEY_MEDIA_PAUSE:
+                return PlaybackState.ACTION_PAUSE;
+            case FLAG_KEY_MEDIA_STOP:
+                return PlaybackState.ACTION_STOP;
+            case FLAG_KEY_MEDIA_FAST_FORWARD:
+                return PlaybackState.ACTION_FAST_FORWARD;
+            case FLAG_KEY_MEDIA_NEXT:
+                return PlaybackState.ACTION_SKIP_TO_NEXT;
+            case FLAG_KEY_MEDIA_POSITION_UPDATE:
+                return PlaybackState.ACTION_SEEK_TO;
+            case FLAG_KEY_MEDIA_RATING:
+                return PlaybackState.ACTION_SET_RATING;
+        }
+        return 0;
+    }
+
+    private static int getRccFlagForAction(long action) {
+        // We only care about the lower set of actions that can map to rcc
+        // flags.
+        int testAction = action < Integer.MAX_VALUE ? (int) action : 0;
+        switch (testAction) {
+            case (int) PlaybackState.ACTION_SKIP_TO_PREVIOUS:
+                return FLAG_KEY_MEDIA_PREVIOUS;
+            case (int) PlaybackState.ACTION_REWIND:
+                return FLAG_KEY_MEDIA_REWIND;
+            case (int) PlaybackState.ACTION_PLAY:
+                return FLAG_KEY_MEDIA_PLAY;
+            case (int) PlaybackState.ACTION_PLAY_PAUSE:
+                return FLAG_KEY_MEDIA_PLAY_PAUSE;
+            case (int) PlaybackState.ACTION_PAUSE:
+                return FLAG_KEY_MEDIA_PAUSE;
+            case (int) PlaybackState.ACTION_STOP:
+                return FLAG_KEY_MEDIA_STOP;
+            case (int) PlaybackState.ACTION_FAST_FORWARD:
+                return FLAG_KEY_MEDIA_FAST_FORWARD;
+            case (int) PlaybackState.ACTION_SKIP_TO_NEXT:
+                return FLAG_KEY_MEDIA_NEXT;
+            case (int) PlaybackState.ACTION_SEEK_TO:
+                return FLAG_KEY_MEDIA_POSITION_UPDATE;
+            case (int) PlaybackState.ACTION_SET_RATING:
+                return FLAG_KEY_MEDIA_RATING;
+        }
+        return 0;
+    }
 }
diff --git a/media/java/android/media/RemoteController.java b/media/java/android/media/RemoteController.java
index 5e9eed7..f70963a 100644
--- a/media/java/android/media/RemoteController.java
+++ b/media/java/android/media/RemoteController.java
@@ -632,8 +632,8 @@
             l = this.mOnClientUpdateListener;
         }
         if (l != null) {
-            int playstate = state == null ? RemoteControlClient.PLAYSTATE_NONE : PlaybackState
-                    .getRccStateFromState(state.getState());
+            int playstate = state == null ? RemoteControlClient.PLAYSTATE_NONE
+                    : RemoteControlClient.getRccStateFromState(state.getState());
             if (state == null || state.getPosition() == PlaybackState.PLAYBACK_POSITION_UNKNOWN) {
                 l.onClientPlaybackStateUpdate(playstate);
             } else {
@@ -642,7 +642,7 @@
             }
             if (state != null) {
                 l.onClientTransportControlUpdate(
-                        PlaybackState.getRccControlFlagsFromActions(state.getActions()));
+                        RemoteControlClient.getRccControlFlagsFromActions(state.getActions()));
             }
         }
     }
diff --git a/media/java/android/media/Ringtone.java b/media/java/android/media/Ringtone.java
index 42597aa..73d3d88 100644
--- a/media/java/android/media/Ringtone.java
+++ b/media/java/android/media/Ringtone.java
@@ -138,7 +138,7 @@
         mAudioAttributes = attributes;
         // The audio attributes have to be set before the media player is prepared.
         // Re-initialize it.
-        setUri(mUri);
+        setUri(mUri, mVolumeShaperConfig);
     }
 
     /**
@@ -415,6 +415,7 @@
             mLocalPlayer.reset();
             mLocalPlayer.release();
             mLocalPlayer = null;
+            mVolumeShaper = null;
             synchronized (sActiveRingtones) {
                 sActiveRingtones.remove(this);
             }
diff --git a/media/java/android/media/Session2Token.java b/media/java/android/media/Session2Token.java
index d8f74c5..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 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,18 +48,10 @@
  * 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}.
  */
-// New version of MediaSession2.Token for following reasons
-//   - Stop implementing Parcelable for updatable support
-//   - Represent session and library service (formerly browser service) in one class.
-//     Previously MediaSession2.Token was for session and ComponentName was for service.
-//     This helps controller apps to keep target of dispatching media key events in uniform way.
-//     For details about the reason, see following. (Android O+)
-//         android.media.session.MediaSessionManager.Callback#onAddressedPlayerChanged
-// TODO: use @link for MediaSession2Service
 public final class Session2Token implements Parcelable {
     private static final String TAG = "Session2Token";
 
@@ -72,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)
@@ -80,7 +83,7 @@
     }
 
     /**
-     * Type for {@link MediaSession2}.
+     * Type for MediaSession2.
      */
     public static final int TYPE_SESSION = 0;
 
@@ -89,12 +92,17 @@
      */
     public static final int TYPE_SESSION_SERVICE = 1;
 
+    private final String mSessionId;
+    private final int mPid;
     private final int mUid;
-    private final @TokenType int mType;
+    @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}.
@@ -113,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
@@ -160,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
@@ -169,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;
     }
 
     /**
@@ -206,14 +247,6 @@
     }
 
     /**
-     * @hide
-     * @return component name of the session. Can be {@code null} for {@link #TYPE_SESSION}.
-     */
-    public ComponentName getComponentName() {
-        return mComponentName;
-    }
-
-    /**
      * @return type of the token
      * @see #TYPE_SESSION
      * @see #TYPE_SESSION_SERVICE
@@ -223,10 +256,35 @@
     }
 
     /**
+     * @return extras
      * @hide
      */
-    public Session2Link getSessionLink() {
-        return mSessionLink;
+    @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/audiofx/AudioEffect.java b/media/java/android/media/audiofx/AudioEffect.java
index 52e9ae1..5b4bbce 100644
--- a/media/java/android/media/audiofx/AudioEffect.java
+++ b/media/java/android/media/audiofx/AudioEffect.java
@@ -26,7 +26,6 @@
 import android.os.Looper;
 import android.os.Message;
 import android.os.Parcel;
-import android.os.Parcelable;
 import android.util.Log;
 
 import java.lang.ref.WeakReference;
@@ -229,7 +228,7 @@
      * The method {@link #queryEffects()} returns an array of Descriptors to facilitate effects
      * enumeration.
      */
-    public static final class Descriptor implements Parcelable {
+    public static class Descriptor {
 
         public Descriptor() {
         }
@@ -294,7 +293,9 @@
             this.implementor = implementor;
         }
 
-        private Descriptor(Parcel in) {
+        /** @hide */
+        @TestApi
+        public Descriptor(Parcel in) {
             type = UUID.fromString(in.readString());
             uuid = UUID.fromString(in.readString());
             connectMode = in.readString();
@@ -302,33 +303,14 @@
             implementor = in.readString();
         }
 
-        public static final Parcelable.Creator<Descriptor> CREATOR =
-                new Parcelable.Creator<Descriptor>() {
-                    /**
-                     * Rebuilds a Descriptor previously stored with writeToParcel().
-                     * @param p Parcel object to read the Descriptor from
-                     * @return a new Descriptor created from the data in the parcel
-                     */
-                    public Descriptor createFromParcel(Parcel p) {
-                        return new Descriptor(p);
-                    }
-                    public Descriptor[] newArray(int size) {
-                        return new Descriptor[size];
-                    }
-        };
-
         @Override
         public int hashCode() {
             return Objects.hash(type, uuid, connectMode, name, implementor);
         }
 
-        @Override
-        public int describeContents() {
-            return 0;
-        }
-
-        @Override
-        public void writeToParcel(Parcel dest, int flags) {
+        /** @hide */
+        @TestApi
+        public void writeToParcel(Parcel dest) {
             dest.writeString(type.toString());
             dest.writeString(uuid.toString());
             dest.writeString(connectMode);
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/MediaSession.java b/media/java/android/media/session/MediaSession.java
index f02d9ba..1a185e9 100644
--- a/media/java/android/media/session/MediaSession.java
+++ b/media/java/android/media/session/MediaSession.java
@@ -19,6 +19,7 @@
 import android.annotation.IntDef;
 import android.annotation.NonNull;
 import android.annotation.Nullable;
+import android.annotation.SystemApi;
 import android.annotation.UnsupportedAppUsage;
 import android.app.Activity;
 import android.app.PendingIntent;
@@ -33,23 +34,15 @@
 import android.net.Uri;
 import android.os.Bundle;
 import android.os.Handler;
-import android.os.Looper;
-import android.os.Message;
 import android.os.Parcel;
 import android.os.Parcelable;
 import android.os.ResultReceiver;
 import android.service.media.MediaBrowserService;
 import android.text.TextUtils;
-import android.util.Log;
-import android.util.Pair;
-import android.view.KeyEvent;
-import android.view.ViewConfiguration;
 
 import java.lang.annotation.Retention;
 import java.lang.annotation.RetentionPolicy;
-import java.lang.ref.WeakReference;
 import java.util.List;
-import java.util.Objects;
 
 /**
  * Allows interaction with media controllers, volume keys, media buttons, and
@@ -74,7 +67,7 @@
  * MediaSession objects are thread safe.
  */
 public final class MediaSession {
-    private static final String TAG = "MediaSession";
+    static final String TAG = "MediaSession";
 
     /**
      * Set this flag on the session to indicate that it can handle media button
@@ -121,21 +114,11 @@
             FLAG_EXCLUSIVE_GLOBAL_PRIORITY })
     public @interface SessionFlags { }
 
-    private final Object mLock = new Object();
-    private final int mMaxBitmapSize;
-
-    private final MediaSession.Token mSessionToken;
-    private final MediaController mController;
-    private final SessionLink mSessionLink;
-    private final SessionCallbackLink mCbStub;
+    private final MediaSessionEngine mImpl;
 
     // Do not change the name of mCallback. Support lib accesses this by using reflection.
     @UnsupportedAppUsage
-    private CallbackMessageHandler mCallback;
-    private VolumeProvider mVolumeProvider;
-    private PlaybackState mPlaybackState;
-
-    private boolean mActive = false;
+    private Object mCallback;
 
     /**
      * Creates a new session. The session will automatically be registered with
@@ -153,15 +136,15 @@
         if (TextUtils.isEmpty(tag)) {
             throw new IllegalArgumentException("tag cannot be null or empty");
         }
-        mMaxBitmapSize = context.getResources().getDimensionPixelSize(
-                android.R.dimen.config_mediaMetadataBitmapMaxSize);
-        mCbStub = new SessionCallbackLink(context, new CallbackStub(this));
         MediaSessionManager manager = (MediaSessionManager) context
                 .getSystemService(Context.MEDIA_SESSION_SERVICE);
         try {
-            mSessionLink = manager.createSession(mCbStub, tag);
-            mSessionToken = new Token(mSessionLink.getController());
-            mController = new MediaController(context, mSessionToken);
+            MediaSessionEngine.CallbackStub cbStub = new MediaSessionEngine.CallbackStub();
+            SessionCallbackLink cbLink = new SessionCallbackLink(context, cbStub);
+            SessionLink sessionLink = manager.createSession(cbLink, tag);
+            mImpl = new MediaSessionEngine(context, sessionLink, cbLink, cbStub,
+                    context.getResources().getDimensionPixelSize(
+                            android.R.dimen.config_mediaMetadataBitmapMaxSize));
         } catch (RuntimeException e) {
             throw new RuntimeException("Remote error creating session.", e);
         }
@@ -177,7 +160,8 @@
      * @param callback The callback object
      */
     public void setCallback(@Nullable Callback callback) {
-        setCallback(callback, null);
+        mCallback = callback == null ? null : new Object();
+        mImpl.setCallback(callback);
     }
 
     /**
@@ -190,24 +174,8 @@
      * @param handler The handler that events should be posted on.
      */
     public void setCallback(@Nullable Callback callback, @Nullable Handler handler) {
-        synchronized (mLock) {
-            if (mCallback != null) {
-                // We're updating the callback, clear the session from the old one.
-                mCallback.mCallback.mSession = null;
-                mCallback.removeCallbacksAndMessages(null);
-            }
-            if (callback == null) {
-                mCallback = null;
-                return;
-            }
-            if (handler == null) {
-                handler = new Handler();
-            }
-            callback.mSession = this;
-            CallbackMessageHandler msgHandler = new CallbackMessageHandler(handler.getLooper(),
-                    callback);
-            mCallback = msgHandler;
-        }
+        mCallback = callback == null ? null : new Object();
+        mImpl.setCallback(callback, handler);
     }
 
     /**
@@ -218,11 +186,7 @@
      * @param pi The intent to launch to show UI for this Session.
      */
     public void setSessionActivity(@Nullable PendingIntent pi) {
-        try {
-            mSessionLink.setLaunchPendingIntent(pi);
-        } catch (RuntimeException e) {
-            Log.wtf(TAG, "Failure in setLaunchPendingIntent.", e);
-        }
+        mImpl.setSessionActivity(pi);
     }
 
     /**
@@ -234,11 +198,7 @@
      * @param mbr The {@link PendingIntent} to send the media button event to.
      */
     public void setMediaButtonReceiver(@Nullable PendingIntent mbr) {
-        try {
-            mSessionLink.setMediaButtonReceiver(mbr);
-        } catch (RuntimeException e) {
-            Log.wtf(TAG, "Failure in setMediaButtonReceiver.", e);
-        }
+        mImpl.setMediaButtonReceiver(mbr);
     }
 
     /**
@@ -247,11 +207,7 @@
      * @param flags The flags to set for this session.
      */
     public void setFlags(@SessionFlags int flags) {
-        try {
-            mSessionLink.setFlags(flags);
-        } catch (RuntimeException e) {
-            Log.wtf(TAG, "Failure in setFlags.", e);
-        }
+        mImpl.setFlags(flags);
     }
 
     /**
@@ -266,14 +222,7 @@
      * @param attributes The {@link AudioAttributes} for this session's audio.
      */
     public void setPlaybackToLocal(AudioAttributes attributes) {
-        if (attributes == null) {
-            throw new IllegalArgumentException("Attributes cannot be null for local playback.");
-        }
-        try {
-            mSessionLink.setPlaybackToLocal(attributes);
-        } catch (RuntimeException e) {
-            Log.wtf(TAG, "Failure in setPlaybackToLocal.", e);
-        }
+        mImpl.setPlaybackToLocal(attributes);
     }
 
     /**
@@ -288,26 +237,7 @@
      *            not be null.
      */
     public void setPlaybackToRemote(@NonNull VolumeProvider volumeProvider) {
-        if (volumeProvider == null) {
-            throw new IllegalArgumentException("volumeProvider may not be null!");
-        }
-        synchronized (mLock) {
-            mVolumeProvider = volumeProvider;
-        }
-        volumeProvider.setCallback(new VolumeProvider.Callback() {
-            @Override
-            public void onVolumeChanged(VolumeProvider volumeProvider) {
-                notifyRemoteVolumeChanged(volumeProvider);
-            }
-        });
-
-        try {
-            mSessionLink.setPlaybackToRemote(volumeProvider.getVolumeControl(),
-                    volumeProvider.getMaxVolume());
-            mSessionLink.setCurrentVolume(volumeProvider.getCurrentVolume());
-        } catch (RuntimeException e) {
-            Log.wtf(TAG, "Failure in setPlaybackToRemote.", e);
-        }
+        mImpl.setPlaybackToRemote(volumeProvider);
     }
 
     /**
@@ -319,15 +249,7 @@
      * @param active Whether this session is active or not.
      */
     public void setActive(boolean active) {
-        if (mActive == active) {
-            return;
-        }
-        try {
-            mSessionLink.setActive(active);
-            mActive = active;
-        } catch (RuntimeException e) {
-            Log.wtf(TAG, "Failure in setActive.", e);
-        }
+        mImpl.setActive(active);
     }
 
     /**
@@ -336,7 +258,7 @@
      * @return True if the session is active, false otherwise.
      */
     public boolean isActive() {
-        return mActive;
+        return mImpl.isActive();
     }
 
     /**
@@ -348,14 +270,7 @@
      * @param extras Any extras included with the event
      */
     public void sendSessionEvent(@NonNull String event, @Nullable Bundle extras) {
-        if (TextUtils.isEmpty(event)) {
-            throw new IllegalArgumentException("event cannot be null or empty");
-        }
-        try {
-            mSessionLink.sendEvent(event, extras);
-        } catch (RuntimeException e) {
-            Log.wtf(TAG, "Error sending event", e);
-        }
+        mImpl.sendSessionEvent(event, extras);
     }
 
     /**
@@ -364,11 +279,7 @@
      * but it must be released if your activity or service is being destroyed.
      */
     public void release() {
-        try {
-            mSessionLink.destroySession();
-        } catch (RuntimeException e) {
-            Log.wtf(TAG, "Error releasing session: ", e);
-        }
+        mImpl.close();
     }
 
     /**
@@ -380,7 +291,7 @@
      *         session
      */
     public @NonNull Token getSessionToken() {
-        return mSessionToken;
+        return mImpl.getSessionToken();
     }
 
     /**
@@ -390,7 +301,7 @@
      * @return A controller for this session.
      */
     public @NonNull MediaController getController() {
-        return mController;
+        return mImpl.getController();
     }
 
     /**
@@ -399,12 +310,7 @@
      * @param state The current state of playback
      */
     public void setPlaybackState(@Nullable PlaybackState state) {
-        mPlaybackState = state;
-        try {
-            mSessionLink.setPlaybackState(state);
-        } catch (RuntimeException e) {
-            Log.wtf(TAG, "Dead object in setPlaybackState.", e);
-        }
+        mImpl.setPlaybackState(state);
     }
 
     /**
@@ -416,24 +322,7 @@
      * @see android.media.MediaMetadata.Builder#putBitmap
      */
     public void setMetadata(@Nullable MediaMetadata metadata) {
-        long duration = -1;
-        int fields = 0;
-        MediaDescription description = null;
-        if (metadata != null) {
-            metadata = (new MediaMetadata.Builder(metadata, mMaxBitmapSize)).build();
-            if (metadata.containsKey(MediaMetadata.METADATA_KEY_DURATION)) {
-                duration = metadata.getLong(MediaMetadata.METADATA_KEY_DURATION);
-            }
-            fields = metadata.size();
-            description = metadata.getDescription();
-        }
-        String metadataDescription = "size=" + fields + ", description=" + description;
-
-        try {
-            mSessionLink.setMetadata(metadata, duration, metadataDescription);
-        } catch (RuntimeException e) {
-            Log.wtf(TAG, "Dead object in setPlaybackState.", e);
-        }
+        mImpl.setMetadata(metadata);
     }
 
     /**
@@ -448,11 +337,7 @@
      * @param queue A list of items in the play queue.
      */
     public void setQueue(@Nullable List<QueueItem> queue) {
-        try {
-            mSessionLink.setQueue(queue);
-        } catch (RuntimeException e) {
-            Log.wtf("Dead object in setQueue.", e);
-        }
+        mImpl.setQueue(queue);
     }
 
     /**
@@ -463,11 +348,7 @@
      * @param title The title of the play queue.
      */
     public void setQueueTitle(@Nullable CharSequence title) {
-        try {
-            mSessionLink.setQueueTitle(title);
-        } catch (RuntimeException e) {
-            Log.wtf("Dead object in setQueueTitle.", e);
-        }
+        mImpl.setQueueTitle(title);
     }
 
     /**
@@ -484,11 +365,7 @@
      * </ul>
      */
     public void setRatingType(@Rating.Style int type) {
-        try {
-            mSessionLink.setRatingType(type);
-        } catch (RuntimeException e) {
-            Log.e(TAG, "Error in setRatingType.", e);
-        }
+        mImpl.setRatingType(type);
     }
 
     /**
@@ -499,11 +376,7 @@
      * @param extras The extras associated with the {@link MediaSession}.
      */
     public void setExtras(@Nullable Bundle extras) {
-        try {
-            mSessionLink.setExtras(extras);
-        } catch (RuntimeException e) {
-            Log.wtf("Dead object in setExtras.", e);
-        }
+        mImpl.setExtras(extras);
     }
 
     /**
@@ -515,11 +388,7 @@
      * @see MediaSessionManager#isTrustedForMediaControl(RemoteUserInfo)
      */
     public final @NonNull RemoteUserInfo getCurrentControllerInfo() {
-        if (mCallback == null || mCallback.mCurrentControllerInfo == null) {
-            throw new IllegalStateException(
-                    "This should be called inside of MediaSession.Callback methods");
-        }
-        return mCallback.mCurrentControllerInfo;
+        return mImpl.getCurrentControllerInfo();
     }
 
     /**
@@ -529,17 +398,7 @@
      * @hide
      */
     public void notifyRemoteVolumeChanged(VolumeProvider provider) {
-        synchronized (mLock) {
-            if (provider == null || provider != mVolumeProvider) {
-                Log.w(TAG, "Received update from stale volume provider");
-                return;
-            }
-        }
-        try {
-            mSessionLink.setCurrentVolume(provider.getCurrentVolume());
-        } catch (RuntimeException e) {
-            Log.e(TAG, "Error in notifyVolumeChanged", e);
-        }
+        mImpl.notifyRemoteVolumeChanged(provider);
     }
 
     /**
@@ -551,119 +410,7 @@
      */
     @UnsupportedAppUsage
     public String getCallingPackage() {
-        if (mCallback != null && mCallback.mCurrentControllerInfo != null) {
-            return mCallback.mCurrentControllerInfo.getPackageName();
-        }
-        return null;
-    }
-
-    private void dispatchPrepare(RemoteUserInfo caller) {
-        postToCallback(caller, CallbackMessageHandler.MSG_PREPARE, null, null);
-    }
-
-    private void dispatchPrepareFromMediaId(RemoteUserInfo caller, String mediaId, Bundle extras) {
-        postToCallback(caller, CallbackMessageHandler.MSG_PREPARE_MEDIA_ID, mediaId, extras);
-    }
-
-    private void dispatchPrepareFromSearch(RemoteUserInfo caller, String query, Bundle extras) {
-        postToCallback(caller, CallbackMessageHandler.MSG_PREPARE_SEARCH, query, extras);
-    }
-
-    private void dispatchPrepareFromUri(RemoteUserInfo caller, Uri uri, Bundle extras) {
-        postToCallback(caller, CallbackMessageHandler.MSG_PREPARE_URI, uri, extras);
-    }
-
-    private void dispatchPlay(RemoteUserInfo caller) {
-        postToCallback(caller, CallbackMessageHandler.MSG_PLAY, null, null);
-    }
-
-    private void dispatchPlayFromMediaId(RemoteUserInfo caller, String mediaId, Bundle extras) {
-        postToCallback(caller, CallbackMessageHandler.MSG_PLAY_MEDIA_ID, mediaId, extras);
-    }
-
-    private void dispatchPlayFromSearch(RemoteUserInfo caller, String query, Bundle extras) {
-        postToCallback(caller, CallbackMessageHandler.MSG_PLAY_SEARCH, query, extras);
-    }
-
-    private void dispatchPlayFromUri(RemoteUserInfo caller, Uri uri, Bundle extras) {
-        postToCallback(caller, CallbackMessageHandler.MSG_PLAY_URI, uri, extras);
-    }
-
-    private void dispatchSkipToItem(RemoteUserInfo caller, long id) {
-        postToCallback(caller, CallbackMessageHandler.MSG_SKIP_TO_ITEM, id, null);
-    }
-
-    private void dispatchPause(RemoteUserInfo caller) {
-        postToCallback(caller, CallbackMessageHandler.MSG_PAUSE, null, null);
-    }
-
-    private void dispatchStop(RemoteUserInfo caller) {
-        postToCallback(caller, CallbackMessageHandler.MSG_STOP, null, null);
-    }
-
-    private void dispatchNext(RemoteUserInfo caller) {
-        postToCallback(caller, CallbackMessageHandler.MSG_NEXT, null, null);
-    }
-
-    private void dispatchPrevious(RemoteUserInfo caller) {
-        postToCallback(caller, CallbackMessageHandler.MSG_PREVIOUS, null, null);
-    }
-
-    private void dispatchFastForward(RemoteUserInfo caller) {
-        postToCallback(caller, CallbackMessageHandler.MSG_FAST_FORWARD, null, null);
-    }
-
-    private void dispatchRewind(RemoteUserInfo caller) {
-        postToCallback(caller, CallbackMessageHandler.MSG_REWIND, null, null);
-    }
-
-    private void dispatchSeekTo(RemoteUserInfo caller, long pos) {
-        postToCallback(caller, CallbackMessageHandler.MSG_SEEK_TO, pos, null);
-    }
-
-    private void dispatchRate(RemoteUserInfo caller, Rating rating) {
-        postToCallback(caller, CallbackMessageHandler.MSG_RATE, rating, null);
-    }
-
-    private void dispatchCustomAction(RemoteUserInfo caller, String action, Bundle args) {
-        postToCallback(caller, CallbackMessageHandler.MSG_CUSTOM_ACTION, action, args);
-    }
-
-    private void dispatchMediaButton(RemoteUserInfo caller, Intent mediaButtonIntent) {
-        postToCallback(caller, CallbackMessageHandler.MSG_MEDIA_BUTTON, mediaButtonIntent, null);
-    }
-
-    private void dispatchMediaButtonDelayed(RemoteUserInfo info, Intent mediaButtonIntent,
-            long delay) {
-        postToCallbackDelayed(info, CallbackMessageHandler.MSG_PLAY_PAUSE_KEY_DOUBLE_TAP_TIMEOUT,
-                mediaButtonIntent, null, delay);
-    }
-
-    private void dispatchAdjustVolume(RemoteUserInfo caller, int direction) {
-        postToCallback(caller, CallbackMessageHandler.MSG_ADJUST_VOLUME, direction, null);
-    }
-
-    private void dispatchSetVolumeTo(RemoteUserInfo caller, int volume) {
-        postToCallback(caller, CallbackMessageHandler.MSG_SET_VOLUME, volume, null);
-    }
-
-    private void dispatchCommand(RemoteUserInfo caller, String command, Bundle args,
-            ResultReceiver resultCb) {
-        Command cmd = new Command(command, args, resultCb);
-        postToCallback(caller, CallbackMessageHandler.MSG_COMMAND, cmd, null);
-    }
-
-    private void postToCallback(RemoteUserInfo caller, int what, Object obj, Bundle data) {
-        postToCallbackDelayed(caller, what, obj, data, 0);
-    }
-
-    private void postToCallbackDelayed(RemoteUserInfo caller, int what, Object obj, Bundle data,
-            long delay) {
-        synchronized (mLock) {
-            if (mCallback != null) {
-                mCallback.post(caller, what, obj, data, delay);
-            }
-        }
+        return mImpl.getCallingPackage();
     }
 
     /**
@@ -672,17 +419,7 @@
      * @hide
      */
     public static boolean isActiveState(int state) {
-        switch (state) {
-            case PlaybackState.STATE_FAST_FORWARDING:
-            case PlaybackState.STATE_REWINDING:
-            case PlaybackState.STATE_SKIPPING_TO_PREVIOUS:
-            case PlaybackState.STATE_SKIPPING_TO_NEXT:
-            case PlaybackState.STATE_BUFFERING:
-            case PlaybackState.STATE_CONNECTING:
-            case PlaybackState.STATE_PLAYING:
-                return true;
-        }
-        return false;
+        return MediaSessionEngine.isActiveState(state);
     }
 
     /**
@@ -692,13 +429,13 @@
      */
     public static final class Token implements Parcelable {
 
-        private ControllerLink mBinder;
+        private ControllerLink mControllerLink;
 
         /**
          * @hide
          */
-        public Token(ControllerLink binder) {
-            mBinder = binder;
+        public Token(ControllerLink controllerLink) {
+            mControllerLink = controllerLink;
         }
 
         @Override
@@ -708,14 +445,15 @@
 
         @Override
         public void writeToParcel(Parcel dest, int flags) {
-            dest.writeParcelable(mBinder, flags);
+            dest.writeParcelable(mControllerLink, flags);
         }
 
         @Override
         public int hashCode() {
             final int prime = 31;
             int result = 1;
-            result = prime * result + ((mBinder == null) ? 0 : mBinder.getBinder().hashCode());
+            result = prime * result + ((mControllerLink == null)
+                    ? 0 : mControllerLink.getBinder().hashCode());
             return result;
         }
 
@@ -728,17 +466,23 @@
             if (getClass() != obj.getClass())
                 return false;
             Token other = (Token) obj;
-            if (mBinder == null) {
-                if (other.mBinder != null)
+            if (mControllerLink == null) {
+                if (other.mControllerLink != null) {
                     return false;
-            } else if (!mBinder.getBinder().equals(other.mBinder.getBinder())) {
+                }
+            } else if (!mControllerLink.getBinder().equals(other.mControllerLink.getBinder())) {
                 return false;
             }
             return true;
         }
 
-        ControllerLink getBinder() {
-            return mBinder;
+        /**
+         * Gets the controller link in this token.
+         * @hide
+         */
+        @SystemApi
+        public ControllerLink getControllerLink() {
+            return mControllerLink;
         }
 
         public static final Parcelable.Creator<Token> CREATOR =
@@ -762,9 +506,7 @@
      */
     public abstract static class Callback {
 
-        private MediaSession mSession;
-        private CallbackMessageHandler mHandler;
-        private boolean mMediaPlayPauseKeyPending;
+        MediaSessionEngine.MediaButtonEventDelegate mMediaButtonEventDelegate;
 
         public Callback() {
         }
@@ -796,110 +538,12 @@
          * @return True if the event was handled, false otherwise.
          */
         public boolean onMediaButtonEvent(@NonNull Intent mediaButtonIntent) {
-            if (mSession != null && mHandler != null
-                    && Intent.ACTION_MEDIA_BUTTON.equals(mediaButtonIntent.getAction())) {
-                KeyEvent ke = mediaButtonIntent.getParcelableExtra(Intent.EXTRA_KEY_EVENT);
-                if (ke != null && ke.getAction() == KeyEvent.ACTION_DOWN) {
-                    PlaybackState state = mSession.mPlaybackState;
-                    long validActions = state == null ? 0 : state.getActions();
-                    switch (ke.getKeyCode()) {
-                        case KeyEvent.KEYCODE_MEDIA_PLAY_PAUSE:
-                        case KeyEvent.KEYCODE_HEADSETHOOK:
-                            if (ke.getRepeatCount() > 0) {
-                                // Consider long-press as a single tap.
-                                handleMediaPlayPauseKeySingleTapIfPending();
-                            } else if (mMediaPlayPauseKeyPending) {
-                                // Consider double tap as the next.
-                                mHandler.removeMessages(CallbackMessageHandler
-                                        .MSG_PLAY_PAUSE_KEY_DOUBLE_TAP_TIMEOUT);
-                                mMediaPlayPauseKeyPending = false;
-                                if ((validActions & PlaybackState.ACTION_SKIP_TO_NEXT) != 0) {
-                                    onSkipToNext();
-                                }
-                            } else {
-                                mMediaPlayPauseKeyPending = true;
-                                mSession.dispatchMediaButtonDelayed(
-                                        mSession.getCurrentControllerInfo(),
-                                        mediaButtonIntent, ViewConfiguration.getDoubleTapTimeout());
-                            }
-                            return true;
-                        default:
-                            // If another key is pressed within double tap timeout, consider the
-                            // pending play/pause as a single tap to handle media keys in order.
-                            handleMediaPlayPauseKeySingleTapIfPending();
-                            break;
-                    }
-
-                    switch (ke.getKeyCode()) {
-                        case KeyEvent.KEYCODE_MEDIA_PLAY:
-                            if ((validActions & PlaybackState.ACTION_PLAY) != 0) {
-                                onPlay();
-                                return true;
-                            }
-                            break;
-                        case KeyEvent.KEYCODE_MEDIA_PAUSE:
-                            if ((validActions & PlaybackState.ACTION_PAUSE) != 0) {
-                                onPause();
-                                return true;
-                            }
-                            break;
-                        case KeyEvent.KEYCODE_MEDIA_NEXT:
-                            if ((validActions & PlaybackState.ACTION_SKIP_TO_NEXT) != 0) {
-                                onSkipToNext();
-                                return true;
-                            }
-                            break;
-                        case KeyEvent.KEYCODE_MEDIA_PREVIOUS:
-                            if ((validActions & PlaybackState.ACTION_SKIP_TO_PREVIOUS) != 0) {
-                                onSkipToPrevious();
-                                return true;
-                            }
-                            break;
-                        case KeyEvent.KEYCODE_MEDIA_STOP:
-                            if ((validActions & PlaybackState.ACTION_STOP) != 0) {
-                                onStop();
-                                return true;
-                            }
-                            break;
-                        case KeyEvent.KEYCODE_MEDIA_FAST_FORWARD:
-                            if ((validActions & PlaybackState.ACTION_FAST_FORWARD) != 0) {
-                                onFastForward();
-                                return true;
-                            }
-                            break;
-                        case KeyEvent.KEYCODE_MEDIA_REWIND:
-                            if ((validActions & PlaybackState.ACTION_REWIND) != 0) {
-                                onRewind();
-                                return true;
-                            }
-                            break;
-                    }
-                }
+            if (mMediaButtonEventDelegate != null) {
+                return mMediaButtonEventDelegate.onMediaButtonIntent(mediaButtonIntent);
             }
             return false;
         }
 
-        private void handleMediaPlayPauseKeySingleTapIfPending() {
-            if (!mMediaPlayPauseKeyPending) {
-                return;
-            }
-            mMediaPlayPauseKeyPending = false;
-            mHandler.removeMessages(CallbackMessageHandler.MSG_PLAY_PAUSE_KEY_DOUBLE_TAP_TIMEOUT);
-            PlaybackState state = mSession.mPlaybackState;
-            long validActions = state == null ? 0 : state.getActions();
-            boolean isPlaying = state != null
-                    && state.getState() == PlaybackState.STATE_PLAYING;
-            boolean canPlay = (validActions & (PlaybackState.ACTION_PLAY_PAUSE
-                        | PlaybackState.ACTION_PLAY)) != 0;
-            boolean canPause = (validActions & (PlaybackState.ACTION_PLAY_PAUSE
-                        | PlaybackState.ACTION_PAUSE)) != 0;
-            if (isPlaying && canPause) {
-                onPause();
-            } else if (!isPlaying && canPlay) {
-                onPlay();
-            }
-        }
-
         /**
          * Override to handle requests to prepare playback. During the preparation, a session should
          * not hold audio focus in order to allow other sessions play seamlessly. The state of
@@ -1042,251 +686,14 @@
          */
         public void onCustomAction(@NonNull String action, @Nullable Bundle extras) {
         }
-    }
 
-    /**
-     * @hide
-     */
-    public static final class CallbackStub extends SessionCallbackLink.CallbackStub {
-        private WeakReference<MediaSession> mMediaSession;
-
-        public CallbackStub(MediaSession session) {
-            mMediaSession = new WeakReference<>(session);
-        }
-
-        private static RemoteUserInfo createRemoteUserInfo(String packageName, int pid, int uid,
-                ControllerCallbackLink caller) {
-            return new RemoteUserInfo(packageName, pid, uid,
-                    caller != null ? caller.getBinder() : null);
-        }
-
-        @Override
-        public void onCommand(String packageName, int pid, int uid,
-                ControllerCallbackLink caller, String command, Bundle args, ResultReceiver cb) {
-            MediaSession session = mMediaSession.get();
-            if (session != null) {
-                session.dispatchCommand(createRemoteUserInfo(packageName, pid, uid, caller),
-                        command, args, cb);
-            }
-        }
-
-        @Override
-        public void onMediaButton(String packageName, int pid, int uid, Intent mediaButtonIntent,
-                int sequenceNumber, ResultReceiver cb) {
-            MediaSession session = mMediaSession.get();
-            try {
-                if (session != null) {
-                    session.dispatchMediaButton(createRemoteUserInfo(packageName, pid, uid, null),
-                            mediaButtonIntent);
-                }
-            } finally {
-                if (cb != null) {
-                    cb.send(sequenceNumber, null);
-                }
-            }
-        }
-
-        @Override
-        public void onMediaButtonFromController(String packageName, int pid, int uid,
-                ControllerCallbackLink caller, Intent mediaButtonIntent) {
-            MediaSession session = mMediaSession.get();
-            if (session != null) {
-                session.dispatchMediaButton(createRemoteUserInfo(packageName, pid, uid, caller),
-                        mediaButtonIntent);
-            }
-        }
-
-        @Override
-        public void onPrepare(String packageName, int pid, int uid,
-                ControllerCallbackLink caller) {
-            MediaSession session = mMediaSession.get();
-            if (session != null) {
-                session.dispatchPrepare(createRemoteUserInfo(packageName, pid, uid, caller));
-            }
-        }
-
-        @Override
-        public void onPrepareFromMediaId(String packageName, int pid, int uid,
-                ControllerCallbackLink caller, String mediaId,
-                Bundle extras) {
-            MediaSession session = mMediaSession.get();
-            if (session != null) {
-                session.dispatchPrepareFromMediaId(
-                        createRemoteUserInfo(packageName, pid, uid, caller), mediaId, extras);
-            }
-        }
-
-        @Override
-        public void onPrepareFromSearch(String packageName, int pid, int uid,
-                ControllerCallbackLink caller, String query,
-                Bundle extras) {
-            MediaSession session = mMediaSession.get();
-            if (session != null) {
-                session.dispatchPrepareFromSearch(
-                        createRemoteUserInfo(packageName, pid, uid, caller), query, extras);
-            }
-        }
-
-        @Override
-        public void onPrepareFromUri(String packageName, int pid, int uid,
-                ControllerCallbackLink caller, Uri uri, Bundle extras) {
-            MediaSession session = mMediaSession.get();
-            if (session != null) {
-                session.dispatchPrepareFromUri(createRemoteUserInfo(packageName, pid, uid, caller),
-                        uri, extras);
-            }
-        }
-
-        @Override
-        public void onPlay(String packageName, int pid, int uid,
-                ControllerCallbackLink caller) {
-            MediaSession session = mMediaSession.get();
-            if (session != null) {
-                session.dispatchPlay(createRemoteUserInfo(packageName, pid, uid, caller));
-            }
-        }
-
-        @Override
-        public void onPlayFromMediaId(String packageName, int pid, int uid,
-                ControllerCallbackLink caller, String mediaId,
-                Bundle extras) {
-            MediaSession session = mMediaSession.get();
-            if (session != null) {
-                session.dispatchPlayFromMediaId(createRemoteUserInfo(packageName, pid, uid, caller),
-                        mediaId, extras);
-            }
-        }
-
-        @Override
-        public void onPlayFromSearch(String packageName, int pid, int uid,
-                ControllerCallbackLink caller, String query,
-                Bundle extras) {
-            MediaSession session = mMediaSession.get();
-            if (session != null) {
-                session.dispatchPlayFromSearch(createRemoteUserInfo(packageName, pid, uid, caller),
-                        query, extras);
-            }
-        }
-
-        @Override
-        public void onPlayFromUri(String packageName, int pid, int uid,
-                ControllerCallbackLink caller, Uri uri, Bundle extras) {
-            MediaSession session = mMediaSession.get();
-            if (session != null) {
-                session.dispatchPlayFromUri(createRemoteUserInfo(packageName, pid, uid, caller),
-                        uri, extras);
-            }
-        }
-
-        @Override
-        public void onSkipToTrack(String packageName, int pid, int uid,
-                ControllerCallbackLink caller, long id) {
-            MediaSession session = mMediaSession.get();
-            if (session != null) {
-                session.dispatchSkipToItem(createRemoteUserInfo(packageName, pid, uid, caller), id);
-            }
-        }
-
-        @Override
-        public void onPause(String packageName, int pid, int uid,
-                ControllerCallbackLink caller) {
-            MediaSession session = mMediaSession.get();
-            if (session != null) {
-                session.dispatchPause(createRemoteUserInfo(packageName, pid, uid, caller));
-            }
-        }
-
-        @Override
-        public void onStop(String packageName, int pid, int uid,
-                ControllerCallbackLink caller) {
-            MediaSession session = mMediaSession.get();
-            if (session != null) {
-                session.dispatchStop(createRemoteUserInfo(packageName, pid, uid, caller));
-            }
-        }
-
-        @Override
-        public void onNext(String packageName, int pid, int uid,
-                ControllerCallbackLink caller) {
-            MediaSession session = mMediaSession.get();
-            if (session != null) {
-                session.dispatchNext(createRemoteUserInfo(packageName, pid, uid, caller));
-            }
-        }
-
-        @Override
-        public void onPrevious(String packageName, int pid, int uid,
-                ControllerCallbackLink caller) {
-            MediaSession session = mMediaSession.get();
-            if (session != null) {
-                session.dispatchPrevious(createRemoteUserInfo(packageName, pid, uid, caller));
-            }
-        }
-
-        @Override
-        public void onFastForward(String packageName, int pid, int uid,
-                ControllerCallbackLink caller) {
-            MediaSession session = mMediaSession.get();
-            if (session != null) {
-                session.dispatchFastForward(createRemoteUserInfo(packageName, pid, uid, caller));
-            }
-        }
-
-        @Override
-        public void onRewind(String packageName, int pid, int uid,
-                ControllerCallbackLink caller) {
-            MediaSession session = mMediaSession.get();
-            if (session != null) {
-                session.dispatchRewind(createRemoteUserInfo(packageName, pid, uid, caller));
-            }
-        }
-
-        @Override
-        public void onSeekTo(String packageName, int pid, int uid,
-                ControllerCallbackLink caller, long pos) {
-            MediaSession session = mMediaSession.get();
-            if (session != null) {
-                session.dispatchSeekTo(createRemoteUserInfo(packageName, pid, uid, caller), pos);
-            }
-        }
-
-        @Override
-        public void onRate(String packageName, int pid, int uid, ControllerCallbackLink caller,
-                Rating rating) {
-            MediaSession session = mMediaSession.get();
-            if (session != null) {
-                session.dispatchRate(createRemoteUserInfo(packageName, pid, uid, caller), rating);
-            }
-        }
-
-        @Override
-        public void onCustomAction(String packageName, int pid, int uid,
-                ControllerCallbackLink caller, String action, Bundle args) {
-            MediaSession session = mMediaSession.get();
-            if (session != null) {
-                session.dispatchCustomAction(createRemoteUserInfo(packageName, pid, uid, caller),
-                        action, args);
-            }
-        }
-
-        @Override
-        public void onAdjustVolume(String packageName, int pid, int uid,
-                ControllerCallbackLink caller, int direction) {
-            MediaSession session = mMediaSession.get();
-            if (session != null) {
-                session.dispatchAdjustVolume(createRemoteUserInfo(packageName, pid, uid, caller),
-                        direction);
-            }
-        }
-
-        @Override
-        public void onSetVolumeTo(String packageName, int pid, int uid,
-                ControllerCallbackLink caller, int value) {
-            MediaSession session = mMediaSession.get();
-            if (session != null) {
-                session.dispatchSetVolumeTo(createRemoteUserInfo(packageName, pid, uid, caller),
-                        value);
-            }
+        /**
+         * @hide
+         */
+        @SystemApi
+        public void onSetMediaButtonEventDelegate(
+                @NonNull MediaSessionEngine.MediaButtonEventDelegate delegate) {
+            mMediaButtonEventDelegate = delegate;
         }
     }
 
@@ -1300,7 +707,7 @@
          */
         public static final int UNKNOWN_ID = -1;
 
-        private final MediaDescription mDescription;
+        private final MediaSessionEngine.QueueItem mImpl;
         @UnsupportedAppUsage
         private final long mId;
 
@@ -1312,39 +719,32 @@
          *            play queue and cannot be {@link #UNKNOWN_ID}.
          */
         public QueueItem(MediaDescription description, long id) {
-            if (description == null) {
-                throw new IllegalArgumentException("Description cannot be null.");
-            }
-            if (id == UNKNOWN_ID) {
-                throw new IllegalArgumentException("Id cannot be QueueItem.UNKNOWN_ID");
-            }
-            mDescription = description;
+            mImpl = new MediaSessionEngine.QueueItem(description, id);
             mId = id;
         }
 
         private QueueItem(Parcel in) {
-            mDescription = MediaDescription.CREATOR.createFromParcel(in);
-            mId = in.readLong();
+            mImpl = new MediaSessionEngine.QueueItem(in);
+            mId = mImpl.getQueueId();
         }
 
         /**
          * Get the description for this item.
          */
         public MediaDescription getDescription() {
-            return mDescription;
+            return mImpl.getDescription();
         }
 
         /**
          * Get the queue id for this item.
          */
         public long getQueueId() {
-            return mId;
+            return mImpl.getQueueId();
         }
 
         @Override
         public void writeToParcel(Parcel dest, int flags) {
-            mDescription.writeToParcel(dest, flags);
-            dest.writeLong(mId);
+            mImpl.writeToParcel(dest, flags);
         }
 
         @Override
@@ -1368,9 +768,7 @@
 
         @Override
         public String toString() {
-            return "MediaSession.QueueItem {" +
-                    "Description=" + mDescription +
-                    ", Id=" + mId + " }";
+            return mImpl.toString();
         }
 
         @Override
@@ -1383,167 +781,7 @@
                 return false;
             }
 
-            final QueueItem item = (QueueItem) o;
-            if (mId != item.mId) {
-                return false;
-            }
-
-            if (!Objects.equals(mDescription, item.mDescription)) {
-                return false;
-            }
-
-            return true;
-        }
-    }
-
-    private static final class Command {
-        public final String command;
-        public final Bundle extras;
-        public final ResultReceiver stub;
-
-        public Command(String command, Bundle extras, ResultReceiver stub) {
-            this.command = command;
-            this.extras = extras;
-            this.stub = stub;
-        }
-    }
-
-    private class CallbackMessageHandler extends Handler {
-        private static final int MSG_COMMAND = 1;
-        private static final int MSG_MEDIA_BUTTON = 2;
-        private static final int MSG_PREPARE = 3;
-        private static final int MSG_PREPARE_MEDIA_ID = 4;
-        private static final int MSG_PREPARE_SEARCH = 5;
-        private static final int MSG_PREPARE_URI = 6;
-        private static final int MSG_PLAY = 7;
-        private static final int MSG_PLAY_MEDIA_ID = 8;
-        private static final int MSG_PLAY_SEARCH = 9;
-        private static final int MSG_PLAY_URI = 10;
-        private static final int MSG_SKIP_TO_ITEM = 11;
-        private static final int MSG_PAUSE = 12;
-        private static final int MSG_STOP = 13;
-        private static final int MSG_NEXT = 14;
-        private static final int MSG_PREVIOUS = 15;
-        private static final int MSG_FAST_FORWARD = 16;
-        private static final int MSG_REWIND = 17;
-        private static final int MSG_SEEK_TO = 18;
-        private static final int MSG_RATE = 19;
-        private static final int MSG_CUSTOM_ACTION = 20;
-        private static final int MSG_ADJUST_VOLUME = 21;
-        private static final int MSG_SET_VOLUME = 22;
-        private static final int MSG_PLAY_PAUSE_KEY_DOUBLE_TAP_TIMEOUT = 23;
-
-        private MediaSession.Callback mCallback;
-        private RemoteUserInfo mCurrentControllerInfo;
-
-        public CallbackMessageHandler(Looper looper, MediaSession.Callback callback) {
-            super(looper);
-            mCallback = callback;
-            mCallback.mHandler = this;
-        }
-
-        public void post(RemoteUserInfo caller, int what, Object obj, Bundle data, long delayMs) {
-            Pair<RemoteUserInfo, Object> objWithCaller = Pair.create(caller, obj);
-            Message msg = obtainMessage(what, objWithCaller);
-            msg.setAsynchronous(true);
-            msg.setData(data);
-            if (delayMs > 0) {
-                sendMessageDelayed(msg, delayMs);
-            } else {
-                sendMessage(msg);
-            }
-        }
-
-        @Override
-        public void handleMessage(Message msg) {
-            mCurrentControllerInfo = ((Pair<RemoteUserInfo, Object>) msg.obj).first;
-
-            VolumeProvider vp;
-            Object obj = ((Pair<RemoteUserInfo, Object>) msg.obj).second;
-
-            switch (msg.what) {
-                case MSG_COMMAND:
-                    Command cmd = (Command) obj;
-                    mCallback.onCommand(cmd.command, cmd.extras, cmd.stub);
-                    break;
-                case MSG_MEDIA_BUTTON:
-                    mCallback.onMediaButtonEvent((Intent) obj);
-                    break;
-                case MSG_PREPARE:
-                    mCallback.onPrepare();
-                    break;
-                case MSG_PREPARE_MEDIA_ID:
-                    mCallback.onPrepareFromMediaId((String) obj, msg.getData());
-                    break;
-                case MSG_PREPARE_SEARCH:
-                    mCallback.onPrepareFromSearch((String) obj, msg.getData());
-                    break;
-                case MSG_PREPARE_URI:
-                    mCallback.onPrepareFromUri((Uri) obj, msg.getData());
-                    break;
-                case MSG_PLAY:
-                    mCallback.onPlay();
-                    break;
-                case MSG_PLAY_MEDIA_ID:
-                    mCallback.onPlayFromMediaId((String) obj, msg.getData());
-                    break;
-                case MSG_PLAY_SEARCH:
-                    mCallback.onPlayFromSearch((String) obj, msg.getData());
-                    break;
-                case MSG_PLAY_URI:
-                    mCallback.onPlayFromUri((Uri) obj, msg.getData());
-                    break;
-                case MSG_SKIP_TO_ITEM:
-                    mCallback.onSkipToQueueItem((Long) obj);
-                    break;
-                case MSG_PAUSE:
-                    mCallback.onPause();
-                    break;
-                case MSG_STOP:
-                    mCallback.onStop();
-                    break;
-                case MSG_NEXT:
-                    mCallback.onSkipToNext();
-                    break;
-                case MSG_PREVIOUS:
-                    mCallback.onSkipToPrevious();
-                    break;
-                case MSG_FAST_FORWARD:
-                    mCallback.onFastForward();
-                    break;
-                case MSG_REWIND:
-                    mCallback.onRewind();
-                    break;
-                case MSG_SEEK_TO:
-                    mCallback.onSeekTo((Long) obj);
-                    break;
-                case MSG_RATE:
-                    mCallback.onSetRating((Rating) obj);
-                    break;
-                case MSG_CUSTOM_ACTION:
-                    mCallback.onCustomAction((String) obj, msg.getData());
-                    break;
-                case MSG_ADJUST_VOLUME:
-                    synchronized (mLock) {
-                        vp = mVolumeProvider;
-                    }
-                    if (vp != null) {
-                        vp.onAdjustVolume((int) obj);
-                    }
-                    break;
-                case MSG_SET_VOLUME:
-                    synchronized (mLock) {
-                        vp = mVolumeProvider;
-                    }
-                    if (vp != null) {
-                        vp.onSetVolumeTo((int) obj);
-                    }
-                    break;
-                case MSG_PLAY_PAUSE_KEY_DOUBLE_TAP_TIMEOUT:
-                    mCallback.handleMediaPlayPauseKeySingleTapIfPending();
-                    break;
-            }
-            mCurrentControllerInfo = null;
+            return mImpl.equals(((QueueItem) o).mImpl);
         }
     }
 }
diff --git a/media/java/android/media/session/MediaSessionManager.java b/media/java/android/media/session/MediaSessionManager.java
index 3f4fbb9..cae4d17 100644
--- a/media/java/android/media/session/MediaSessionManager.java
+++ b/media/java/android/media/session/MediaSessionManager.java
@@ -26,9 +26,7 @@
 import android.content.Context;
 import android.media.AudioManager;
 import android.media.IRemoteVolumeController;
-import android.media.MediaSession2;
 import android.media.Session2Token;
-import android.media.browse.MediaBrowser;
 import android.os.Handler;
 import android.os.IBinder;
 import android.os.RemoteException;
@@ -37,6 +35,7 @@
 import android.os.UserHandle;
 import android.service.media.MediaBrowserService;
 import android.service.notification.NotificationListenerService;
+import android.text.TextUtils;
 import android.util.ArrayMap;
 import android.util.Log;
 import android.view.KeyEvent;
@@ -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}.
      */
@@ -797,7 +824,6 @@
         private final String mPackageName;
         private final int mPid;
         private final int mUid;
-        private final IBinder mCallerBinder;
 
         /**
          * Create a new remote user information.
@@ -807,22 +833,9 @@
          * @param uid The uid of the remote user
          */
         public RemoteUserInfo(@NonNull String packageName, int pid, int uid) {
-            this(packageName, pid, uid, null);
-        }
-
-        /**
-         * Create a new remote user information.
-         *
-         * @param packageName The package name of the remote user
-         * @param pid The pid of the remote user
-         * @param uid The uid of the remote user
-         * @param callerBinder The binder of the remote user. Can be {@code null}.
-         */
-        public RemoteUserInfo(String packageName, int pid, int uid, IBinder callerBinder) {
             mPackageName = packageName;
             mPid = pid;
             mUid = uid;
-            mCallerBinder = callerBinder;
         }
 
         /**
@@ -847,13 +860,8 @@
         }
 
         /**
-         * Returns equality of two RemoteUserInfo. Two RemoteUserInfos are the same only if they're
-         * sent to the same controller (either {@link MediaController} or
-         * {@link MediaBrowser}. If it's not nor one of them is triggered by the key presses, they
-         * would be considered as different one.
-         * <p>
-         * If you only want to compare the caller's package, compare them with the
-         * {@link #getPackageName()}, {@link #getPid()}, and/or {@link #getUid()} directly.
+         * Returns equality of two RemoteUserInfo. Two RemoteUserInfo objects are equal
+         * if and only if they have the same package name, same pid, and same uid.
          *
          * @param obj the reference object with which to compare.
          * @return {@code true} if equals, {@code false} otherwise
@@ -867,8 +875,9 @@
                 return true;
             }
             RemoteUserInfo otherUserInfo = (RemoteUserInfo) obj;
-            return (mCallerBinder == null || otherUserInfo.mCallerBinder == null) ? false
-                    : mCallerBinder.equals(otherUserInfo.mCallerBinder);
+            return TextUtils.equals(mPackageName, otherUserInfo.mPackageName)
+                    && mPid == otherUserInfo.mPid
+                    && mUid == otherUserInfo.mUid;
         }
 
         @Override
diff --git a/media/java/android/media/tv/TvInputInfo.java b/media/java/android/media/tv/TvInputInfo.java
index 5cb8fb8..30907a5 100644
--- a/media/java/android/media/tv/TvInputInfo.java
+++ b/media/java/android/media/tv/TvInputInfo.java
@@ -33,7 +33,10 @@
 import android.content.res.XmlResourceParser;
 import android.graphics.drawable.Drawable;
 import android.graphics.drawable.Icon;
+import android.hardware.hdmi.HdmiControlManager;
 import android.hardware.hdmi.HdmiDeviceInfo;
+import android.hardware.hdmi.HdmiUtils;
+import android.hardware.hdmi.HdmiUtils.HdmiAddressRelativePosition;
 import android.net.Uri;
 import android.os.Bundle;
 import android.os.Parcel;
@@ -49,7 +52,6 @@
 import org.xmlpull.v1.XmlPullParser;
 import org.xmlpull.v1.XmlPullParserException;
 
-import java.io.FileNotFoundException;
 import java.io.IOException;
 import java.io.InputStream;
 import java.lang.annotation.Retention;
@@ -145,6 +147,8 @@
     // Attributes specific to HDMI
     private final HdmiDeviceInfo mHdmiDeviceInfo;
     private final boolean mIsConnectedToHdmiSwitch;
+    @HdmiAddressRelativePosition
+    private final int mHdmiConnectionRelativePosition;
     private final String mParentId;
 
     private final Bundle mExtras;
@@ -260,7 +264,9 @@
     private TvInputInfo(ResolveInfo service, String id, int type, boolean isHardwareInput,
             CharSequence label, int labelResId, Icon icon, Icon iconStandby, Icon iconDisconnected,
             String setupActivity, boolean canRecord, int tunerCount, HdmiDeviceInfo hdmiDeviceInfo,
-            boolean isConnectedToHdmiSwitch, String parentId, Bundle extras) {
+            boolean isConnectedToHdmiSwitch,
+            @HdmiAddressRelativePosition int hdmiConnectionRelativePosition, String parentId,
+            Bundle extras) {
         mService = service;
         mId = id;
         mType = type;
@@ -275,6 +281,7 @@
         mTunerCount = tunerCount;
         mHdmiDeviceInfo = hdmiDeviceInfo;
         mIsConnectedToHdmiSwitch = isConnectedToHdmiSwitch;
+        mHdmiConnectionRelativePosition = hdmiConnectionRelativePosition;
         mParentId = parentId;
         mExtras = extras;
     }
@@ -419,6 +426,7 @@
     /**
      * Returns {@code true}, if a CEC device for this TV input is connected to an HDMI switch, i.e.,
      * the device isn't directly connected to a HDMI port.
+     * TODO(b/110094868): add @Deprecated for Q
      * @hide
      */
     @SystemApi
@@ -427,6 +435,16 @@
     }
 
     /**
+     * Returns the relative position of this HDMI input.
+     * TODO(b/110094868): unhide for Q
+     * @hide
+     */
+    @HdmiAddressRelativePosition
+    public int getHdmiConnectionRelativePosition() {
+        return mHdmiConnectionRelativePosition;
+    }
+
+    /**
      * Checks if this TV input is marked hidden by the user in the settings.
      *
      * @param context Supplies a {@link Context} used to check if this TV input is hidden.
@@ -555,6 +573,7 @@
                 && mTunerCount == obj.mTunerCount
                 && Objects.equals(mHdmiDeviceInfo, obj.mHdmiDeviceInfo)
                 && mIsConnectedToHdmiSwitch == obj.mIsConnectedToHdmiSwitch
+                && mHdmiConnectionRelativePosition == obj.mHdmiConnectionRelativePosition
                 && TextUtils.equals(mParentId, obj.mParentId)
                 && Objects.equals(mExtras, obj.mExtras);
     }
@@ -589,6 +608,7 @@
         dest.writeInt(mTunerCount);
         dest.writeParcelable(mHdmiDeviceInfo, flags);
         dest.writeByte(mIsConnectedToHdmiSwitch ? (byte) 1 : 0);
+        dest.writeInt(mHdmiConnectionRelativePosition);
         dest.writeString(mParentId);
         dest.writeBundle(mExtras);
     }
@@ -630,6 +650,7 @@
         mTunerCount = in.readInt();
         mHdmiDeviceInfo = in.readParcelable(null);
         mIsConnectedToHdmiSwitch = in.readByte() == 1;
+        mHdmiConnectionRelativePosition = in.readInt();
         mParentId = in.readString();
         mExtras = in.readBundle();
     }
@@ -883,12 +904,17 @@
             int type;
             boolean isHardwareInput = false;
             boolean isConnectedToHdmiSwitch = false;
+            @HdmiAddressRelativePosition
+            int hdmiConnectionRelativePosition = HdmiUtils.HDMI_RELATIVE_POSITION_UNKNOWN;
 
             if (mHdmiDeviceInfo != null) {
                 id = generateInputId(componentName, mHdmiDeviceInfo);
                 type = TYPE_HDMI;
                 isHardwareInput = true;
-                isConnectedToHdmiSwitch = (mHdmiDeviceInfo.getPhysicalAddress() & 0x0FFF) != 0;
+                hdmiConnectionRelativePosition = getRelativePosition(mContext, mHdmiDeviceInfo);
+                isConnectedToHdmiSwitch =
+                        hdmiConnectionRelativePosition
+                                != HdmiUtils.HDMI_RELATIVE_POSITION_DIRECTLY_BELOW;
             } else if (mTvInputHardwareInfo != null) {
                 id = generateInputId(componentName, mTvInputHardwareInfo);
                 type = sHardwareTypeToTvInputType.get(mTvInputHardwareInfo.getType(), TYPE_TUNER);
@@ -901,7 +927,8 @@
             return new TvInputInfo(mResolveInfo, id, type, isHardwareInput, mLabel, mLabelResId,
                     mIcon, mIconStandby, mIconDisconnected, mSetupActivity,
                     mCanRecord == null ? false : mCanRecord, mTunerCount == null ? 0 : mTunerCount,
-                    mHdmiDeviceInfo, isConnectedToHdmiSwitch, mParentId, mExtras);
+                    mHdmiDeviceInfo, isConnectedToHdmiSwitch, hdmiConnectionRelativePosition,
+                    mParentId, mExtras);
         }
 
         private static String generateInputId(ComponentName name) {
@@ -923,6 +950,16 @@
                     + tvInputHardwareInfo.getDeviceId();
         }
 
+        private static int getRelativePosition(Context context, HdmiDeviceInfo info) {
+            HdmiControlManager hcm =
+                    (HdmiControlManager) context.getSystemService(Context.HDMI_CONTROL_SERVICE);
+            if (hcm == null) {
+                return HdmiUtils.HDMI_RELATIVE_POSITION_UNKNOWN;
+            }
+            return HdmiUtils.getHdmiAddressRelativePosition(
+                    info.getPhysicalAddress(), hcm.getPhysicalAddress());
+        }
+
         private void parseServiceMetadata(int inputType) {
             ServiceInfo si = mResolveInfo.serviceInfo;
             PackageManager pm = mContext.getPackageManager();
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_MediaDrm.cpp b/media/jni/android_media_MediaDrm.cpp
index 42c5b05..81fce8a 100644
--- a/media/jni/android_media_MediaDrm.cpp
+++ b/media/jni/android_media_MediaDrm.cpp
@@ -542,14 +542,15 @@
 
 
 // static
-bool JDrm::IsCryptoSchemeSupported(const uint8_t uuid[16], const String8 &mimeType) {
+bool JDrm::IsCryptoSchemeSupported(const uint8_t uuid[16], const String8 &mimeType,
+                                   DrmPlugin::SecurityLevel securityLevel) {
     sp<IDrm> drm = MakeDrm();
 
     if (drm == NULL) {
         return false;
     }
 
-    return drm->isCryptoSchemeSupported(uuid, mimeType);
+    return drm->isCryptoSchemeSupported(uuid, mimeType, securityLevel);
 }
 
 status_t JDrm::initCheck() const {
@@ -930,8 +931,30 @@
     setDrm(env, thiz, drm);
 }
 
+DrmPlugin::SecurityLevel jintToSecurityLevel(jint jlevel) {
+    DrmPlugin::SecurityLevel level;
+
+    if (jlevel == gSecurityLevels.kSecurityLevelMax) {
+        level = DrmPlugin::kSecurityLevelMax;
+    }  else if (jlevel == gSecurityLevels.kSecurityLevelSwSecureCrypto) {
+        level = DrmPlugin::kSecurityLevelSwSecureCrypto;
+    } else if (jlevel == gSecurityLevels.kSecurityLevelSwSecureDecode) {
+        level = DrmPlugin::kSecurityLevelSwSecureDecode;
+    } else if (jlevel == gSecurityLevels.kSecurityLevelHwSecureCrypto) {
+        level = DrmPlugin::kSecurityLevelHwSecureCrypto;
+    } else if (jlevel == gSecurityLevels.kSecurityLevelHwSecureDecode) {
+        level = DrmPlugin::kSecurityLevelHwSecureDecode;
+    } else if (jlevel == gSecurityLevels.kSecurityLevelHwSecureAll) {
+        level = DrmPlugin::kSecurityLevelHwSecureAll;
+    } else {
+        level = DrmPlugin::kSecurityLevelUnknown;
+    }
+    return level;
+}
+
 static jboolean android_media_MediaDrm_isCryptoSchemeSupportedNative(
-    JNIEnv *env, jobject /* thiz */, jbyteArray uuidObj, jstring jmimeType) {
+        JNIEnv *env, jobject /* thiz */, jbyteArray uuidObj, jstring jmimeType,
+        jint jSecurityLevel) {
 
     if (uuidObj == NULL) {
         jniThrowException(env, "java/lang/IllegalArgumentException", NULL);
@@ -952,8 +975,9 @@
     if (jmimeType != NULL) {
         mimeType = JStringToString8(env, jmimeType);
     }
+    DrmPlugin::SecurityLevel securityLevel = jintToSecurityLevel(jSecurityLevel);
 
-    return JDrm::IsCryptoSchemeSupported(uuid.array(), mimeType);
+    return JDrm::IsCryptoSchemeSupported(uuid.array(), mimeType, securityLevel);
 }
 
 static jbyteArray android_media_MediaDrm_openSession(
@@ -965,21 +989,8 @@
     }
 
     Vector<uint8_t> sessionId;
-    DrmPlugin::SecurityLevel level;
-
-    if (jlevel == gSecurityLevels.kSecurityLevelMax) {
-        level = DrmPlugin::kSecurityLevelMax;
-    }  else if (jlevel == gSecurityLevels.kSecurityLevelSwSecureCrypto) {
-        level = DrmPlugin::kSecurityLevelSwSecureCrypto;
-    } else if (jlevel == gSecurityLevels.kSecurityLevelSwSecureDecode) {
-        level = DrmPlugin::kSecurityLevelSwSecureDecode;
-    } else if (jlevel == gSecurityLevels.kSecurityLevelHwSecureCrypto) {
-        level = DrmPlugin::kSecurityLevelHwSecureCrypto;
-    } else if (jlevel == gSecurityLevels.kSecurityLevelHwSecureDecode) {
-        level = DrmPlugin::kSecurityLevelHwSecureDecode;
-    } else if (jlevel == gSecurityLevels.kSecurityLevelHwSecureAll) {
-        level = DrmPlugin::kSecurityLevelHwSecureAll;
-    } else {
+    DrmPlugin::SecurityLevel level = jintToSecurityLevel(jlevel);
+    if (level == DrmPlugin::kSecurityLevelUnknown) {
         jniThrowException(env, "java/lang/IllegalArgumentException", "Invalid security level");
         return NULL;
     }
@@ -1903,7 +1914,7 @@
     { "native_setup", "(Ljava/lang/Object;[BLjava/lang/String;)V",
       (void *)android_media_MediaDrm_native_setup },
 
-    { "isCryptoSchemeSupportedNative", "([BLjava/lang/String;)Z",
+    { "isCryptoSchemeSupportedNative", "([BLjava/lang/String;I)Z",
       (void *)android_media_MediaDrm_isCryptoSchemeSupportedNative },
 
     { "openSession", "(I)[B",
diff --git a/media/jni/android_media_MediaDrm.h b/media/jni/android_media_MediaDrm.h
index b9356f3..9338861 100644
--- a/media/jni/android_media_MediaDrm.h
+++ b/media/jni/android_media_MediaDrm.h
@@ -37,7 +37,9 @@
 };
 
 struct JDrm : public BnDrmClient {
-    static bool IsCryptoSchemeSupported(const uint8_t uuid[16], const String8 &mimeType);
+    static bool IsCryptoSchemeSupported(const uint8_t uuid[16],
+                                        const String8 &mimeType,
+                                        DrmPlugin::SecurityLevel level);
 
     JDrm(JNIEnv *env, jobject thiz, const uint8_t uuid[16], const String8 &appPackageName);
 
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/Android.bp b/native/android/Android.bp
index 73d4c45..7c1af4a 100644
--- a/native/android/Android.bp
+++ b/native/android/Android.bp
@@ -56,6 +56,7 @@
 
     shared_libs: [
         "liblog",
+        "libhidlbase",
         "libcutils",
         "libandroidfw",
         "libinput",
@@ -70,6 +71,8 @@
         "libnetd_client",
         "libhwui",
         "libxml2",
+        "android.hardware.configstore@1.0",
+        "android.hardware.configstore-utils",
     ],
 
     static_libs: [
diff --git a/native/android/libandroid.map.txt b/native/android/libandroid.map.txt
index 8be8eda..51afbc7 100644
--- a/native/android/libandroid.map.txt
+++ b/native/android/libandroid.map.txt
@@ -172,6 +172,7 @@
     ASensorEventQueue_hasEvents;
     ASensorEventQueue_registerSensor; # introduced=26
     ASensorEventQueue_setEventRate;
+    ASensorEventQueue_requestAdditionalInfoEvents; # introduced=29
     ASensorManager_configureDirectReport; # introduced=26
     ASensorManager_createEventQueue;
     ASensorManager_createHardwareBufferDirectChannel; # introduced=26
@@ -185,6 +186,7 @@
     ASensorManager_getSensorList;
     ASensor_getFifoMaxEventCount; # introduced=21
     ASensor_getFifoReservedEventCount; # introduced=21
+    ASensor_getHandle; # introduced=29
     ASensor_getHighestDirectReportRateLevel; # introduced=26
     ASensor_getMinDelay;
     ASensor_getName;
@@ -207,7 +209,7 @@
     AStorageManager_unmountObb;
     ASurfaceControl_create; # introduced=29
     ASurfaceControl_createFromWindow; # introduced=29
-    ASurfaceControl_destroy; # introduced=29
+    ASurfaceControl_release; # introduced=29
     ASurfaceTexture_acquireANativeWindow; # introduced=28
     ASurfaceTexture_attachToGLContext; # introduced=28
     ASurfaceTexture_detachFromGLContext; # introduced=28
@@ -216,13 +218,25 @@
     ASurfaceTexture_getTransformMatrix; # introduced=28
     ASurfaceTexture_release; # introduced=28
     ASurfaceTexture_updateTexImage; # introduced=28
+    ASurfaceTransactionStats_getAcquireTime; # introduced=29
+    ASurfaceTransactionStats_getASurfaceControls; # introduced=29
+    ASurfaceTransactionStats_getLatchTime; # introduced=29
+    ASurfaceTransactionStats_getPresentFenceFd; # introduced=29
+    ASurfaceTransactionStats_getPreviousReleaseFenceFd; # introduced=29
+    ASurfaceTransactionStats_releaseASurfaceControls; # introduced=29
     ASurfaceTransaction_apply; # introduced=29
     ASurfaceTransaction_create; # introduced=29
     ASurfaceTransaction_delete; # introduced=29
+    ASurfaceTransaction_reparent; # introduced=29
     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
+    ASurfaceTransaction_setHdrMetadata_cta861_3; # introduced=29
+    ASurfaceTransaction_setHdrMetadata_smpte2086; # introduced=29
     ASurfaceTransaction_setOnComplete; # introduced=29
     ASurfaceTransaction_setVisibility; # introduced=29
     ASurfaceTransaction_setZOrder; # introduced=29
diff --git a/native/android/sensor.cpp b/native/android/sensor.cpp
index c3b2e25..63082fd 100644
--- a/native/android/sensor.cpp
+++ b/native/android/sensor.cpp
@@ -115,6 +115,7 @@
     if (queue != 0) {
         ALooper_addFd(looper, queue->getFd(), ident, ALOOPER_EVENT_INPUT, callback, data);
         queue->looper = looper;
+        queue->requestAdditionalInfo = false;
         queue->incStrong(manager);
     }
     return static_cast<ASensorEventQueue*>(queue.get());
@@ -274,11 +275,19 @@
         return android::BAD_VALUE;
     }
 
-    ssize_t actual = static_cast<SensorEventQueue*>(queue)->read(events, count);
+    SensorEventQueue* sensorQueue = static_cast<SensorEventQueue*>(queue);
+    ssize_t actual = sensorQueue->read(events, count);
     if (actual > 0) {
-        static_cast<SensorEventQueue*>(queue)->sendAck(events, actual);
+        sensorQueue->sendAck(events, actual);
     }
-    return actual;
+
+    return sensorQueue->filterEvents(events, actual);
+}
+
+int ASensorEventQueue_requestAdditionalInfoEvents(ASensorEventQueue* queue, bool enable) {
+    RETURN_IF_QUEUE_IS_NULL(android::BAD_VALUE);
+    queue->requestAdditionalInfo = enable;
+    return android::OK;
 }
 
 /*****************************************************************************/
diff --git a/native/android/surface_control.cpp b/native/android/surface_control.cpp
index ead5b0b..5fae9d5 100644
--- a/native/android/surface_control.cpp
+++ b/native/android/surface_control.cpp
@@ -14,14 +14,26 @@
  * limitations under the License.
  */
 
+#include <android/hardware/configstore/1.0/ISurfaceFlingerConfigs.h>
 #include <android/native_window.h>
 #include <android/surface_control.h>
 
+#include <configstore/Utils.h>
+
+#include <gui/HdrMetadata.h>
+#include <gui/ISurfaceComposer.h>
 #include <gui/Surface.h>
 #include <gui/SurfaceComposerClient.h>
 #include <gui/SurfaceControl.h>
 
+#include <ui/HdrCapabilities.h>
+
+#include <utils/Timers.h>
+
+using namespace android::hardware::configstore;
+using namespace android::hardware::configstore::V1_0;
 using namespace android;
+using android::hardware::configstore::V1_0::ISurfaceFlingerConfigs;
 
 using Transaction = SurfaceComposerClient::Transaction;
 
@@ -32,6 +44,76 @@
     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));
+
+    Vector<ui::ColorMode> colorModes;
+    status_t err = client->getDisplayColorModes(display, &colorModes);
+    if (err) {
+        ALOGE("unable to get wide color support");
+        return false;
+    }
+
+    bool wideColorBoardConfig =
+        getBool<ISurfaceFlingerConfigs,
+                &ISurfaceFlingerConfigs::hasWideColorDisplay>(false);
+
+    for (android::ui::ColorMode colorMode : colorModes) {
+        switch (colorMode) {
+            case ui::ColorMode::DISPLAY_P3:
+            case ui::ColorMode::ADOBE_RGB:
+            case ui::ColorMode::DCI_P3:
+                if (wideColorBoardConfig) {
+                    return true;
+                }
+                break;
+            default:
+                break;
+        }
+    }
+    return false;
+}
+
+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);
 }
@@ -95,10 +177,9 @@
     return reinterpret_cast<ASurfaceControl*>(surfaceControl.get());
 }
 
-void ASurfaceControl_destroy(ASurfaceControl* aSurfaceControl) {
+void ASurfaceControl_release(ASurfaceControl* aSurfaceControl) {
     sp<SurfaceControl> surfaceControl = ASurfaceControl_to_SurfaceControl(aSurfaceControl);
 
-    Transaction().reparent(surfaceControl, nullptr).apply();
     SurfaceControl_release(surfaceControl.get());
 }
 
@@ -120,6 +201,86 @@
     transaction->apply();
 }
 
+typedef struct ASurfaceControlStats {
+    int64_t acquireTime;
+    sp<Fence> previousReleaseFence;
+} ASurfaceControlStats;
+
+struct ASurfaceTransactionStats {
+    std::unordered_map<ASurfaceControl*, ASurfaceControlStats> aSurfaceControlStats;
+    int64_t latchTime;
+    sp<Fence> presentFence;
+};
+
+int64_t ASurfaceTransactionStats_getLatchTime(ASurfaceTransactionStats* aSurfaceTransactionStats) {
+    CHECK_NOT_NULL(aSurfaceTransactionStats);
+    return aSurfaceTransactionStats->latchTime;
+}
+
+int ASurfaceTransactionStats_getPresentFenceFd(ASurfaceTransactionStats* aSurfaceTransactionStats) {
+    CHECK_NOT_NULL(aSurfaceTransactionStats);
+    auto& presentFence = aSurfaceTransactionStats->presentFence;
+    return (presentFence) ? presentFence->dup() : -1;
+}
+
+void ASurfaceTransactionStats_getASurfaceControls(ASurfaceTransactionStats* aSurfaceTransactionStats,
+                                                  ASurfaceControl*** outASurfaceControls,
+                                                  size_t* outASurfaceControlsSize) {
+    CHECK_NOT_NULL(aSurfaceTransactionStats);
+    CHECK_NOT_NULL(outASurfaceControls);
+    CHECK_NOT_NULL(outASurfaceControlsSize);
+
+    size_t size = aSurfaceTransactionStats->aSurfaceControlStats.size();
+
+    SurfaceControl** surfaceControls = new SurfaceControl*[size];
+    ASurfaceControl** aSurfaceControls = reinterpret_cast<ASurfaceControl**>(surfaceControls);
+
+    size_t i = 0;
+    for (auto& [aSurfaceControl, aSurfaceControlStats] : aSurfaceTransactionStats->aSurfaceControlStats) {
+        aSurfaceControls[i] = aSurfaceControl;
+        i++;
+    }
+
+    *outASurfaceControls = aSurfaceControls;
+    *outASurfaceControlsSize = size;
+}
+
+int64_t ASurfaceTransactionStats_getAcquireTime(ASurfaceTransactionStats* aSurfaceTransactionStats,
+                                                ASurfaceControl* aSurfaceControl) {
+    CHECK_NOT_NULL(aSurfaceTransactionStats);
+    CHECK_NOT_NULL(aSurfaceControl);
+
+    const auto& aSurfaceControlStats =
+            aSurfaceTransactionStats->aSurfaceControlStats.find(aSurfaceControl);
+    LOG_ALWAYS_FATAL_IF(
+            aSurfaceControlStats == aSurfaceTransactionStats->aSurfaceControlStats.end(),
+            "ASurfaceControl not found");
+
+    return aSurfaceControlStats->second.acquireTime;
+}
+
+int ASurfaceTransactionStats_getPreviousReleaseFenceFd(
+            ASurfaceTransactionStats* aSurfaceTransactionStats, ASurfaceControl* aSurfaceControl) {
+    CHECK_NOT_NULL(aSurfaceTransactionStats);
+    CHECK_NOT_NULL(aSurfaceControl);
+
+    const auto& aSurfaceControlStats =
+            aSurfaceTransactionStats->aSurfaceControlStats.find(aSurfaceControl);
+    LOG_ALWAYS_FATAL_IF(
+            aSurfaceControlStats == aSurfaceTransactionStats->aSurfaceControlStats.end(),
+            "ASurfaceControl not found");
+
+    auto& previousReleaseFence = aSurfaceControlStats->second.previousReleaseFence;
+    return (previousReleaseFence) ? previousReleaseFence->dup() : -1;
+}
+
+void ASurfaceTransactionStats_releaseASurfaceControls(ASurfaceControl** aSurfaceControls) {
+    CHECK_NOT_NULL(aSurfaceControls);
+
+    SurfaceControl** surfaceControls = reinterpret_cast<SurfaceControl**>(aSurfaceControls);
+    delete[] surfaceControls;
+}
+
 void ASurfaceTransaction_setOnComplete(ASurfaceTransaction* aSurfaceTransaction, void* context,
                                        ASurfaceTransaction_OnComplete func) {
     CHECK_NOT_NULL(aSurfaceTransaction);
@@ -127,9 +288,23 @@
     CHECK_NOT_NULL(func);
 
     TransactionCompletedCallbackTakesContext callback = [func](void* callback_context,
-                                                               const TransactionStats& stats) {
-        int fence = (stats.presentFence) ? stats.presentFence->dup() : -1;
-        (*func)(callback_context, fence);
+                                                               nsecs_t latchTime,
+                                                               const sp<Fence>& presentFence,
+                                                               const std::vector<SurfaceControlStats>& surfaceControlStats) {
+        ASurfaceTransactionStats aSurfaceTransactionStats;
+
+        aSurfaceTransactionStats.latchTime = latchTime;
+        aSurfaceTransactionStats.presentFence = presentFence;
+
+        auto& aSurfaceControlStats = aSurfaceTransactionStats.aSurfaceControlStats;
+
+        for (const auto& [surfaceControl, acquireTime, previousReleaseFence] : surfaceControlStats) {
+            ASurfaceControl* aSurfaceControl = reinterpret_cast<ASurfaceControl*>(surfaceControl.get());
+            aSurfaceControlStats[aSurfaceControl].acquireTime = acquireTime;
+            aSurfaceControlStats[aSurfaceControl].previousReleaseFence = previousReleaseFence;
+        }
+
+        (*func)(callback_context, &aSurfaceTransactionStats);
     };
 
     Transaction* transaction = ASurfaceTransaction_to_Transaction(aSurfaceTransaction);
@@ -137,7 +312,23 @@
     transaction->addTransactionCompletedCallback(callback, context);
 }
 
-void ASurfaceTransaction_setVisibility(ASurfaceTransaction* aSurfaceTransaction, ASurfaceControl* aSurfaceControl,
+void ASurfaceTransaction_reparent(ASurfaceTransaction* aSurfaceTransaction,
+                                  ASurfaceControl* aSurfaceControl,
+                                  ASurfaceControl* newParentASurfaceControl) {
+    CHECK_NOT_NULL(aSurfaceTransaction);
+    CHECK_NOT_NULL(aSurfaceControl);
+
+    sp<SurfaceControl> surfaceControl = ASurfaceControl_to_SurfaceControl(aSurfaceControl);
+    sp<SurfaceControl> newParentSurfaceControl = ASurfaceControl_to_SurfaceControl(
+            newParentASurfaceControl);
+    sp<IBinder> newParentHandle = (newParentSurfaceControl)? newParentSurfaceControl->getHandle() : nullptr;
+    Transaction* transaction = ASurfaceTransaction_to_Transaction(aSurfaceTransaction);
+
+    transaction->reparent(surfaceControl, newParentHandle);
+}
+
+void ASurfaceTransaction_setVisibility(ASurfaceTransaction* aSurfaceTransaction,
+                                       ASurfaceControl* aSurfaceControl,
                                        int8_t visibility) {
     CHECK_NOT_NULL(aSurfaceTransaction);
     CHECK_NOT_NULL(aSurfaceControl);
@@ -157,7 +348,8 @@
     }
 }
 
-void ASurfaceTransaction_setZOrder(ASurfaceTransaction* aSurfaceTransaction, ASurfaceControl* aSurfaceControl,
+void ASurfaceTransaction_setZOrder(ASurfaceTransaction* aSurfaceTransaction,
+                                   ASurfaceControl* aSurfaceControl,
                                    int32_t z_order) {
     CHECK_NOT_NULL(aSurfaceTransaction);
     CHECK_NOT_NULL(aSurfaceControl);
@@ -168,8 +360,9 @@
     transaction->setLayer(surfaceControl, z_order);
 }
 
-void ASurfaceTransaction_setBuffer(ASurfaceTransaction* aSurfaceTransaction, ASurfaceControl* aSurfaceControl,
-                                   AHardwareBuffer* buffer, int fence_fd) {
+void ASurfaceTransaction_setBuffer(ASurfaceTransaction* aSurfaceTransaction,
+                                   ASurfaceControl* aSurfaceControl,
+                                   AHardwareBuffer* buffer, int acquire_fence_fd) {
     CHECK_NOT_NULL(aSurfaceTransaction);
     CHECK_NOT_NULL(aSurfaceControl);
 
@@ -179,8 +372,8 @@
     sp<GraphicBuffer> graphic_buffer(reinterpret_cast<GraphicBuffer*>(buffer));
 
     transaction->setBuffer(surfaceControl, graphic_buffer);
-    if (fence_fd != -1) {
-        sp<Fence> fence = new Fence(fence_fd);
+    if (acquire_fence_fd != -1) {
+        sp<Fence> fence = new Fence(acquire_fence_fd);
         transaction->setAcquireFence(surfaceControl, fence);
     }
 }
@@ -215,7 +408,8 @@
     transaction->setFlags(surfaceControl, flags, layer_state_t::eLayerOpaque);
 }
 
-void ASurfaceTransaction_setDamageRegion(ASurfaceTransaction* aSurfaceTransaction, ASurfaceControl* aSurfaceControl,
+void ASurfaceTransaction_setDamageRegion(ASurfaceTransaction* aSurfaceTransaction,
+                                         ASurfaceControl* aSurfaceControl,
                                          const ARect rects[], uint32_t count) {
     CHECK_NOT_NULL(aSurfaceTransaction);
     CHECK_NOT_NULL(aSurfaceControl);
@@ -230,3 +424,101 @@
 
     transaction->setSurfaceDamageRegion(surfaceControl, region);
 }
+
+void ASurfaceTransaction_setDesiredPresentTime(ASurfaceTransaction* aSurfaceTransaction,
+                                         int64_t desiredPresentTime) {
+    CHECK_NOT_NULL(aSurfaceTransaction);
+
+    Transaction* transaction = ASurfaceTransaction_to_Transaction(aSurfaceTransaction);
+
+    transaction->setDesiredPresentTime(static_cast<nsecs_t>(desiredPresentTime));
+}
+
+void ASurfaceTransaction_setBufferAlpha(ASurfaceTransaction* aSurfaceTransaction,
+                                         ASurfaceControl* aSurfaceControl,
+                                         float alpha) {
+    CHECK_NOT_NULL(aSurfaceTransaction);
+    CHECK_NOT_NULL(aSurfaceControl);
+
+    LOG_ALWAYS_FATAL_IF(alpha < 0.0 || alpha > 1.0, "invalid alpha");
+
+    sp<SurfaceControl> surfaceControl = ASurfaceControl_to_SurfaceControl(aSurfaceControl);
+    Transaction* transaction = ASurfaceTransaction_to_Transaction(aSurfaceTransaction);
+
+    transaction->setAlpha(surfaceControl, alpha);
+}
+
+void ASurfaceTransaction_setHdrMetadata_smpte2086(ASurfaceTransaction* aSurfaceTransaction,
+                                                  ASurfaceControl* aSurfaceControl,
+                                                  struct AHdrMetadata_smpte2086* metadata) {
+    CHECK_NOT_NULL(aSurfaceTransaction);
+    CHECK_NOT_NULL(aSurfaceControl);
+
+    sp<SurfaceControl> surfaceControl = ASurfaceControl_to_SurfaceControl(aSurfaceControl);
+    Transaction* transaction = ASurfaceTransaction_to_Transaction(aSurfaceTransaction);
+
+    HdrMetadata hdrMetadata;
+
+    if (metadata) {
+        hdrMetadata.smpte2086.displayPrimaryRed.x = metadata->displayPrimaryRed.x;
+        hdrMetadata.smpte2086.displayPrimaryRed.y = metadata->displayPrimaryRed.y;
+        hdrMetadata.smpte2086.displayPrimaryGreen.x = metadata->displayPrimaryGreen.x;
+        hdrMetadata.smpte2086.displayPrimaryGreen.y = metadata->displayPrimaryGreen.y;
+        hdrMetadata.smpte2086.displayPrimaryBlue.x = metadata->displayPrimaryBlue.x;
+        hdrMetadata.smpte2086.displayPrimaryBlue.y = metadata->displayPrimaryBlue.y;
+        hdrMetadata.smpte2086.whitePoint.x = metadata->whitePoint.x;
+        hdrMetadata.smpte2086.whitePoint.y = metadata->whitePoint.y;
+        hdrMetadata.smpte2086.minLuminance = metadata->minLuminance;
+        hdrMetadata.smpte2086.maxLuminance = metadata->maxLuminance;
+
+        hdrMetadata.validTypes |= HdrMetadata::SMPTE2086;
+    } else {
+        hdrMetadata.validTypes &= ~HdrMetadata::SMPTE2086;
+    }
+
+    transaction->setHdrMetadata(surfaceControl, hdrMetadata);
+}
+
+void ASurfaceTransaction_setHdrMetadata_cta861_3(ASurfaceTransaction* aSurfaceTransaction,
+                                                 ASurfaceControl* aSurfaceControl,
+                                                 struct AHdrMetadata_cta861_3* metadata) {
+    CHECK_NOT_NULL(aSurfaceTransaction);
+    CHECK_NOT_NULL(aSurfaceControl);
+
+    sp<SurfaceControl> surfaceControl = ASurfaceControl_to_SurfaceControl(aSurfaceControl);
+    Transaction* transaction = ASurfaceTransaction_to_Transaction(aSurfaceTransaction);
+
+    HdrMetadata hdrMetadata;
+
+    if (metadata) {
+        hdrMetadata.cta8613.maxContentLightLevel = metadata->maxContentLightLevel;
+        hdrMetadata.cta8613.maxFrameAverageLightLevel = metadata->maxFrameAverageLightLevel;
+
+        hdrMetadata.validTypes |= HdrMetadata::CTA861_3;
+    } else {
+        hdrMetadata.validTypes &= ~HdrMetadata::CTA861_3;
+    }
+
+    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/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/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/ExternalStorageProvider/src/com/android/externalstorage/ExternalStorageProvider.java b/packages/ExternalStorageProvider/src/com/android/externalstorage/ExternalStorageProvider.java
index 8d04702..da3416b 100644
--- a/packages/ExternalStorageProvider/src/com/android/externalstorage/ExternalStorageProvider.java
+++ b/packages/ExternalStorageProvider/src/com/android/externalstorage/ExternalStorageProvider.java
@@ -76,7 +76,7 @@
 
     private static final String[] DEFAULT_ROOT_PROJECTION = new String[] {
             Root.COLUMN_ROOT_ID, Root.COLUMN_FLAGS, Root.COLUMN_ICON, Root.COLUMN_TITLE,
-            Root.COLUMN_DOCUMENT_ID, Root.COLUMN_AVAILABLE_BYTES,
+            Root.COLUMN_DOCUMENT_ID, Root.COLUMN_AVAILABLE_BYTES, Root.COLUMN_QUERY_ARGS
     };
 
     private static final String[] DEFAULT_DOCUMENT_PROJECTION = new String[] {
@@ -444,6 +444,7 @@
                 row.add(Root.COLUMN_FLAGS, root.flags);
                 row.add(Root.COLUMN_TITLE, root.title);
                 row.add(Root.COLUMN_DOCUMENT_ID, root.docId);
+                row.add(Root.COLUMN_QUERY_ARGS, SUPPORTED_QUERY_ARGS);
 
                 long availableBytes = -1;
                 if (root.reportAvailableBytes) {
diff --git a/packages/NetworkStack/Android.bp b/packages/NetworkStack/Android.bp
index 2f7d599..a2da0a0 100644
--- a/packages/NetworkStack/Android.bp
+++ b/packages/NetworkStack/Android.bp
@@ -24,7 +24,7 @@
         ":services-networkstack-shared-srcs",
     ],
     static_libs: [
-        "dhcp-packet-lib",
+        "services-netlink-lib",
     ]
 }
 
diff --git a/packages/NetworkStack/AndroidManifest.xml b/packages/NetworkStack/AndroidManifest.xml
index 7f8bb93..5ab833b 100644
--- a/packages/NetworkStack/AndroidManifest.xml
+++ b/packages/NetworkStack/AndroidManifest.xml
@@ -28,6 +28,7 @@
     <!-- Launch captive portal app as specific user -->
     <uses-permission android:name="android.permission.INTERACT_ACROSS_USERS_FULL" />
     <uses-permission android:name="android.permission.NETWORK_STACK" />
+    <uses-permission android:name="android.permission.WAKE_LOCK" />
     <application
         android:label="NetworkStack"
         android:defaultToDeviceProtectedStorage="true"
diff --git a/services/net/java/android/net/apf/ApfFilter.java b/packages/NetworkStack/src/android/net/apf/ApfFilter.java
similarity index 98%
rename from services/net/java/android/net/apf/ApfFilter.java
rename to packages/NetworkStack/src/android/net/apf/ApfFilter.java
index 4943952..50c4dfc 100644
--- a/services/net/java/android/net/apf/ApfFilter.java
+++ b/packages/NetworkStack/src/android/net/apf/ApfFilter.java
@@ -16,10 +16,6 @@
 
 package android.net.apf;
 
-import static android.net.util.NetworkConstants.ICMPV6_ECHO_REQUEST_TYPE;
-import static android.net.util.NetworkConstants.ICMPV6_NEIGHBOR_ADVERTISEMENT;
-import static android.net.util.NetworkConstants.ICMPV6_ROUTER_ADVERTISEMENT;
-import static android.net.util.NetworkConstants.ICMPV6_ROUTER_SOLICITATION;
 import static android.net.util.SocketUtils.makePacketSocketAddress;
 import static android.system.OsConstants.AF_PACKET;
 import static android.system.OsConstants.ARPHRD_ETHER;
@@ -35,6 +31,10 @@
 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;
+import static com.android.server.util.NetworkStackConstants.ICMPV6_ROUTER_SOLICITATION;
 
 import android.annotation.Nullable;
 import android.content.BroadcastReceiver;
@@ -46,7 +46,7 @@
 import android.net.NetworkUtils;
 import android.net.apf.ApfGenerator.IllegalInstructionException;
 import android.net.apf.ApfGenerator.Register;
-import android.net.ip.IpClientCallbacks;
+import android.net.ip.IpClient.IpClientCallbacksWrapper;
 import android.net.metrics.ApfProgramEvent;
 import android.net.metrics.ApfStats;
 import android.net.metrics.IpConnectivityLog;
@@ -337,7 +337,7 @@
     private static final int APF_MAX_ETH_TYPE_BLACK_LIST_LEN = 20;
 
     private final ApfCapabilities mApfCapabilities;
-    private final IpClientCallbacks mIpClientCallback;
+    private final IpClientCallbacksWrapper mIpClientCallback;
     private final InterfaceParams mInterfaceParams;
     private final IpConnectivityLog mMetricsLog;
 
@@ -378,7 +378,7 @@
 
     @VisibleForTesting
     ApfFilter(Context context, ApfConfiguration config, InterfaceParams ifParams,
-            IpClientCallbacks ipClientCallback, IpConnectivityLog log) {
+            IpClientCallbacksWrapper ipClientCallback, IpConnectivityLog log) {
         mApfCapabilities = config.apfCapabilities;
         mIpClientCallback = ipClientCallback;
         mInterfaceParams = ifParams;
@@ -1420,7 +1420,7 @@
      * filtering using APF programs.
      */
     public static ApfFilter maybeCreate(Context context, ApfConfiguration config,
-            InterfaceParams ifParams, IpClientCallbacks ipClientCallback) {
+            InterfaceParams ifParams, IpClientCallbacksWrapper ipClientCallback) {
         if (context == null || config == null || ifParams == null) return null;
         ApfCapabilities apfCapabilities =  config.apfCapabilities;
         if (apfCapabilities == null) return null;
diff --git a/services/net/java/android/net/apf/ApfGenerator.java b/packages/NetworkStack/src/android/net/apf/ApfGenerator.java
similarity index 100%
rename from services/net/java/android/net/apf/ApfGenerator.java
rename to packages/NetworkStack/src/android/net/apf/ApfGenerator.java
diff --git a/services/net/java/android/net/dhcp/DhcpAckPacket.java b/packages/NetworkStack/src/android/net/dhcp/DhcpAckPacket.java
similarity index 100%
rename from services/net/java/android/net/dhcp/DhcpAckPacket.java
rename to packages/NetworkStack/src/android/net/dhcp/DhcpAckPacket.java
diff --git a/services/net/java/android/net/dhcp/DhcpClient.java b/packages/NetworkStack/src/android/net/dhcp/DhcpClient.java
similarity index 100%
rename from services/net/java/android/net/dhcp/DhcpClient.java
rename to packages/NetworkStack/src/android/net/dhcp/DhcpClient.java
diff --git a/services/net/java/android/net/dhcp/DhcpDeclinePacket.java b/packages/NetworkStack/src/android/net/dhcp/DhcpDeclinePacket.java
similarity index 100%
rename from services/net/java/android/net/dhcp/DhcpDeclinePacket.java
rename to packages/NetworkStack/src/android/net/dhcp/DhcpDeclinePacket.java
diff --git a/services/net/java/android/net/dhcp/DhcpDiscoverPacket.java b/packages/NetworkStack/src/android/net/dhcp/DhcpDiscoverPacket.java
similarity index 100%
rename from services/net/java/android/net/dhcp/DhcpDiscoverPacket.java
rename to packages/NetworkStack/src/android/net/dhcp/DhcpDiscoverPacket.java
diff --git a/services/net/java/android/net/dhcp/DhcpInformPacket.java b/packages/NetworkStack/src/android/net/dhcp/DhcpInformPacket.java
similarity index 100%
rename from services/net/java/android/net/dhcp/DhcpInformPacket.java
rename to packages/NetworkStack/src/android/net/dhcp/DhcpInformPacket.java
diff --git a/services/net/java/android/net/dhcp/DhcpNakPacket.java b/packages/NetworkStack/src/android/net/dhcp/DhcpNakPacket.java
similarity index 100%
rename from services/net/java/android/net/dhcp/DhcpNakPacket.java
rename to packages/NetworkStack/src/android/net/dhcp/DhcpNakPacket.java
diff --git a/services/net/java/android/net/dhcp/DhcpOfferPacket.java b/packages/NetworkStack/src/android/net/dhcp/DhcpOfferPacket.java
similarity index 100%
rename from services/net/java/android/net/dhcp/DhcpOfferPacket.java
rename to packages/NetworkStack/src/android/net/dhcp/DhcpOfferPacket.java
diff --git a/services/net/java/android/net/dhcp/DhcpPacket.java b/packages/NetworkStack/src/android/net/dhcp/DhcpPacket.java
similarity index 100%
rename from services/net/java/android/net/dhcp/DhcpPacket.java
rename to packages/NetworkStack/src/android/net/dhcp/DhcpPacket.java
diff --git a/services/net/java/android/net/dhcp/DhcpReleasePacket.java b/packages/NetworkStack/src/android/net/dhcp/DhcpReleasePacket.java
similarity index 100%
rename from services/net/java/android/net/dhcp/DhcpReleasePacket.java
rename to packages/NetworkStack/src/android/net/dhcp/DhcpReleasePacket.java
diff --git a/services/net/java/android/net/dhcp/DhcpRequestPacket.java b/packages/NetworkStack/src/android/net/dhcp/DhcpRequestPacket.java
similarity index 100%
rename from services/net/java/android/net/dhcp/DhcpRequestPacket.java
rename to packages/NetworkStack/src/android/net/dhcp/DhcpRequestPacket.java
diff --git a/services/net/java/android/net/ip/ConnectivityPacketTracker.java b/packages/NetworkStack/src/android/net/ip/ConnectivityPacketTracker.java
similarity index 100%
rename from services/net/java/android/net/ip/ConnectivityPacketTracker.java
rename to packages/NetworkStack/src/android/net/ip/ConnectivityPacketTracker.java
diff --git a/services/net/java/android/net/ip/IpClient.java b/packages/NetworkStack/src/android/net/ip/IpClient.java
similarity index 88%
rename from services/net/java/android/net/ip/IpClient.java
rename to packages/NetworkStack/src/android/net/ip/IpClient.java
index 8187ac5..ad7f85d 100644
--- a/services/net/java/android/net/ip/IpClient.java
+++ b/packages/NetworkStack/src/android/net/ip/IpClient.java
@@ -16,9 +16,14 @@
 
 package android.net.ip;
 
+import static android.net.shared.IpConfigurationParcelableUtil.toStableParcelable;
 import static android.net.shared.LinkPropertiesParcelableUtil.fromStableParcelable;
+import static android.net.shared.LinkPropertiesParcelableUtil.toStableParcelable;
+
+import static com.android.server.util.PermissionUtil.checkNetworkStackCallingPermission;
 
 import android.content.Context;
+import android.net.ConnectivityManager;
 import android.net.DhcpResults;
 import android.net.INetd;
 import android.net.IpPrefix;
@@ -29,16 +34,16 @@
 import android.net.ProxyInfo;
 import android.net.ProxyInfoParcelable;
 import android.net.RouteInfo;
-import android.net.StaticIpConfiguration;
 import android.net.apf.ApfCapabilities;
 import android.net.apf.ApfFilter;
 import android.net.dhcp.DhcpClient;
+import android.net.ip.IIpClientCallbacks;
 import android.net.metrics.IpConnectivityLog;
 import android.net.metrics.IpManagerEvent;
 import android.net.shared.InitialConfiguration;
+import android.net.shared.NetdService;
+import android.net.shared.ProvisioningConfiguration;
 import android.net.util.InterfaceParams;
-import android.net.util.MultinetworkPolicyTracker;
-import android.net.util.NetdService;
 import android.net.util.SharedLog;
 import android.os.ConditionVariable;
 import android.os.INetworkManagementService;
@@ -53,7 +58,6 @@
 
 import com.android.internal.R;
 import com.android.internal.annotations.VisibleForTesting;
-import com.android.internal.util.ArrayUtils;
 import com.android.internal.util.IState;
 import com.android.internal.util.IndentingPrintWriter;
 import com.android.internal.util.MessageUtils;
@@ -69,6 +73,7 @@
 import java.util.Collection;
 import java.util.List;
 import java.util.Objects;
+import java.util.Set;
 import java.util.concurrent.ConcurrentHashMap;
 import java.util.concurrent.CountDownLatch;
 import java.util.function.Predicate;
@@ -101,11 +106,13 @@
     private static final ConcurrentHashMap<String, SharedLog> sSmLogs = new ConcurrentHashMap<>();
     private static final ConcurrentHashMap<String, LocalLog> sPktLogs = new ConcurrentHashMap<>();
 
-    // If |args| is non-empty, assume it's a list of interface names for which
-    // we should print IpClient logs (filter out all others).
-    public static void dumpAllLogs(PrintWriter writer, String[] args) {
+    /**
+     * Dump all state machine and connectivity packet logs to the specified writer.
+     * @param skippedIfaces Interfaces for which logs should not be dumped.
+     */
+    public static void dumpAllLogs(PrintWriter writer, Set<String> skippedIfaces) {
         for (String ifname : sSmLogs.keySet()) {
-            if (!ArrayUtils.isEmpty(args) && !ArrayUtils.contains(args, ifname)) continue;
+            if (skippedIfaces.contains(ifname)) continue;
 
             writer.println(String.format("--- BEGIN %s ---", ifname));
 
@@ -127,19 +134,6 @@
         }
     }
 
-    /**
-     * TODO: remove after migrating clients to use IpClientCallbacks directly
-     * @see IpClientCallbacks
-     */
-    public static class Callback extends IpClientCallbacks {}
-
-    /**
-     * TODO: remove once clients are migrated to IpClientUtil.WaitForProvisioningCallback
-     * @see IpClientUtil.WaitForProvisioningCallbacks
-     */
-    public static class WaitForProvisioningCallback
-            extends IpClientUtil.WaitForProvisioningCallbacks {}
-
     // Use a wrapper class to log in order to ensure complete and detailed
     // logging. This method is lighter weight than annotations/reflection
     // and has the following benefits:
@@ -160,181 +154,134 @@
     // once passed on to the callback they may be modified by another thread.
     //
     // TODO: Find an lighter weight approach.
-    private class LoggingCallbackWrapper extends IpClientCallbacks {
+    public static class IpClientCallbacksWrapper {
         private static final String PREFIX = "INVOKE ";
-        private final IpClientCallbacks mCallback;
+        private final IIpClientCallbacks mCallback;
+        private final SharedLog mLog;
 
-        LoggingCallbackWrapper(IpClientCallbacks callback) {
-            mCallback = (callback != null) ? callback : new IpClientCallbacks();
+        @VisibleForTesting
+        protected IpClientCallbacksWrapper(IIpClientCallbacks callback, SharedLog log) {
+            mCallback = callback;
+            mLog = log;
         }
 
         private void log(String msg) {
             mLog.log(PREFIX + msg);
         }
 
-        @Override
+        private void log(String msg, Throwable e) {
+            mLog.e(PREFIX + msg, e);
+        }
+
         public void onPreDhcpAction() {
             log("onPreDhcpAction()");
-            mCallback.onPreDhcpAction();
+            try {
+                mCallback.onPreDhcpAction();
+            } catch (RemoteException e) {
+                log("Failed to call onPreDhcpAction", e);
+            }
         }
-        @Override
+
         public void onPostDhcpAction() {
             log("onPostDhcpAction()");
-            mCallback.onPostDhcpAction();
+            try {
+                mCallback.onPostDhcpAction();
+            } catch (RemoteException e) {
+                log("Failed to call onPostDhcpAction", e);
+            }
         }
-        @Override
+
         public void onNewDhcpResults(DhcpResults dhcpResults) {
             log("onNewDhcpResults({" + dhcpResults + "})");
-            mCallback.onNewDhcpResults(dhcpResults);
+            try {
+                mCallback.onNewDhcpResults(toStableParcelable(dhcpResults));
+            } catch (RemoteException e) {
+                log("Failed to call onNewDhcpResults", e);
+            }
         }
-        @Override
+
         public void onProvisioningSuccess(LinkProperties newLp) {
             log("onProvisioningSuccess({" + newLp + "})");
-            mCallback.onProvisioningSuccess(newLp);
+            try {
+                mCallback.onProvisioningSuccess(toStableParcelable(newLp));
+            } catch (RemoteException e) {
+                log("Failed to call onProvisioningSuccess", e);
+            }
         }
-        @Override
+
         public void onProvisioningFailure(LinkProperties newLp) {
             log("onProvisioningFailure({" + newLp + "})");
-            mCallback.onProvisioningFailure(newLp);
+            try {
+                mCallback.onProvisioningFailure(toStableParcelable(newLp));
+            } catch (RemoteException e) {
+                log("Failed to call onProvisioningFailure", e);
+            }
         }
-        @Override
+
         public void onLinkPropertiesChange(LinkProperties newLp) {
             log("onLinkPropertiesChange({" + newLp + "})");
-            mCallback.onLinkPropertiesChange(newLp);
+            try {
+                mCallback.onLinkPropertiesChange(toStableParcelable(newLp));
+            } catch (RemoteException e) {
+                log("Failed to call onLinkPropertiesChange", e);
+            }
         }
-        @Override
+
         public void onReachabilityLost(String logMsg) {
             log("onReachabilityLost(" + logMsg + ")");
-            mCallback.onReachabilityLost(logMsg);
+            try {
+                mCallback.onReachabilityLost(logMsg);
+            } catch (RemoteException e) {
+                log("Failed to call onReachabilityLost", e);
+            }
         }
-        @Override
+
         public void onQuit() {
             log("onQuit()");
-            mCallback.onQuit();
+            try {
+                mCallback.onQuit();
+            } catch (RemoteException e) {
+                log("Failed to call onQuit", e);
+            }
         }
-        @Override
+
         public void installPacketFilter(byte[] filter) {
             log("installPacketFilter(byte[" + filter.length + "])");
-            mCallback.installPacketFilter(filter);
+            try {
+                mCallback.installPacketFilter(filter);
+            } catch (RemoteException e) {
+                log("Failed to call installPacketFilter", e);
+            }
         }
-        @Override
+
         public void startReadPacketFilter() {
             log("startReadPacketFilter()");
-            mCallback.startReadPacketFilter();
+            try {
+                mCallback.startReadPacketFilter();
+            } catch (RemoteException e) {
+                log("Failed to call startReadPacketFilter", e);
+            }
         }
-        @Override
+
         public void setFallbackMulticastFilter(boolean enabled) {
             log("setFallbackMulticastFilter(" + enabled + ")");
-            mCallback.setFallbackMulticastFilter(enabled);
+            try {
+                mCallback.setFallbackMulticastFilter(enabled);
+            } catch (RemoteException e) {
+                log("Failed to call setFallbackMulticastFilter", e);
+            }
         }
-        @Override
+
         public void setNeighborDiscoveryOffload(boolean enable) {
             log("setNeighborDiscoveryOffload(" + enable + ")");
-            mCallback.setNeighborDiscoveryOffload(enable);
-        }
-    }
-
-    /**
-     * 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);
+            try {
+                mCallback.setNeighborDiscoveryOffload(enable);
+            } catch (RemoteException e) {
+                log("Failed to call setNeighborDiscoveryOffload", e);
             }
         }
     }
 
-    public static final String DUMP_ARG = "ipclient";
     public static final String DUMP_ARG_CONFIRM = "confirm";
 
     private static final int CMD_TERMINATE_AFTER_STOP             = 1;
@@ -388,9 +335,10 @@
     private final String mInterfaceName;
     private final String mClatInterfaceName;
     @VisibleForTesting
-    protected final IpClientCallbacks mCallback;
+    protected final IpClientCallbacksWrapper mCallback;
     private final Dependencies mDependencies;
     private final CountDownLatch mShutdownLatch;
+    private final ConnectivityManager mCm;
     private final INetworkManagementService mNwService;
     private final NetlinkTracker mNetlinkTracker;
     private final WakeupMessage mProvisioningTimeoutAlarm;
@@ -408,7 +356,6 @@
      */
     private LinkProperties mLinkProperties;
     private android.net.shared.ProvisioningConfiguration mConfiguration;
-    private MultinetworkPolicyTracker mMultinetworkPolicyTracker;
     private IpReachabilityMonitor mIpReachabilityMonitor;
     private DhcpClient mDhcpClient;
     private DhcpResults mDhcpResults;
@@ -444,7 +391,7 @@
         }
     }
 
-    public IpClient(Context context, String ifName, IpClientCallbacks callback) {
+    public IpClient(Context context, String ifName, IIpClientCallbacks callback) {
         this(context, ifName, callback, new Dependencies());
     }
 
@@ -452,7 +399,7 @@
      * An expanded constructor, useful for dependency injection.
      * TODO: migrate all test users to mock IpClient directly and remove this ctor.
      */
-    public IpClient(Context context, String ifName, IpClientCallbacks callback,
+    public IpClient(Context context, String ifName, IIpClientCallbacks callback,
             INetworkManagementService nwService) {
         this(context, ifName, callback, new Dependencies() {
             @Override
@@ -463,7 +410,7 @@
     }
 
     @VisibleForTesting
-    IpClient(Context context, String ifName, IpClientCallbacks callback, Dependencies deps) {
+    IpClient(Context context, String ifName, IIpClientCallbacks callback, Dependencies deps) {
         super(IpClient.class.getSimpleName() + "." + ifName);
         Preconditions.checkNotNull(ifName);
         Preconditions.checkNotNull(callback);
@@ -473,9 +420,9 @@
         mContext = context;
         mInterfaceName = ifName;
         mClatInterfaceName = CLAT_PREFIX + ifName;
-        mCallback = new LoggingCallbackWrapper(callback);
         mDependencies = deps;
         mShutdownLatch = new CountDownLatch(1);
+        mCm = mContext.getSystemService(ConnectivityManager.class);
         mNwService = deps.getNMS();
 
         sSmLogs.putIfAbsent(mInterfaceName, new SharedLog(MAX_LOG_RECORDS, mTag));
@@ -483,6 +430,7 @@
         sPktLogs.putIfAbsent(mInterfaceName, new LocalLog(MAX_PACKET_RECORDS));
         mConnectivityPacketLog = sPktLogs.get(mInterfaceName);
         mMsgStateLogger = new MessageHandlingLogger();
+        mCallback = new IpClientCallbacksWrapper(callback, mLog);
 
         // TODO: Consider creating, constructing, and passing in some kind of
         // InterfaceController.Dependencies class.
@@ -560,45 +508,53 @@
     class IpClientConnector extends IIpClient.Stub {
         @Override
         public void completedPreDhcpAction() {
+            checkNetworkStackCallingPermission();
             IpClient.this.completedPreDhcpAction();
         }
         @Override
         public void confirmConfiguration() {
+            checkNetworkStackCallingPermission();
             IpClient.this.confirmConfiguration();
         }
         @Override
         public void readPacketFilterComplete(byte[] data) {
+            checkNetworkStackCallingPermission();
             IpClient.this.readPacketFilterComplete(data);
         }
         @Override
         public void shutdown() {
+            checkNetworkStackCallingPermission();
             IpClient.this.shutdown();
         }
         @Override
         public void startProvisioning(ProvisioningConfigurationParcelable req) {
-            IpClient.this.startProvisioning(
-                    android.net.shared.ProvisioningConfiguration.fromStableParcelable(req));
+            checkNetworkStackCallingPermission();
+            IpClient.this.startProvisioning(ProvisioningConfiguration.fromStableParcelable(req));
         }
         @Override
         public void stop() {
+            checkNetworkStackCallingPermission();
             IpClient.this.stop();
         }
         @Override
         public void setTcpBufferSizes(String tcpBufferSizes) {
+            checkNetworkStackCallingPermission();
             IpClient.this.setTcpBufferSizes(tcpBufferSizes);
         }
         @Override
         public void setHttpProxy(ProxyInfoParcelable proxyInfo) {
+            checkNetworkStackCallingPermission();
             IpClient.this.setHttpProxy(fromStableParcelable(proxyInfo));
         }
         @Override
         public void setMulticastFilter(boolean enabled) {
+            checkNetworkStackCallingPermission();
             IpClient.this.setMulticastFilter(enabled);
         }
-        // TODO: remove and have IpClient logs dumped in NetworkStack dumpsys
-        public void dumpIpClientLogs(FileDescriptor fd, PrintWriter pw, String[] args) {
-            IpClient.this.dump(fd, pw, args);
-        }
+    }
+
+    public String getInterfaceName() {
+        return mInterfaceName;
     }
 
     private void configureAndStartStateMachine() {
@@ -644,25 +600,10 @@
         sendMessage(CMD_TERMINATE_AFTER_STOP);
     }
 
-    // In order to avoid deadlock, this method MUST NOT be called on the
-    // IpClient instance's thread. This prohibition includes code executed by
-    // when methods on the passed-in IpClient.Callback instance are called.
-    public void awaitShutdown() {
-        try {
-            mShutdownLatch.await();
-        } catch (InterruptedException e) {
-            mLog.e("Interrupted while awaiting shutdown: " + e);
-        }
-    }
-
-    public static ProvisioningConfiguration.Builder buildProvisioningConfiguration() {
-        return new ProvisioningConfiguration.Builder();
-    }
-
     /**
      * Start provisioning with the provided parameters.
      */
-    public void startProvisioning(android.net.shared.ProvisioningConfiguration req) {
+    public void startProvisioning(ProvisioningConfiguration req) {
         if (!req.isValid()) {
             doImmediateProvisioningFailure(IpManagerEvent.ERROR_INVALID_PROVISIONING);
             return;
@@ -679,17 +620,6 @@
         sendMessage(CMD_START, new android.net.shared.ProvisioningConfiguration(req));
     }
 
-    // TODO: Delete this.
-    public void startProvisioning(StaticIpConfiguration staticIpConfig) {
-        startProvisioning(buildProvisioningConfiguration()
-                .withStaticConfiguration(staticIpConfig)
-                .build());
-    }
-
-    public void startProvisioning() {
-        startProvisioning(new android.net.shared.ProvisioningConfiguration());
-    }
-
     /**
      * Stop this IpClient.
      *
@@ -961,8 +891,9 @@
         // Note that we can still be disconnected by IpReachabilityMonitor
         // if the IPv6 default gateway (but not the IPv6 DNS servers; see
         // accompanying code in IpReachabilityMonitor) is unreachable.
-        final boolean ignoreIPv6ProvisioningLoss = (mMultinetworkPolicyTracker != null)
-                && !mMultinetworkPolicyTracker.getAvoidBadWifi();
+        final boolean ignoreIPv6ProvisioningLoss =
+                mConfiguration != null && mConfiguration.mUsingMultinetworkPolicyTracker
+                && mCm.getAvoidBadWifi();
 
         // Additionally:
         //
@@ -1253,7 +1184,7 @@
                             mCallback.onReachabilityLost(logMsg);
                         }
                     },
-                    mMultinetworkPolicyTracker);
+                    mConfiguration.mUsingMultinetworkPolicyTracker);
         } catch (IllegalArgumentException iae) {
             // Failed to start IpReachabilityMonitor. Log it and call
             // onProvisioningFailure() immediately.
@@ -1486,13 +1417,6 @@
                 return;
             }
 
-            if (mConfiguration.mUsingMultinetworkPolicyTracker) {
-                mMultinetworkPolicyTracker = new MultinetworkPolicyTracker(
-                        mContext, getHandler(),
-                        () -> mLog.log("OBSERVED AvoidBadWifi changed"));
-                mMultinetworkPolicyTracker.start();
-            }
-
             if (mConfiguration.mUsingIpReachabilityMonitor && !startIpReachabilityMonitor()) {
                 doImmediateProvisioningFailure(
                         IpManagerEvent.ERROR_STARTING_IPREACHABILITYMONITOR);
@@ -1510,11 +1434,6 @@
                 mIpReachabilityMonitor = null;
             }
 
-            if (mMultinetworkPolicyTracker != null) {
-                mMultinetworkPolicyTracker.shutdown();
-                mMultinetworkPolicyTracker = null;
-            }
-
             if (mDhcpClient != null) {
                 mDhcpClient.sendMessage(DhcpClient.CMD_STOP_DHCP);
                 mDhcpClient.doQuit();
diff --git a/services/net/java/android/net/ip/IpNeighborMonitor.java b/packages/NetworkStack/src/android/net/ip/IpNeighborMonitor.java
similarity index 100%
rename from services/net/java/android/net/ip/IpNeighborMonitor.java
rename to packages/NetworkStack/src/android/net/ip/IpNeighborMonitor.java
diff --git a/services/net/java/android/net/ip/IpReachabilityMonitor.java b/packages/NetworkStack/src/android/net/ip/IpReachabilityMonitor.java
similarity index 94%
rename from services/net/java/android/net/ip/IpReachabilityMonitor.java
rename to packages/NetworkStack/src/android/net/ip/IpReachabilityMonitor.java
index 29e2f0c..761db68 100644
--- a/services/net/java/android/net/ip/IpReachabilityMonitor.java
+++ b/packages/NetworkStack/src/android/net/ip/IpReachabilityMonitor.java
@@ -22,6 +22,7 @@
 import static android.net.metrics.IpReachabilityEvent.PROVISIONING_LOST_ORGANIC;
 
 import android.content.Context;
+import android.net.ConnectivityManager;
 import android.net.LinkProperties;
 import android.net.RouteInfo;
 import android.net.ip.IpNeighborMonitor.NeighborEvent;
@@ -29,7 +30,6 @@
 import android.net.metrics.IpReachabilityEvent;
 import android.net.netlink.StructNdMsg;
 import android.net.util.InterfaceParams;
-import android.net.util.MultinetworkPolicyTracker;
 import android.net.util.SharedLog;
 import android.os.Handler;
 import android.os.PowerManager;
@@ -165,7 +165,8 @@
     private final SharedLog mLog;
     private final Callback mCallback;
     private final Dependencies mDependencies;
-    private final MultinetworkPolicyTracker mMultinetworkPolicyTracker;
+    private final boolean mUsingMultinetworkPolicyTracker;
+    private final ConnectivityManager mCm;
     private final IpConnectivityLog mMetricsLog = new IpConnectivityLog();
     private LinkProperties mLinkProperties = new LinkProperties();
     private Map<InetAddress, NeighborEvent> mNeighborWatchList = new HashMap<>();
@@ -174,19 +175,21 @@
 
     public IpReachabilityMonitor(
             Context context, InterfaceParams ifParams, Handler h, SharedLog log, Callback callback,
-            MultinetworkPolicyTracker tracker) {
-        this(ifParams, h, log, callback, tracker, Dependencies.makeDefault(context, ifParams.name));
+            boolean usingMultinetworkPolicyTracker) {
+        this(context, ifParams, h, log, callback, usingMultinetworkPolicyTracker,
+                Dependencies.makeDefault(context, ifParams.name));
     }
 
     @VisibleForTesting
-    IpReachabilityMonitor(InterfaceParams ifParams, Handler h, SharedLog log, Callback callback,
-            MultinetworkPolicyTracker tracker, Dependencies dependencies) {
+    IpReachabilityMonitor(Context context, InterfaceParams ifParams, Handler h, SharedLog log,
+            Callback callback, boolean usingMultinetworkPolicyTracker, Dependencies dependencies) {
         if (ifParams == null) throw new IllegalArgumentException("null InterfaceParams");
 
         mInterfaceParams = ifParams;
         mLog = log.forSubComponent(TAG);
         mCallback = callback;
-        mMultinetworkPolicyTracker = tracker;
+        mUsingMultinetworkPolicyTracker = usingMultinetworkPolicyTracker;
+        mCm = context.getSystemService(ConnectivityManager.class);
         mDependencies = dependencies;
 
         mIpNeighborMonitor = new IpNeighborMonitor(h, mLog,
@@ -324,7 +327,7 @@
     }
 
     private boolean avoidingBadLinks() {
-        return (mMultinetworkPolicyTracker == null) || mMultinetworkPolicyTracker.getAvoidBadWifi();
+        return !mUsingMultinetworkPolicyTracker || mCm.getAvoidBadWifi();
     }
 
     public void probeAll() {
diff --git a/services/net/java/android/net/util/ConnectivityPacketSummary.java b/packages/NetworkStack/src/android/net/util/ConnectivityPacketSummary.java
similarity index 79%
rename from services/net/java/android/net/util/ConnectivityPacketSummary.java
rename to packages/NetworkStack/src/android/net/util/ConnectivityPacketSummary.java
index ec833b0..08c3f60 100644
--- a/services/net/java/android/net/util/ConnectivityPacketSummary.java
+++ b/packages/NetworkStack/src/android/net/util/ConnectivityPacketSummary.java
@@ -16,47 +16,46 @@
 
 package android.net.util;
 
-import static android.net.util.NetworkConstants.ARP_HWTYPE_ETHER;
-import static android.net.util.NetworkConstants.ARP_PAYLOAD_LEN;
-import static android.net.util.NetworkConstants.ARP_REPLY;
-import static android.net.util.NetworkConstants.ARP_REQUEST;
-import static android.net.util.NetworkConstants.DHCP4_CLIENT_PORT;
-import static android.net.util.NetworkConstants.ETHER_ADDR_LEN;
-import static android.net.util.NetworkConstants.ETHER_DST_ADDR_OFFSET;
-import static android.net.util.NetworkConstants.ETHER_HEADER_LEN;
-import static android.net.util.NetworkConstants.ETHER_SRC_ADDR_OFFSET;
-import static android.net.util.NetworkConstants.ETHER_TYPE_ARP;
-import static android.net.util.NetworkConstants.ETHER_TYPE_IPV4;
-import static android.net.util.NetworkConstants.ETHER_TYPE_IPV6;
-import static android.net.util.NetworkConstants.ETHER_TYPE_OFFSET;
-import static android.net.util.NetworkConstants.ICMPV6_HEADER_MIN_LEN;
-import static android.net.util.NetworkConstants.ICMPV6_ND_OPTION_LENGTH_SCALING_FACTOR;
-import static android.net.util.NetworkConstants.ICMPV6_ND_OPTION_MIN_LENGTH;
-import static android.net.util.NetworkConstants.ICMPV6_ND_OPTION_MTU;
-import static android.net.util.NetworkConstants.ICMPV6_ND_OPTION_SLLA;
-import static android.net.util.NetworkConstants.ICMPV6_ND_OPTION_TLLA;
-import static android.net.util.NetworkConstants.ICMPV6_NEIGHBOR_ADVERTISEMENT;
-import static android.net.util.NetworkConstants.ICMPV6_NEIGHBOR_SOLICITATION;
-import static android.net.util.NetworkConstants.ICMPV6_ROUTER_ADVERTISEMENT;
-import static android.net.util.NetworkConstants.ICMPV6_ROUTER_SOLICITATION;
-import static android.net.util.NetworkConstants.IPV4_ADDR_LEN;
-import static android.net.util.NetworkConstants.IPV4_DST_ADDR_OFFSET;
-import static android.net.util.NetworkConstants.IPV4_FLAGS_OFFSET;
-import static android.net.util.NetworkConstants.IPV4_FRAGMENT_MASK;
-import static android.net.util.NetworkConstants.IPV4_HEADER_MIN_LEN;
-import static android.net.util.NetworkConstants.IPV4_IHL_MASK;
-import static android.net.util.NetworkConstants.IPV4_PROTOCOL_OFFSET;
-import static android.net.util.NetworkConstants.IPV4_SRC_ADDR_OFFSET;
-import static android.net.util.NetworkConstants.IPV6_ADDR_LEN;
-import static android.net.util.NetworkConstants.IPV6_HEADER_LEN;
-import static android.net.util.NetworkConstants.IPV6_PROTOCOL_OFFSET;
-import static android.net.util.NetworkConstants.IPV6_SRC_ADDR_OFFSET;
-import static android.net.util.NetworkConstants.UDP_HEADER_LEN;
-import static android.net.util.NetworkConstants.asString;
-import static android.net.util.NetworkConstants.asUint;
 import static android.system.OsConstants.IPPROTO_ICMPV6;
 import static android.system.OsConstants.IPPROTO_UDP;
 
+import static com.android.server.util.NetworkStackConstants.ARP_HWTYPE_ETHER;
+import static com.android.server.util.NetworkStackConstants.ARP_PAYLOAD_LEN;
+import static com.android.server.util.NetworkStackConstants.ARP_REPLY;
+import static com.android.server.util.NetworkStackConstants.ARP_REQUEST;
+import static com.android.server.util.NetworkStackConstants.DHCP4_CLIENT_PORT;
+import static com.android.server.util.NetworkStackConstants.ETHER_ADDR_LEN;
+import static com.android.server.util.NetworkStackConstants.ETHER_DST_ADDR_OFFSET;
+import static com.android.server.util.NetworkStackConstants.ETHER_HEADER_LEN;
+import static com.android.server.util.NetworkStackConstants.ETHER_SRC_ADDR_OFFSET;
+import static com.android.server.util.NetworkStackConstants.ETHER_TYPE_ARP;
+import static com.android.server.util.NetworkStackConstants.ETHER_TYPE_IPV4;
+import static com.android.server.util.NetworkStackConstants.ETHER_TYPE_IPV6;
+import static com.android.server.util.NetworkStackConstants.ETHER_TYPE_OFFSET;
+import static com.android.server.util.NetworkStackConstants.ICMPV6_HEADER_MIN_LEN;
+import static com.android.server.util.NetworkStackConstants.ICMPV6_ND_OPTION_LENGTH_SCALING_FACTOR;
+import static com.android.server.util.NetworkStackConstants.ICMPV6_ND_OPTION_MIN_LENGTH;
+import static com.android.server.util.NetworkStackConstants.ICMPV6_ND_OPTION_MTU;
+import static com.android.server.util.NetworkStackConstants.ICMPV6_ND_OPTION_SLLA;
+import static com.android.server.util.NetworkStackConstants.ICMPV6_ND_OPTION_TLLA;
+import static com.android.server.util.NetworkStackConstants.ICMPV6_NEIGHBOR_ADVERTISEMENT;
+import static com.android.server.util.NetworkStackConstants.ICMPV6_NEIGHBOR_SOLICITATION;
+import static com.android.server.util.NetworkStackConstants.ICMPV6_ROUTER_ADVERTISEMENT;
+import static com.android.server.util.NetworkStackConstants.ICMPV6_ROUTER_SOLICITATION;
+import static com.android.server.util.NetworkStackConstants.IPV4_ADDR_LEN;
+import static com.android.server.util.NetworkStackConstants.IPV4_DST_ADDR_OFFSET;
+import static com.android.server.util.NetworkStackConstants.IPV4_FLAGS_OFFSET;
+import static com.android.server.util.NetworkStackConstants.IPV4_FRAGMENT_MASK;
+import static com.android.server.util.NetworkStackConstants.IPV4_HEADER_MIN_LEN;
+import static com.android.server.util.NetworkStackConstants.IPV4_IHL_MASK;
+import static com.android.server.util.NetworkStackConstants.IPV4_PROTOCOL_OFFSET;
+import static com.android.server.util.NetworkStackConstants.IPV4_SRC_ADDR_OFFSET;
+import static com.android.server.util.NetworkStackConstants.IPV6_ADDR_LEN;
+import static com.android.server.util.NetworkStackConstants.IPV6_HEADER_LEN;
+import static com.android.server.util.NetworkStackConstants.IPV6_PROTOCOL_OFFSET;
+import static com.android.server.util.NetworkStackConstants.IPV6_SRC_ADDR_OFFSET;
+import static com.android.server.util.NetworkStackConstants.UDP_HEADER_LEN;
+
 import android.net.MacAddress;
 import android.net.dhcp.DhcpPacket;
 
@@ -412,4 +411,25 @@
         final String MAC48_FORMAT = "%02x:%02x:%02x:%02x:%02x:%02x";
         return String.format(MAC48_FORMAT, printableBytes);
     }
+
+    /**
+     * Convenience method to convert an int to a String.
+     */
+    public static String asString(int i) {
+        return Integer.toString(i);
+    }
+
+    /**
+     * Convenience method to read a byte as an unsigned int.
+     */
+    public static int asUint(byte b) {
+        return (b & 0xff);
+    }
+
+    /**
+     * Convenience method to read a short as an unsigned int.
+     */
+    public static int asUint(short s) {
+        return (s & 0xffff);
+    }
 }
diff --git a/services/net/java/android/net/util/FdEventsReader.java b/packages/NetworkStack/src/android/net/util/FdEventsReader.java
similarity index 100%
rename from services/net/java/android/net/util/FdEventsReader.java
rename to packages/NetworkStack/src/android/net/util/FdEventsReader.java
diff --git a/services/net/java/android/net/util/PacketReader.java b/packages/NetworkStack/src/android/net/util/PacketReader.java
similarity index 100%
rename from services/net/java/android/net/util/PacketReader.java
rename to packages/NetworkStack/src/android/net/util/PacketReader.java
diff --git a/packages/NetworkStack/src/com/android/server/NetworkStackService.java b/packages/NetworkStack/src/com/android/server/NetworkStackService.java
index cca71e7..4080ddf 100644
--- a/packages/NetworkStack/src/com/android/server/NetworkStackService.java
+++ b/packages/NetworkStack/src/com/android/server/NetworkStackService.java
@@ -39,6 +39,8 @@
 import android.net.dhcp.DhcpServingParams;
 import android.net.dhcp.DhcpServingParamsParcel;
 import android.net.dhcp.IDhcpServerCallbacks;
+import android.net.ip.IIpClientCallbacks;
+import android.net.ip.IpClient;
 import android.net.shared.PrivateDnsConfig;
 import android.net.util.SharedLog;
 import android.os.IBinder;
@@ -50,7 +52,11 @@
 
 import java.io.FileDescriptor;
 import java.io.PrintWriter;
+import java.lang.ref.WeakReference;
 import java.util.ArrayDeque;
+import java.util.ArrayList;
+import java.util.HashSet;
+import java.util.Iterator;
 
 /**
  * Android service used to start the network stack when bound to via an intent.
@@ -80,6 +86,8 @@
         private static final int NUM_VALIDATION_LOG_LINES = 20;
         private final Context mContext;
         private final ConnectivityManager mCm;
+        @GuardedBy("mIpClients")
+        private final ArrayList<WeakReference<IpClient>> mIpClients = new ArrayList<>();
 
         private static final int MAX_VALIDATION_LOGS = 10;
         @GuardedBy("mValidationLogs")
@@ -138,6 +146,24 @@
         }
 
         @Override
+        public void makeIpClient(String ifName, IIpClientCallbacks cb) throws RemoteException {
+            final IpClient ipClient = new IpClient(mContext, ifName, cb);
+
+            synchronized (mIpClients) {
+                final Iterator<WeakReference<IpClient>> it = mIpClients.iterator();
+                while (it.hasNext()) {
+                    final IpClient ipc = it.next().get();
+                    if (ipc == null) {
+                        it.remove();
+                    }
+                }
+                mIpClients.add(new WeakReference<>(ipClient));
+            }
+
+            cb.onIpClientCreated(ipClient.makeConnector());
+        }
+
+        @Override
         protected void dump(@NonNull FileDescriptor fd, @NonNull PrintWriter fout,
                 @Nullable String[] args) {
             checkDumpPermission();
@@ -145,6 +171,33 @@
             pw.println("NetworkStack logs:");
             mLog.dump(fd, pw, args);
 
+            // Dump full IpClient logs for non-GCed clients
+            pw.println();
+            pw.println("Recently active IpClient logs:");
+            final ArrayList<IpClient> ipClients = new ArrayList<>();
+            final HashSet<String> dumpedIpClientIfaces = new HashSet<>();
+            synchronized (mIpClients) {
+                for (WeakReference<IpClient> ipcRef : mIpClients) {
+                    final IpClient ipc = ipcRef.get();
+                    if (ipc != null) {
+                        ipClients.add(ipc);
+                    }
+                }
+            }
+
+            for (IpClient ipc : ipClients) {
+                pw.println(ipc.getName());
+                pw.increaseIndent();
+                ipc.dump(fd, pw, args);
+                pw.decreaseIndent();
+                dumpedIpClientIfaces.add(ipc.getInterfaceName());
+            }
+
+            // State machine and connectivity metrics logs are kept for GCed IpClients
+            pw.println();
+            pw.println("Other IpClient logs:");
+            IpClient.dumpAllLogs(fout, dumpedIpClientIfaces);
+
             pw.println();
             pw.println("Validation logs (most recent first):");
             synchronized (mValidationLogs) {
diff --git a/packages/NetworkStack/src/com/android/server/connectivity/NetworkMonitor.java b/packages/NetworkStack/src/com/android/server/connectivity/NetworkMonitor.java
index a3d7852..6b31b82 100644
--- a/packages/NetworkStack/src/com/android/server/connectivity/NetworkMonitor.java
+++ b/packages/NetworkStack/src/com/android/server/connectivity/NetworkMonitor.java
@@ -247,7 +247,6 @@
     private final TelephonyManager mTelephonyManager;
     private final WifiManager mWifiManager;
     private final ConnectivityManager mCm;
-    private final NetworkRequest mDefaultRequest;
     private final IpConnectivityLog mMetricsLog;
     private final Dependencies mDependencies;
 
@@ -336,7 +335,6 @@
         mTelephonyManager = (TelephonyManager) context.getSystemService(Context.TELEPHONY_SERVICE);
         mWifiManager = (WifiManager) context.getSystemService(Context.WIFI_SERVICE);
         mCm = (ConnectivityManager) context.getSystemService(Context.CONNECTIVITY_SERVICE);
-        mDefaultRequest = defaultRequest;
 
         // CHECKSTYLE:OFF IndentationCheck
         addState(mDefaultState);
@@ -486,8 +484,7 @@
     }
 
     private boolean isValidationRequired() {
-        return NetworkMonitorUtils.isValidationRequired(
-                mDefaultRequest.networkCapabilities, mNetworkCapabilities);
+        return NetworkMonitorUtils.isValidationRequired(mNetworkCapabilities);
     }
 
 
diff --git a/packages/NetworkStack/src/com/android/server/util/NetworkStackConstants.java b/packages/NetworkStack/src/com/android/server/util/NetworkStackConstants.java
index bb5900c..eedaf30 100644
--- a/packages/NetworkStack/src/com/android/server/util/NetworkStackConstants.java
+++ b/packages/NetworkStack/src/com/android/server/util/NetworkStackConstants.java
@@ -32,12 +32,103 @@
     public static final int IPV4_MAX_MTU = 65_535;
 
     /**
+     * Ethernet constants.
+     *
+     * See also:
+     *     - https://tools.ietf.org/html/rfc894
+     *     - https://tools.ietf.org/html/rfc2464
+     *     - https://tools.ietf.org/html/rfc7042
+     *     - http://www.iana.org/assignments/ethernet-numbers/ethernet-numbers.xhtml
+     *     - http://www.iana.org/assignments/ieee-802-numbers/ieee-802-numbers.xhtml
+     */
+    public static final int ETHER_DST_ADDR_OFFSET = 0;
+    public static final int ETHER_SRC_ADDR_OFFSET = 6;
+    public static final int ETHER_ADDR_LEN = 6;
+    public static final int ETHER_TYPE_OFFSET = 12;
+    public static final int ETHER_TYPE_LENGTH = 2;
+    public static final int ETHER_TYPE_ARP  = 0x0806;
+    public static final int ETHER_TYPE_IPV4 = 0x0800;
+    public static final int ETHER_TYPE_IPV6 = 0x86dd;
+    public static final int ETHER_HEADER_LEN = 14;
+
+    /**
+     * ARP constants.
+     *
+     * See also:
+     *     - https://tools.ietf.org/html/rfc826
+     *     - http://www.iana.org/assignments/arp-parameters/arp-parameters.xhtml
+     */
+    public static final int ARP_PAYLOAD_LEN = 28;  // For Ethernet+IPv4.
+    public static final int ARP_REQUEST = 1;
+    public static final int ARP_REPLY   = 2;
+    public static final int ARP_HWTYPE_RESERVED_LO = 0;
+    public static final int ARP_HWTYPE_ETHER       = 1;
+    public static final int ARP_HWTYPE_RESERVED_HI = 0xffff;
+
+    /**
+     * IPv4 constants.
+     *
+     * See also:
+     *     - https://tools.ietf.org/html/rfc791
+     */
+    public static final int IPV4_HEADER_MIN_LEN = 20;
+    public static final int IPV4_IHL_MASK = 0xf;
+    public static final int IPV4_FLAGS_OFFSET = 6;
+    public static final int IPV4_FRAGMENT_MASK = 0x1fff;
+    public static final int IPV4_PROTOCOL_OFFSET = 9;
+    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;
+
+    /**
+     * IPv6 constants.
+     *
+     * See also:
+     *     - https://tools.ietf.org/html/rfc2460
+     */
+    public static final int IPV6_ADDR_LEN = 16;
+    public static final int IPV6_HEADER_LEN = 40;
+    public static final int IPV6_PROTOCOL_OFFSET = 6;
+    public static final int IPV6_SRC_ADDR_OFFSET = 8;
+    public static final int IPV6_DST_ADDR_OFFSET = 24;
+
+    /**
+     * ICMPv6 constants.
+     *
+     * See also:
+     *     - https://tools.ietf.org/html/rfc4443
+     *     - https://tools.ietf.org/html/rfc4861
+     */
+    public static final int ICMPV6_HEADER_MIN_LEN = 4;
+    public static final int ICMPV6_ECHO_REPLY_TYPE = 129;
+    public static final int ICMPV6_ECHO_REQUEST_TYPE = 128;
+    public static final int ICMPV6_ROUTER_SOLICITATION    = 133;
+    public static final int ICMPV6_ROUTER_ADVERTISEMENT   = 134;
+    public static final int ICMPV6_NEIGHBOR_SOLICITATION  = 135;
+    public static final int ICMPV6_NEIGHBOR_ADVERTISEMENT = 136;
+    public static final int ICMPV6_ND_OPTION_MIN_LENGTH = 8;
+    public static final int ICMPV6_ND_OPTION_LENGTH_SCALING_FACTOR = 8;
+    public static final int ICMPV6_ND_OPTION_SLLA = 1;
+    public static final int ICMPV6_ND_OPTION_TLLA = 2;
+    public static final int ICMPV6_ND_OPTION_MTU  = 5;
+
+    /**
+     * UDP constants.
+     *
+     * See also:
+     *     - https://tools.ietf.org/html/rfc768
+     */
+    public static final int UDP_HEADER_LEN = 8;
+
+
+    /**
      * DHCP constants.
      *
      * See also:
      *     - https://tools.ietf.org/html/rfc2131
      */
     public static final int INFINITE_LEASE = 0xffffffff;
+    public static final int DHCP4_CLIENT_PORT = 68;
 
     private NetworkStackConstants() {
         throw new UnsupportedOperationException("This class is not to be instantiated");
diff --git a/packages/NetworkStack/tests/Android.bp b/packages/NetworkStack/tests/Android.bp
index bd7ff2a..45fa2dc 100644
--- a/packages/NetworkStack/tests/Android.bp
+++ b/packages/NetworkStack/tests/Android.bp
@@ -16,9 +16,12 @@
 
 android_test {
     name: "NetworkStackTests",
+    certificate: "platform",
     srcs: ["src/**/*.java"],
+    resource_dirs: ["res"],
     static_libs: [
         "android-support-test",
+        "frameworks-base-testutils",
         "mockito-target-extended-minus-junit4",
         "NetworkStackLib",
         "testables",
@@ -26,10 +29,70 @@
     libs: [
         "android.test.runner",
         "android.test.base",
+        "android.test.mock",
     ],
     jni_libs: [
         // For mockito extended
         "libdexmakerjvmtiagent",
         "libstaticjvmtiagent",
-    ]
-}
\ No newline at end of file
+        // For ApfTest
+        "libartbase",
+        "libbacktrace",
+        "libbase",
+        "libbinder",
+        "libbinderthreadstate",
+        "libc++",
+        "libcrypto",
+        "libcutils",
+        "libdexfile",
+        "libhidl-gen-utils",
+        "libhidlbase",
+        "libhidltransport",
+        "libhwbinder",
+        "liblog",
+        "liblzma",
+        "libnativehelper",
+        "libnetworkstacktestsjni",
+        "libpackagelistparser",
+        "libpcre2",
+        "libprocessgroup",
+        "libselinux",
+        "libui",
+        "libutils",
+        "libvintf",
+        "libvndksupport",
+        "libtinyxml2",
+        "libunwindstack",
+        "libutilscallstack",
+        "libziparchive",
+        "libz",
+        "netd_aidl_interface-cpp",
+    ],
+}
+
+cc_library_shared {
+    name: "libnetworkstacktestsjni",
+    srcs: [
+        "jni/**/*.cpp"
+    ],
+    cflags: [
+        "-Wall",
+        "-Wextra",
+        "-Werror",
+    ],
+    include_dirs: [
+        "hardware/google/apf",
+    ],
+    shared_libs: [
+        "libbinder",
+        "liblog",
+        "libcutils",
+        "libnativehelper",
+        "netd_aidl_interface-cpp",
+    ],
+    static_libs: [
+        "libapf",
+        "libpcap",
+    ],
+
+}
diff --git a/packages/NetworkStack/tests/AndroidManifest.xml b/packages/NetworkStack/tests/AndroidManifest.xml
index 8b8474f..9cb2c21 100644
--- a/packages/NetworkStack/tests/AndroidManifest.xml
+++ b/packages/NetworkStack/tests/AndroidManifest.xml
@@ -15,6 +15,35 @@
 -->
 <manifest xmlns:android="http://schemas.android.com/apk/res/android"
           package="com.android.server.networkstack.tests">
+
+    <uses-permission android:name="android.permission.READ_LOGS" />
+    <uses-permission android:name="android.permission.WRITE_SETTINGS" />
+    <uses-permission android:name="android.permission.WRITE_SECURE_SETTINGS" />
+    <uses-permission android:name="android.permission.READ_PHONE_STATE" />
+    <uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" />
+    <uses-permission android:name="android.permission.BROADCAST_STICKY" />
+    <uses-permission android:name="android.permission.UPDATE_DEVICE_STATS" />
+    <uses-permission android:name="android.permission.MANAGE_APP_TOKENS" />
+    <uses-permission android:name="android.permission.WAKE_LOCK" />
+    <uses-permission android:name="android.permission.INTERACT_ACROSS_USERS" />
+    <uses-permission android:name="android.permission.REAL_GET_TASKS" />
+    <uses-permission android:name="android.permission.GET_DETAILED_TASKS" />
+    <uses-permission android:name="android.permission.MANAGE_NETWORK_POLICY" />
+    <uses-permission android:name="android.permission.READ_NETWORK_USAGE_HISTORY" />
+    <uses-permission android:name="android.permission.CONNECTIVITY_INTERNAL" />
+    <uses-permission android:name="android.permission.ACCESS_WIFI_STATE" />
+    <uses-permission android:name="android.permission.MANAGE_USERS" />
+    <uses-permission android:name="android.permission.INTERACT_ACROSS_USERS_FULL" />
+    <uses-permission android:name="android.permission.MANAGE_DEVICE_ADMINS" />
+    <uses-permission android:name="android.permission.MODIFY_PHONE_STATE" />
+    <uses-permission android:name="android.permission.INTERNET" />
+    <uses-permission android:name="android.permission.CHANGE_NETWORK_STATE" />
+    <uses-permission android:name="android.permission.PACKET_KEEPALIVE_OFFLOAD" />
+    <uses-permission android:name="android.permission.GET_INTENT_SENDER_INTENT" />
+    <uses-permission android:name="android.permission.MANAGE_ACTIVITY_STACKS" />
+    <uses-permission android:name="android.permission.INSTALL_PACKAGES" />
+    <uses-permission android:name="android.permission.NETWORK_STACK" />
+
     <application android:debuggable="true">
         <uses-library android:name="android.test.runner" />
     </application>
diff --git a/tests/net/jni/apf_jni.cpp b/packages/NetworkStack/tests/jni/apf_jni.cpp
similarity index 100%
rename from tests/net/jni/apf_jni.cpp
rename to packages/NetworkStack/tests/jni/apf_jni.cpp
diff --git a/tests/net/res/raw/apf.pcap b/packages/NetworkStack/tests/res/raw/apf.pcap
similarity index 100%
rename from tests/net/res/raw/apf.pcap
rename to packages/NetworkStack/tests/res/raw/apf.pcap
Binary files differ
diff --git a/tests/net/res/raw/apfPcap.pcap b/packages/NetworkStack/tests/res/raw/apfPcap.pcap
similarity index 100%
rename from tests/net/res/raw/apfPcap.pcap
rename to packages/NetworkStack/tests/res/raw/apfPcap.pcap
Binary files differ
diff --git a/tests/net/java/android/net/apf/ApfTest.java b/packages/NetworkStack/tests/src/android/net/apf/ApfTest.java
similarity index 98%
rename from tests/net/java/android/net/apf/ApfTest.java
rename to packages/NetworkStack/tests/src/android/net/apf/ApfTest.java
index 3c3e7ce..f76e412 100644
--- a/tests/net/java/android/net/apf/ApfTest.java
+++ b/packages/NetworkStack/tests/src/android/net/apf/ApfTest.java
@@ -16,8 +16,6 @@
 
 package android.net.apf;
 
-import static android.net.util.NetworkConstants.ICMPV6_ECHO_REQUEST_TYPE;
-import static android.net.util.NetworkConstants.ICMPV6_ROUTER_ADVERTISEMENT;
 import static android.system.OsConstants.AF_UNIX;
 import static android.system.OsConstants.ARPHRD_ETHER;
 import static android.system.OsConstants.ETH_P_ARP;
@@ -28,12 +26,14 @@
 import static android.system.OsConstants.SOCK_STREAM;
 
 import static com.android.internal.util.BitUtils.bytesToBEInt;
+import static com.android.server.util.NetworkStackConstants.ICMPV6_ECHO_REQUEST_TYPE;
 
 import static org.junit.Assert.assertEquals;
 import static org.junit.Assert.assertFalse;
 import static org.junit.Assert.assertTrue;
 import static org.junit.Assert.fail;
 import static org.mockito.Mockito.atLeastOnce;
+import static org.mockito.Mockito.mock;
 import static org.mockito.Mockito.verify;
 
 import android.content.Context;
@@ -42,10 +42,14 @@
 import android.net.apf.ApfFilter.ApfConfiguration;
 import android.net.apf.ApfGenerator.IllegalInstructionException;
 import android.net.apf.ApfGenerator.Register;
+import android.net.ip.IIpClientCallbacks;
+import android.net.ip.IpClient;
+import android.net.ip.IpClient.IpClientCallbacksWrapper;
 import android.net.ip.IpClientCallbacks;
 import android.net.metrics.IpConnectivityLog;
 import android.net.metrics.RaEvent;
 import android.net.util.InterfaceParams;
+import android.net.util.SharedLog;
 import android.os.ConditionVariable;
 import android.os.Parcelable;
 import android.os.SystemClock;
@@ -57,8 +61,9 @@
 import android.text.format.DateUtils;
 import android.util.Log;
 
-import com.android.frameworks.tests.net.R;
 import com.android.internal.util.HexDump;
+import com.android.server.networkstack.tests.R;
+import com.android.server.util.NetworkStackConstants;
 
 import libcore.io.IoUtils;
 import libcore.io.Streams;
@@ -100,7 +105,7 @@
     public void setUp() throws Exception {
         MockitoAnnotations.initMocks(this);
         // Load up native shared library containing APF interpreter exposed via JNI.
-        System.loadLibrary("frameworksnettestsjni");
+        System.loadLibrary("networkstacktestsjni");
     }
 
     private static final String TAG = "ApfTest";
@@ -915,10 +920,14 @@
             HexDump.toHexString(data, false), result);
     }
 
-    private class MockIpClientCallback extends IpClientCallbacks {
+    private class MockIpClientCallback extends IpClientCallbacksWrapper {
         private final ConditionVariable mGotApfProgram = new ConditionVariable();
         private byte[] mLastApfProgram;
 
+        MockIpClientCallback() {
+            super(mock(IIpClientCallbacks.class), mock(SharedLog.class));
+        }
+
         @Override
         public void installPacketFilter(byte[] filter) {
             mLastApfProgram = filter;
@@ -946,7 +955,7 @@
         private final long mFixedTimeMs = SystemClock.elapsedRealtime();
 
         public TestApfFilter(Context context, ApfConfiguration config,
-                IpClientCallbacks ipClientCallback, IpConnectivityLog log) throws Exception {
+                IpClientCallbacksWrapper ipClientCallback, IpConnectivityLog log) throws Exception {
             super(context, config, InterfaceParams.getByName("lo"), ipClientCallback, log);
         }
 
@@ -1075,8 +1084,8 @@
     private static final byte[] IPV4_ANY_HOST_ADDR       = {0, 0, 0, 0};
 
     // Helper to initialize a default apfFilter.
-    private ApfFilter setupApfFilter(IpClientCallbacks ipClientCallback, ApfConfiguration config)
-            throws Exception {
+    private ApfFilter setupApfFilter(
+            IpClientCallbacksWrapper ipClientCallback, ApfConfiguration config) throws Exception {
         LinkAddress link = new LinkAddress(InetAddress.getByAddress(MOCK_IPV4_ADDR), 19);
         LinkProperties lp = new LinkProperties();
         lp.addLinkAddress(link);
@@ -1294,7 +1303,7 @@
 
         // However, we should still let through all other ICMPv6 types.
         ByteBuffer raPacket = ByteBuffer.wrap(packet.array().clone());
-        raPacket.put(ICMP6_TYPE_OFFSET, (byte)ICMPV6_ROUTER_ADVERTISEMENT);
+        raPacket.put(ICMP6_TYPE_OFFSET, (byte) NetworkStackConstants.ICMPV6_ROUTER_ADVERTISEMENT);
         assertPass(ipClientCallback.getApfProgram(), raPacket.array());
 
         // Now wake up from doze mode to ensure that we no longer drop the packets.
diff --git a/tests/net/java/android/net/apf/Bpf2Apf.java b/packages/NetworkStack/tests/src/android/net/apf/Bpf2Apf.java
similarity index 100%
rename from tests/net/java/android/net/apf/Bpf2Apf.java
rename to packages/NetworkStack/tests/src/android/net/apf/Bpf2Apf.java
diff --git a/tests/net/java/android/net/dhcp/DhcpPacketTest.java b/packages/NetworkStack/tests/src/android/net/dhcp/DhcpPacketTest.java
similarity index 100%
rename from tests/net/java/android/net/dhcp/DhcpPacketTest.java
rename to packages/NetworkStack/tests/src/android/net/dhcp/DhcpPacketTest.java
diff --git a/tests/net/java/android/net/ip/IpClientTest.java b/packages/NetworkStack/tests/src/android/net/ip/IpClientTest.java
similarity index 92%
rename from tests/net/java/android/net/ip/IpClientTest.java
rename to packages/NetworkStack/tests/src/android/net/ip/IpClientTest.java
index 7a83757..4ae044de 100644
--- a/tests/net/java/android/net/ip/IpClientTest.java
+++ b/packages/NetworkStack/tests/src/android/net/ip/IpClientTest.java
@@ -16,10 +16,13 @@
 
 package android.net.ip;
 
+import static android.net.shared.LinkPropertiesParcelableUtil.fromStableParcelable;
+
 import static org.junit.Assert.assertEquals;
 import static org.junit.Assert.assertFalse;
 import static org.junit.Assert.assertTrue;
 import static org.junit.Assert.fail;
+import static org.mockito.ArgumentMatchers.argThat;
 import static org.mockito.Mockito.any;
 import static org.mockito.Mockito.anyString;
 import static org.mockito.Mockito.eq;
@@ -33,6 +36,7 @@
 import android.app.AlarmManager;
 import android.content.Context;
 import android.content.res.Resources;
+import android.net.ConnectivityManager;
 import android.net.INetd;
 import android.net.IpPrefix;
 import android.net.LinkAddress;
@@ -82,10 +86,11 @@
     private static final int TEST_TIMEOUT_MS = 400;
 
     @Mock private Context mContext;
+    @Mock private ConnectivityManager mCm;
     @Mock private INetworkManagementService mNMService;
     @Mock private INetd mNetd;
     @Mock private Resources mResources;
-    @Mock private IpClientCallbacks mCb;
+    @Mock private IIpClientCallbacks mCb;
     @Mock private AlarmManager mAlarm;
     @Mock private IpClient.Dependencies mDependecies;
     private MockContentResolver mContentResolver;
@@ -98,6 +103,7 @@
         MockitoAnnotations.initMocks(this);
 
         when(mContext.getSystemService(eq(Context.ALARM_SERVICE))).thenReturn(mAlarm);
+        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);
@@ -204,7 +210,8 @@
         verify(mNetd, timeout(TEST_TIMEOUT_MS).times(1)).interfaceSetEnableIPv6(iface, false);
         verify(mNetd, timeout(TEST_TIMEOUT_MS).times(1)).interfaceClearAddrs(iface);
         verify(mCb, timeout(TEST_TIMEOUT_MS).times(1))
-                .onLinkPropertiesChange(eq(makeEmptyLinkProperties(iface)));
+                .onLinkPropertiesChange(argThat(
+                        lp -> fromStableParcelable(lp).equals(makeEmptyLinkProperties(iface))));
     }
 
     @Test
@@ -249,13 +256,15 @@
         mObserver.addressUpdated(iface, new LinkAddress(addresses[lastAddr]));
         LinkProperties want = linkproperties(links(addresses), routes(prefixes));
         want.setInterfaceName(iface);
-        verify(mCb, timeout(TEST_TIMEOUT_MS).times(1)).onProvisioningSuccess(eq(want));
+        verify(mCb, timeout(TEST_TIMEOUT_MS).times(1)).onProvisioningSuccess(argThat(
+                lp -> fromStableParcelable(lp).equals(want)));
 
         ipc.shutdown();
         verify(mNetd, timeout(TEST_TIMEOUT_MS).times(1)).interfaceSetEnableIPv6(iface, false);
         verify(mNetd, timeout(TEST_TIMEOUT_MS).times(1)).interfaceClearAddrs(iface);
         verify(mCb, timeout(TEST_TIMEOUT_MS).times(1))
-                .onLinkPropertiesChange(eq(makeEmptyLinkProperties(iface)));
+                .onLinkPropertiesChange(argThat(
+                        lp -> fromStableParcelable(lp).equals(makeEmptyLinkProperties(iface))));
     }
 
     @Test
@@ -487,11 +496,11 @@
         List<String> list3 = Arrays.asList("bar", "baz");
         List<String> list4 = Arrays.asList("foo", "bar", "baz");
 
-        assertTrue(IpClient.all(list1, (x) -> false));
-        assertFalse(IpClient.all(list2, (x) -> false));
-        assertTrue(IpClient.all(list3, (x) -> true));
-        assertTrue(IpClient.all(list2, (x) -> x.charAt(0) == 'f'));
-        assertFalse(IpClient.all(list4, (x) -> x.charAt(0) == 'f'));
+        assertTrue(InitialConfiguration.all(list1, (x) -> false));
+        assertFalse(InitialConfiguration.all(list2, (x) -> false));
+        assertTrue(InitialConfiguration.all(list3, (x) -> true));
+        assertTrue(InitialConfiguration.all(list2, (x) -> x.charAt(0) == 'f'));
+        assertFalse(InitialConfiguration.all(list4, (x) -> x.charAt(0) == 'f'));
     }
 
     @Test
@@ -501,11 +510,11 @@
         List<String> list3 = Arrays.asList("bar", "baz");
         List<String> list4 = Arrays.asList("foo", "bar", "baz");
 
-        assertFalse(IpClient.any(list1, (x) -> true));
-        assertTrue(IpClient.any(list2, (x) -> true));
-        assertTrue(IpClient.any(list2, (x) -> x.charAt(0) == 'f'));
-        assertFalse(IpClient.any(list3, (x) -> x.charAt(0) == 'f'));
-        assertTrue(IpClient.any(list4, (x) -> x.charAt(0) == 'f'));
+        assertFalse(InitialConfiguration.any(list1, (x) -> true));
+        assertTrue(InitialConfiguration.any(list2, (x) -> true));
+        assertTrue(InitialConfiguration.any(list2, (x) -> x.charAt(0) == 'f'));
+        assertFalse(InitialConfiguration.any(list3, (x) -> x.charAt(0) == 'f'));
+        assertTrue(InitialConfiguration.any(list4, (x) -> x.charAt(0) == 'f'));
     }
 
     @Test
diff --git a/tests/net/java/android/net/ip/IpReachabilityMonitorTest.java b/packages/NetworkStack/tests/src/android/net/ip/IpReachabilityMonitorTest.java
similarity index 90%
rename from tests/net/java/android/net/ip/IpReachabilityMonitorTest.java
rename to packages/NetworkStack/tests/src/android/net/ip/IpReachabilityMonitorTest.java
index e65585f..e3b5ddf 100644
--- a/tests/net/java/android/net/ip/IpReachabilityMonitorTest.java
+++ b/packages/NetworkStack/tests/src/android/net/ip/IpReachabilityMonitorTest.java
@@ -16,11 +16,10 @@
 
 package android.net.ip;
 
-import static org.junit.Assert.assertEquals;
-import static org.junit.Assert.fail;
 import static org.mockito.Mockito.anyString;
 import static org.mockito.Mockito.when;
 
+import android.content.Context;
 import android.net.util.InterfaceParams;
 import android.net.util.SharedLog;
 import android.os.Handler;
@@ -45,6 +44,7 @@
     @Mock IpReachabilityMonitor.Callback mCallback;
     @Mock IpReachabilityMonitor.Dependencies mDependencies;
     @Mock SharedLog mLog;
+    @Mock Context mContext;
     Handler mHandler;
 
     @Before
@@ -56,7 +56,8 @@
 
     IpReachabilityMonitor makeMonitor() {
         final InterfaceParams ifParams = new InterfaceParams("fake0", 1, null);
-        return new IpReachabilityMonitor(ifParams, mHandler, mLog, mCallback, null, mDependencies);
+        return new IpReachabilityMonitor(
+                mContext, ifParams, mHandler, mLog, mCallback, false, mDependencies);
     }
 
     @Test
diff --git a/tests/net/java/android/net/util/ConnectivityPacketSummaryTest.java b/packages/NetworkStack/tests/src/android/net/util/ConnectivityPacketSummaryTest.java
similarity index 100%
rename from tests/net/java/android/net/util/ConnectivityPacketSummaryTest.java
rename to packages/NetworkStack/tests/src/android/net/util/ConnectivityPacketSummaryTest.java
diff --git a/tests/net/java/android/net/util/PacketReaderTest.java b/packages/NetworkStack/tests/src/android/net/util/PacketReaderTest.java
similarity index 100%
rename from tests/net/java/android/net/util/PacketReaderTest.java
rename to packages/NetworkStack/tests/src/android/net/util/PacketReaderTest.java
diff --git a/packages/SettingsLib/EntityHeaderWidgets/res/layout/app_entities_header.xml b/packages/SettingsLib/EntityHeaderWidgets/res/layout/app_entities_header.xml
index 9f30eda..716fc8d 100644
--- a/packages/SettingsLib/EntityHeaderWidgets/res/layout/app_entities_header.xml
+++ b/packages/SettingsLib/EntityHeaderWidgets/res/layout/app_entities_header.xml
@@ -33,7 +33,7 @@
         android:textAppearance="@style/AppEntitiesHeader.Text.HeaderTitle"/>
 
     <LinearLayout
-        android:id="@+id/all_apps_view"
+        android:id="@+id/app_views_container"
         android:layout_width="match_parent"
         android:layout_height="wrap_content"
         android:paddingTop="16dp"
@@ -61,4 +61,12 @@
         android:layout_height="48dp"
         android:gravity="center"/>
 
+    <TextView
+        android:id="@+id/empty_view"
+        android:layout_width="match_parent"
+        android:layout_height="106dp"
+        android:gravity="center"
+        android:visibility="gone"
+        android:textAppearance="@style/AppEntitiesHeader.Text.Summary"/>
+
 </LinearLayout>
diff --git a/packages/SettingsLib/EntityHeaderWidgets/src/com/android/settingslib/widget/AppEntitiesHeaderController.java b/packages/SettingsLib/EntityHeaderWidgets/src/com/android/settingslib/widget/AppEntitiesHeaderController.java
index 73cb8db..330049f 100644
--- a/packages/SettingsLib/EntityHeaderWidgets/src/com/android/settingslib/widget/AppEntitiesHeaderController.java
+++ b/packages/SettingsLib/EntityHeaderWidgets/src/com/android/settingslib/widget/AppEntitiesHeaderController.java
@@ -77,9 +77,11 @@
 
     private final Context mContext;
     private final TextView mHeaderTitleView;
+    private final TextView mHeaderEmptyView;
     private final Button mHeaderDetailsView;
 
     private final AppEntityInfo[] mAppEntityInfos;
+    private final View mAppViewsContainer;
     private final View[] mAppEntityViews;
     private final ImageView[] mAppIconViews;
     private final TextView[] mAppTitleViews;
@@ -87,6 +89,7 @@
 
     private int mHeaderTitleRes;
     private int mHeaderDetailsRes;
+    private int mHeaderEmptyRes;
     private View.OnClickListener mDetailsOnClickListener;
 
     /**
@@ -104,6 +107,8 @@
         mContext = context;
         mHeaderTitleView = appEntitiesHeaderView.findViewById(R.id.header_title);
         mHeaderDetailsView = appEntitiesHeaderView.findViewById(R.id.header_details);
+        mHeaderEmptyView = appEntitiesHeaderView.findViewById(R.id.empty_view);
+        mAppViewsContainer = appEntitiesHeaderView.findViewById(R.id.app_views_container);
 
         mAppEntityInfos = new AppEntityInfo[MAXIMUM_APPS];
         mAppIconViews = new ImageView[MAXIMUM_APPS];
@@ -152,6 +157,14 @@
     }
 
     /**
+     * Sets the string resource id for the empty text.
+     */
+    public AppEntitiesHeaderController setHeaderEmptyRes(@StringRes int emptyRes) {
+        mHeaderEmptyRes = emptyRes;
+        return this;
+    }
+
+    /**
      * Set an app entity at a specified position view.
      *
      * @param index         the index at which the specified view is to be inserted
@@ -192,6 +205,12 @@
      */
     public void apply() {
         bindHeaderTitleView();
+
+        if (isAppEntityInfosEmpty()) {
+            setEmptyViewVisible(true);
+            return;
+        }
+        setEmptyViewVisible(false);
         bindHeaderDetailsView();
 
         // Rebind all apps view
@@ -245,4 +264,22 @@
             mAppSummaryViews[index].setText(summary);
         }
     }
+
+    private void setEmptyViewVisible(boolean visible) {
+        if (mHeaderEmptyRes != 0) {
+            mHeaderEmptyView.setText(mHeaderEmptyRes);
+        }
+        mHeaderEmptyView.setVisibility(visible ? View.VISIBLE : View.GONE);
+        mHeaderDetailsView.setVisibility(visible ? View.GONE : View.VISIBLE);
+        mAppViewsContainer.setVisibility(visible ? View.GONE : View.VISIBLE);
+    }
+
+    private boolean isAppEntityInfosEmpty() {
+        for (AppEntityInfo info : mAppEntityInfos) {
+            if (info != null) {
+                return false;
+            }
+        }
+        return true;
+    }
 }
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/src/com/android/settingslib/wifi/AccessPoint.java b/packages/SettingsLib/src/com/android/settingslib/wifi/AccessPoint.java
index 27dc628..9b12a31 100644
--- a/packages/SettingsLib/src/com/android/settingslib/wifi/AccessPoint.java
+++ b/packages/SettingsLib/src/com/android/settingslib/wifi/AccessPoint.java
@@ -622,7 +622,7 @@
                     .append(KEY_PREFIX_FQDN)
                     .append(config.FQDN).toString();
         } else {
-            return getKey(config.SSID, config.BSSID, getSecurity(config));
+            return getKey(removeDoubleQuotes(config.SSID), config.BSSID, getSecurity(config));
         }
     }
 
@@ -1555,7 +1555,7 @@
                     mOsuFailure = mContext.getString(
                             R.string.osu_failure_provisioning_not_available);
                     break;
-                case OSU_FAILURE_INVALID_SERVER_URL:
+                case OSU_FAILURE_INVALID_URL_FORMAT_FOR_OSU:
                     mOsuFailure = mContext.getString(R.string.osu_failure_invalid_server_url);
                     break;
                 case OSU_FAILURE_UNEXPECTED_COMMAND_TYPE:
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/SettingsLib/tests/robotests/src/com/android/settingslib/widget/AppEntitiesHeaderControllerTest.java b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/widget/AppEntitiesHeaderControllerTest.java
index 8c18c35..4c68c14 100644
--- a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/widget/AppEntitiesHeaderControllerTest.java
+++ b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/widget/AppEntitiesHeaderControllerTest.java
@@ -60,6 +60,7 @@
                 .setOnClickListener(v -> {
                 })
                 .build();
+        mController.setAppEntity(0, mAppEntityInfo);
     }
 
     @Test
@@ -172,6 +173,8 @@
         mController.setAppEntity(0, mAppEntityInfo)
                 .setAppEntity(1, mAppEntityInfo)
                 .setAppEntity(2, mAppEntityInfo).apply();
+        final View appViewsContainer = mAppEntitiesHeaderView.findViewById(
+                R.id.app_views_container);
         final View app1View = mAppEntitiesHeaderView.findViewById(R.id.app1_view);
         final View app2View = mAppEntitiesHeaderView.findViewById(R.id.app2_view);
         final View app3View = mAppEntitiesHeaderView.findViewById(R.id.app3_view);
@@ -181,8 +184,28 @@
         assertThat(app3View.getVisibility()).isEqualTo(View.VISIBLE);
 
         mController.clearAllAppEntities().apply();
-        assertThat(app1View.getVisibility()).isEqualTo(View.GONE);
-        assertThat(app2View.getVisibility()).isEqualTo(View.GONE);
-        assertThat(app3View.getVisibility()).isEqualTo(View.GONE);
+
+        assertThat(appViewsContainer.getVisibility()).isEqualTo(View.GONE);
+    }
+
+    @Test
+    public void apply_noAppEntitySet_shouldOnlyShowTitleAndEmptyView() {
+        mController.setHeaderTitleRes(R.string.expand_button_title)
+                .setAppEntity(0, mAppEntityInfo)
+                .setAppEntity(1, mAppEntityInfo)
+                .setAppEntity(2, mAppEntityInfo).apply();
+        final View titleView = mAppEntitiesHeaderView.findViewById(R.id.header_title);
+        final View detailsView = mAppEntitiesHeaderView.findViewById(R.id.header_details);
+        final View emptyView = mAppEntitiesHeaderView.findViewById(R.id.empty_view);
+        final View appViewsContainer = mAppEntitiesHeaderView.findViewById(
+                R.id.app_views_container);
+
+        mController.clearAllAppEntities().apply();
+
+        assertThat(titleView.getVisibility()).isEqualTo(View.VISIBLE);
+        assertThat(emptyView.getVisibility()).isEqualTo(View.VISIBLE);
+
+        assertThat(detailsView.getVisibility()).isEqualTo(View.GONE);
+        assertThat(appViewsContainer.getVisibility()).isEqualTo(View.GONE);
     }
 }
diff --git a/packages/SettingsProvider/res/values/defaults.xml b/packages/SettingsProvider/res/values/defaults.xml
index de86789..dbeee1c 100644
--- a/packages/SettingsProvider/res/values/defaults.xml
+++ b/packages/SettingsProvider/res/values/defaults.xml
@@ -40,11 +40,8 @@
     <bool name="def_wifi_display_on">false</bool>
     <bool name="def_install_non_market_apps">false</bool>
     <bool name="def_package_verifier_enable">true</bool>
-    <!-- Comma-separated list of location providers.
-         Network location is off by default because it requires
-         user opt-in via Setup Wizard or Settings.
-    -->
-    <string name="def_location_providers_allowed" translatable="false">gps</string>
+    <!-- Comma-separated list of location providers -->
+    <string name="def_location_providers_allowed" translatable="false">gps,network</string>
     <bool name="assisted_gps_enabled">true</bool>
     <bool name="def_netstats_enabled">true</bool>
     <bool name="def_usb_mass_storage_enabled">true</bool>
diff --git a/packages/SettingsProvider/src/com/android/providers/settings/SettingsProtoDumpUtil.java b/packages/SettingsProvider/src/com/android/providers/settings/SettingsProtoDumpUtil.java
index e843eb4..4f6a4ad 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);
@@ -2377,6 +2391,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/SettingsProvider/src/com/android/providers/settings/SettingsProvider.java b/packages/SettingsProvider/src/com/android/providers/settings/SettingsProvider.java
index 5105ff4..4453121 100644
--- a/packages/SettingsProvider/src/com/android/providers/settings/SettingsProvider.java
+++ b/packages/SettingsProvider/src/com/android/providers/settings/SettingsProvider.java
@@ -3232,7 +3232,7 @@
         }
 
         private final class UpgradeController {
-            private static final int SETTINGS_VERSION = 172;
+            private static final int SETTINGS_VERSION = 173;
 
             private final int mUserId;
 
@@ -4217,6 +4217,41 @@
                     currentVersion = 172;
                 }
 
+                if (currentVersion == 172) {
+                    // Version 172: Set the default value for Secure Settings: LOCATION_MODE
+
+                    final SettingsState secureSettings = getSecureSettingsLocked(userId);
+
+                    final Setting locationMode = secureSettings.getSettingLocked(
+                            Secure.LOCATION_MODE);
+
+                    if (locationMode.isNull()) {
+                        final Setting locationProvidersAllowed = secureSettings.getSettingLocked(
+                                Secure.LOCATION_PROVIDERS_ALLOWED);
+
+                        String defLocationMode = Integer.toString(
+                                !TextUtils.isEmpty(locationProvidersAllowed.getValue())
+                                        ? Secure.LOCATION_MODE_HIGH_ACCURACY
+                                        : Secure.LOCATION_MODE_OFF);
+                        secureSettings.insertSettingLocked(
+                                Secure.LOCATION_MODE, defLocationMode,
+                                null, true, SettingsState.SYSTEM_PACKAGE_NAME);
+
+                        // also reset LOCATION_PROVIDERS_ALLOWED back to the default value - this
+                        // setting is now only for debug/test purposes, and will likely be removed
+                        // in a later release. LocationManagerService is responsible for adjusting
+                        // these settings to the proper state.
+
+                        String defLocationProvidersAllowed = getContext().getResources().getString(
+                                R.string.def_location_providers_allowed);
+                        secureSettings.insertSettingLocked(
+                                Secure.LOCATION_PROVIDERS_ALLOWED, defLocationProvidersAllowed,
+                                null, true, SettingsState.SYSTEM_PACKAGE_NAME);
+                    }
+
+                    currentVersion = 173;
+                }
+
                 // vXXX: Add new settings above this point.
 
                 if (currentVersion != newVersion) {
diff --git a/packages/Shell/AndroidManifest.xml b/packages/Shell/AndroidManifest.xml
index b903142..c3c3f25 100644
--- a/packages/Shell/AndroidManifest.xml
+++ b/packages/Shell/AndroidManifest.xml
@@ -166,6 +166,8 @@
     <uses-permission android:name="android.permission.CONTROL_KEYGUARD" />
     <uses-permission android:name="android.permission.SUSPEND_APPS" />
     <uses-permission android:name="android.permission.READ_CLIPBOARD_IN_BACKGROUND" />
+    <!-- Permission needed to wipe the device for Test Harness Mode -->
+    <uses-permission android:name="android.permission.ENABLE_TEST_HARNESS_MODE" />
 
     <uses-permission android:name="android.permission.MANAGE_APPOPS" />
 
diff --git a/packages/Shell/src/com/android/shell/BugreportProgressService.java b/packages/Shell/src/com/android/shell/BugreportProgressService.java
index 2d7471d..a9ff21f 100644
--- a/packages/Shell/src/com/android/shell/BugreportProgressService.java
+++ b/packages/Shell/src/com/android/shell/BugreportProgressService.java
@@ -1962,8 +1962,7 @@
         }
 
         @Override
-        public void onFinished(long durationMs, String title, String description)
-                throws RemoteException {
+        public void onFinished() throws RemoteException {
             // TODO(b/111441001): implement
         }
 
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/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/config.xml b/packages/SystemUI/res/values/config.xml
index 98f0cbe..f2be2e7 100644
--- a/packages/SystemUI/res/values/config.xml
+++ b/packages/SystemUI/res/values/config.xml
@@ -458,6 +458,11 @@
          heads-up notifications.  -->
     <bool name="config_smart_replies_in_notifications_show_in_heads_up">true</bool>
 
+    <!-- Smart replies in notifications: Minimum number of system generated smart replies that
+         should be shown in a notification. If we cannot show at least this many replies we instead
+         show none. -->
+    <integer name="config_smart_replies_in_notifications_min_num_system_generated_replies">0</integer>
+
     <!-- Screenshot editing default activity.  Must handle ACTION_EDIT image/png intents.
          Blank sends the user to the Chooser first.
          This name is in the ComponentName flattened format (package/class)  -->
diff --git a/packages/SystemUI/res/values/dimens.xml b/packages/SystemUI/res/values/dimens.xml
index ab0bbe1..a14259e 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>
@@ -982,8 +994,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 +1012,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 f384d8f..190bd7a 100644
--- a/packages/SystemUI/res/values/strings.xml
+++ b/packages/SystemUI/res/values/strings.xml
@@ -150,12 +150,21 @@
     <!-- Option to always allow USB debugging from the attached computer -->
     <string name="usb_debugging_always">Always allow from this computer</string>
 
+    <!-- Button label for confirming acceptance of enabling USB debugging [CHAR LIMIT=15] -->
+    <string name="usb_debugging_allow">Allow</string>
+
     <!-- Title of notification shown when trying to enable USB debugging but a secondary user is the current foreground user. -->
     <string name="usb_debugging_secondary_user_title">USB debugging not allowed</string>
 
     <!-- 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>
@@ -2276,7 +2285,7 @@
          app for debugging. Will not be seen by users. [CHAR LIMIT=20] -->
     <string name="heap_dump_tile_name">Dump SysUI Heap</string>
 
-    <!-- Text on chip for multiple apps using a single app op [CHAR LIMIT=10] -->
+    <!-- Text on chip for multiple apps using a single app op [CHAR LIMIT=12] -->
     <plurals name="ongoing_privacy_chip_multiple_apps">
         <item quantity="one"><xliff:g id="num_apps" example="1">%d</xliff:g> app</item>
         <item quantity="few"><xliff:g id="num_apps" example="3">%d</xliff:g> apps</item>
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/keyguard/KeyguardClockSwitch.java b/packages/SystemUI/src/com/android/keyguard/KeyguardClockSwitch.java
index 1aff394..7218acf 100644
--- a/packages/SystemUI/src/com/android/keyguard/KeyguardClockSwitch.java
+++ b/packages/SystemUI/src/com/android/keyguard/KeyguardClockSwitch.java
@@ -1,15 +1,9 @@
 package com.android.keyguard;
 
-import android.content.ContentResolver;
 import android.content.Context;
-import android.database.ContentObserver;
 import android.graphics.Paint;
 import android.graphics.Paint.Style;
-import android.os.Handler;
-import android.os.Looper;
-import android.provider.Settings;
 import android.util.AttributeSet;
-import android.view.LayoutInflater;
 import android.view.View;
 import android.view.ViewGroup;
 import android.widget.FrameLayout;
@@ -18,29 +12,19 @@
 
 import androidx.annotation.VisibleForTesting;
 
-import com.android.keyguard.clock.BubbleClockController;
-import com.android.keyguard.clock.StretchAnalogClockController;
-import com.android.keyguard.clock.TypeClockController;
+import com.android.keyguard.clock.ClockManager;
 import com.android.systemui.Dependency;
 import com.android.systemui.plugins.ClockPlugin;
 import com.android.systemui.statusbar.StatusBarState;
 import com.android.systemui.statusbar.StatusBarStateController;
-import com.android.systemui.statusbar.policy.ExtensionController;
-import com.android.systemui.statusbar.policy.ExtensionController.Extension;
 
-import java.util.Objects;
 import java.util.TimeZone;
-import java.util.function.Consumer;
-import java.util.function.Supplier;
 
 /**
  * Switch to show plugin clock when plugin is connected, otherwise it will show default clock.
  */
 public class KeyguardClockSwitch extends RelativeLayout {
 
-    private LayoutInflater mLayoutInflater;
-
-    private final ContentResolver mContentResolver;
     /**
      * Optional/alternative clock injected via plugin.
      */
@@ -63,14 +47,6 @@
      */
     private View mKeyguardStatusArea;
     /**
-     * Used to select between plugin or default implementations of ClockPlugin interface.
-     */
-    private Extension<ClockPlugin> mClockExtension;
-    /**
-     * Consumer that accepts the a new ClockPlugin implementation when the Extension reloads.
-     */
-    private final Consumer<ClockPlugin> mClockPluginConsumer = plugin -> setClockPlugin(plugin);
-    /**
      * Maintain state so that a newly connected plugin can be initialized.
      */
     private float mDarkAmount;
@@ -94,16 +70,7 @@
                 }
     };
 
-    private final ContentObserver mContentObserver =
-            new ContentObserver(new Handler(Looper.getMainLooper())) {
-                @Override
-                public void onChange(boolean selfChange) {
-                    super.onChange(selfChange);
-                    if (mClockExtension != null) {
-                        mClockExtension.reload();
-                    }
-                }
-    };
+    private ClockManager.ClockChangedListener mClockChangedListener = this::setClockPlugin;
 
     public KeyguardClockSwitch(Context context) {
         this(context, null);
@@ -111,8 +78,6 @@
 
     public KeyguardClockSwitch(Context context, AttributeSet attrs) {
         super(context, attrs);
-        mLayoutInflater = LayoutInflater.from(context);
-        mContentResolver = context.getContentResolver();
     }
 
     /**
@@ -133,45 +98,14 @@
     @Override
     protected void onAttachedToWindow() {
         super.onAttachedToWindow();
-        mClockExtension = Dependency.get(ExtensionController.class).newExtension(ClockPlugin.class)
-                .withPlugin(ClockPlugin.class)
-                .withCallback(mClockPluginConsumer)
-                // Using withDefault even though this isn't the default as a workaround.
-                // ExtensionBulider doesn't provide the ability to supply a ClockPlugin
-                // instance based off of the value of a setting. Since multiple "default"
-                // can be provided, using a supplier that changes the settings value.
-                // A null return will cause Extension#reload to look at the next "default"
-                // supplier.
-                .withDefault(
-                        new SettingsGattedSupplier(
-                                mContentResolver,
-                                Settings.Secure.LOCK_SCREEN_CUSTOM_CLOCK_FACE,
-                                BubbleClockController.class.getName(),
-                                () -> BubbleClockController.build(mLayoutInflater)))
-                .withDefault(
-                        new SettingsGattedSupplier(
-                                mContentResolver,
-                                Settings.Secure.LOCK_SCREEN_CUSTOM_CLOCK_FACE,
-                                StretchAnalogClockController.class.getName(),
-                                () -> StretchAnalogClockController.build(mLayoutInflater)))
-                .withDefault(
-                        new SettingsGattedSupplier(
-                                mContentResolver,
-                                Settings.Secure.LOCK_SCREEN_CUSTOM_CLOCK_FACE,
-                                TypeClockController.class.getName(),
-                                () -> TypeClockController.build(mLayoutInflater)))
-                .build();
-        mContentResolver.registerContentObserver(
-                Settings.Secure.getUriFor(Settings.Secure.LOCK_SCREEN_CUSTOM_CLOCK_FACE),
-                false, mContentObserver);
+        Dependency.get(ClockManager.class).addOnClockChangedListener(mClockChangedListener);
         Dependency.get(StatusBarStateController.class).addCallback(mStateListener);
     }
 
     @Override
     protected void onDetachedFromWindow() {
         super.onDetachedFromWindow();
-        mClockExtension.destroy();
-        mContentResolver.unregisterContentObserver(mContentObserver);
+        Dependency.get(ClockManager.class).removeOnClockChangedListener(mClockChangedListener);
         Dependency.get(StatusBarStateController.class).removeCallback(mStateListener);
     }
 
@@ -313,52 +247,12 @@
     }
 
     @VisibleForTesting (otherwise = VisibleForTesting.NONE)
-    Consumer<ClockPlugin> getClockPluginConsumer() {
-        return mClockPluginConsumer;
+    ClockManager.ClockChangedListener getClockChangedListener() {
+        return mClockChangedListener;
     }
 
     @VisibleForTesting (otherwise = VisibleForTesting.NONE)
     StatusBarStateController.StateListener getStateListener() {
         return mStateListener;
     }
-
-    /**
-     * Supplier that only gets an instance when a settings value matches expected value.
-     */
-    private static class SettingsGattedSupplier implements Supplier<ClockPlugin> {
-
-        private final ContentResolver mContentResolver;
-        private final String mKey;
-        private final String mValue;
-        private final Supplier<ClockPlugin> mSupplier;
-
-        /**
-         * Constructs a supplier that changes secure setting key against value.
-         *
-         * @param contentResolver Used to look up settings value.
-         * @param key Settings key.
-         * @param value If the setting matches this values that get supplies a ClockPlugin
-         *        instance.
-         * @param supplier Supplier of ClockPlugin instance, only used if the setting
-         *        matches value.
-         */
-        SettingsGattedSupplier(ContentResolver contentResolver, String key, String value,
-                Supplier<ClockPlugin> supplier) {
-            mContentResolver = contentResolver;
-            mKey = key;
-            mValue = value;
-            mSupplier = supplier;
-        }
-
-        /**
-         * Returns null if the settings value doesn't match the expected value.
-         *
-         * A null return causes Extension#reload to skip this supplier and move to the next.
-         */
-        @Override
-        public ClockPlugin get() {
-            final String currentValue = Settings.Secure.getString(mContentResolver, mKey);
-            return Objects.equals(currentValue, mValue) ? mSupplier.get() : null;
-        }
-    }
 }
diff --git a/packages/SystemUI/src/com/android/keyguard/clock/ClockManager.java b/packages/SystemUI/src/com/android/keyguard/clock/ClockManager.java
new file mode 100644
index 0000000..3217ca6
--- /dev/null
+++ b/packages/SystemUI/src/com/android/keyguard/clock/ClockManager.java
@@ -0,0 +1,202 @@
+/*
+ * 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.clock;
+
+import android.content.ContentResolver;
+import android.content.Context;
+import android.database.ContentObserver;
+import android.os.Handler;
+import android.os.Looper;
+import android.provider.Settings;
+import android.view.LayoutInflater;
+
+import com.android.systemui.plugins.ClockPlugin;
+import com.android.systemui.statusbar.policy.ExtensionController;
+import com.android.systemui.statusbar.policy.ExtensionController.Extension;
+
+import java.util.ArrayList;
+import java.util.List;
+import java.util.Objects;
+import java.util.function.Consumer;
+import java.util.function.Supplier;
+
+import javax.inject.Inject;
+import javax.inject.Singleton;
+
+/**
+ * Manages custom clock faces.
+ */
+@Singleton
+public final class ClockManager {
+
+    private final LayoutInflater mLayoutInflater;
+    private final ContentResolver mContentResolver;
+
+    /**
+     * Observe settings changes to know when to switch the clock face.
+     */
+    private final ContentObserver mContentObserver =
+            new ContentObserver(new Handler(Looper.getMainLooper())) {
+                @Override
+                public void onChange(boolean selfChange) {
+                    super.onChange(selfChange);
+                    if (mClockExtension != null) {
+                        mClockExtension.reload();
+                    }
+                }
+            };
+
+    private final ExtensionController mExtensionController;
+    /**
+     * Used to select between plugin or default implementations of ClockPlugin interface.
+     */
+    private Extension<ClockPlugin> mClockExtension;
+    /**
+     * Consumer that accepts the a new ClockPlugin implementation when the Extension reloads.
+     */
+    private final Consumer<ClockPlugin> mClockPluginConsumer = this::setClockPlugin;
+
+    private final List<ClockChangedListener> mListeners = new ArrayList<>();
+
+    @Inject
+    public ClockManager(Context context, ExtensionController extensionController) {
+        mExtensionController = extensionController;
+        mLayoutInflater = LayoutInflater.from(context);
+        mContentResolver = context.getContentResolver();
+    }
+
+    /**
+     * Add listener to be notified when clock implementation should change.
+     */
+    public void addOnClockChangedListener(ClockChangedListener listener) {
+        if (mListeners.isEmpty()) {
+            register();
+        }
+        mListeners.add(listener);
+        if (mClockExtension != null) {
+            mClockExtension.reload();
+        }
+    }
+
+    /**
+     * Remove listener added with {@link addOnClockChangedListener}.
+     */
+    public void removeOnClockChangedListener(ClockChangedListener listener) {
+        mListeners.remove(listener);
+        if (mListeners.isEmpty()) {
+            unregister();
+        }
+    }
+
+    private void setClockPlugin(ClockPlugin plugin) {
+        for (int i = 0; i < mListeners.size(); i++) {
+            // It probably doesn't make sense to supply the same plugin instances to multiple
+            // listeners. This should be fine for now since there is only a single listener.
+            mListeners.get(i).onClockChanged(plugin);
+        }
+    }
+
+    private void register() {
+        mContentResolver.registerContentObserver(
+                Settings.Secure.getUriFor(Settings.Secure.LOCK_SCREEN_CUSTOM_CLOCK_FACE),
+                false, mContentObserver);
+        mClockExtension = mExtensionController.newExtension(ClockPlugin.class)
+            .withPlugin(ClockPlugin.class)
+            .withCallback(mClockPluginConsumer)
+            // Using withDefault even though this isn't the default as a workaround.
+            // ExtensionBuilder doesn't provide the ability to supply a ClockPlugin
+            // instance based off of the value of a setting. Since multiple "default"
+            // can be provided, using a supplier that changes the settings value.
+            // A null return will cause Extension#reload to look at the next "default"
+            // supplier.
+            .withDefault(
+                    new SettingsGattedSupplier(
+                        mContentResolver,
+                        Settings.Secure.LOCK_SCREEN_CUSTOM_CLOCK_FACE,
+                        BubbleClockController.class.getName(),
+                            () -> BubbleClockController.build(mLayoutInflater)))
+            .withDefault(
+                    new SettingsGattedSupplier(
+                        mContentResolver,
+                        Settings.Secure.LOCK_SCREEN_CUSTOM_CLOCK_FACE,
+                        StretchAnalogClockController.class.getName(),
+                            () -> StretchAnalogClockController.build(mLayoutInflater)))
+            .withDefault(
+                    new SettingsGattedSupplier(
+                        mContentResolver,
+                        Settings.Secure.LOCK_SCREEN_CUSTOM_CLOCK_FACE,
+                        TypeClockController.class.getName(),
+                            () -> TypeClockController.build(mLayoutInflater)))
+            .build();
+    }
+
+    private void unregister() {
+        mContentResolver.unregisterContentObserver(mContentObserver);
+        mClockExtension.destroy();
+    }
+
+    /**
+     * Listener for events that should cause the custom clock face to change.
+     */
+    public interface ClockChangedListener {
+        /**
+         * Called when custom clock should change.
+         *
+         * @param clock Custom clock face to use. A null value indicates the default clock face.
+         */
+        void onClockChanged(ClockPlugin clock);
+    }
+
+    /**
+     * Supplier that only gets an instance when a settings value matches expected value.
+     */
+    private static class SettingsGattedSupplier implements Supplier<ClockPlugin> {
+
+        private final ContentResolver mContentResolver;
+        private final String mKey;
+        private final String mValue;
+        private final Supplier<ClockPlugin> mSupplier;
+
+        /**
+         * Constructs a supplier that changes secure setting key against value.
+         *
+         * @param contentResolver Used to look up settings value.
+         * @param key Settings key.
+         * @param value If the setting matches this values that get supplies a ClockPlugin
+         *        instance.
+         * @param supplier Supplier of ClockPlugin instance, only used if the setting
+         *        matches value.
+         */
+        SettingsGattedSupplier(ContentResolver contentResolver, String key, String value,
+                Supplier<ClockPlugin> supplier) {
+            mContentResolver = contentResolver;
+            mKey = key;
+            mValue = value;
+            mSupplier = supplier;
+        }
+
+        /**
+         * Returns null if the settings value doesn't match the expected value.
+         *
+         * A null return causes Extension#reload to skip this supplier and move to the next.
+         */
+        @Override
+        public ClockPlugin get() {
+            final String currentValue = Settings.Secure.getString(mContentResolver, mKey);
+            return Objects.equals(currentValue, mValue) ? mSupplier.get() : null;
+        }
+    }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/Dependency.java b/packages/SystemUI/src/com/android/systemui/Dependency.java
index ec6ecc6..fece94e 100644
--- a/packages/SystemUI/src/com/android/systemui/Dependency.java
+++ b/packages/SystemUI/src/com/android/systemui/Dependency.java
@@ -29,6 +29,7 @@
 import com.android.internal.logging.MetricsLogger;
 import com.android.internal.statusbar.IStatusBarService;
 import com.android.internal.util.Preconditions;
+import com.android.keyguard.clock.ClockManager;
 import com.android.settingslib.bluetooth.LocalBluetoothManager;
 import com.android.systemui.appops.AppOpsController;
 import com.android.systemui.assist.AssistManager;
@@ -43,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;
@@ -277,12 +279,14 @@
     @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;
     @Inject @Named(TIME_TICK_HANDLER_NAME) Lazy<Handler> mTimeTickHandler;
     @Nullable
     @Inject @Named(LEAK_REPORT_EMAIL_NAME) Lazy<String> mLeakReportEmail;
+    @Inject Lazy<ClockManager> mClockManager;
 
     @Inject
     public Dependency() {
@@ -449,6 +453,9 @@
         mProviders.put(NotificationAlertingManager.class, mNotificationAlertingManager::get);
         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/ImageWallpaper.java b/packages/SystemUI/src/com/android/systemui/ImageWallpaper.java
index 1d2d7fa..2aecc24 100644
--- a/packages/SystemUI/src/com/android/systemui/ImageWallpaper.java
+++ b/packages/SystemUI/src/com/android/systemui/ImageWallpaper.java
@@ -16,14 +16,18 @@
 
 package com.android.systemui;
 
+import static android.view.Display.DEFAULT_DISPLAY;
+
 import android.app.WallpaperManager;
 import android.content.ComponentCallbacks2;
+import android.content.Context;
 import android.graphics.Bitmap;
 import android.graphics.Canvas;
 import android.graphics.RecordingCanvas;
 import android.graphics.Rect;
 import android.graphics.RectF;
 import android.graphics.Region.Op;
+import android.hardware.display.DisplayManager;
 import android.os.AsyncTask;
 import android.os.Handler;
 import android.os.Trace;
@@ -33,7 +37,6 @@
 import android.view.DisplayInfo;
 import android.view.Surface;
 import android.view.SurfaceHolder;
-import android.view.WindowManager;
 
 import com.android.internal.annotations.VisibleForTesting;
 
@@ -94,7 +97,7 @@
         float mYOffset = 0f;
         float mScale = 1f;
 
-        private Display mDefaultDisplay;
+        private Display mDisplay;
         private final DisplayInfo mTmpDisplayInfo = new DisplayInfo();
 
         boolean mVisible = true;
@@ -138,10 +141,20 @@
             super.onCreate(surfaceHolder);
 
             //noinspection ConstantConditions
-            mDefaultDisplay = getSystemService(WindowManager.class).getDefaultDisplay();
+            final Context displayContext = getDisplayContext();
+            final int displayId = displayContext == null ? DEFAULT_DISPLAY :
+                    displayContext.getDisplayId();
+            DisplayManager dm = getSystemService(DisplayManager.class);
+            if (dm != null) {
+                mDisplay = dm.getDisplay(displayId);
+                if (mDisplay == null) {
+                    Log.e(TAG, "Cannot find display! Fallback to default.");
+                    mDisplay = dm.getDisplay(DEFAULT_DISPLAY);
+                }
+            }
             setOffsetNotificationsEnabled(false);
 
-            updateSurfaceSize(surfaceHolder, getDefaultDisplayInfo(), false /* forDraw */);
+            updateSurfaceSize(surfaceHolder, getDisplayInfo(), false /* forDraw */);
         }
 
         @Override
@@ -165,9 +178,26 @@
                 hasWallpaper = false;
             }
 
-            // Set surface size equal to bitmap size, prevent memory waste
-            int surfaceWidth = Math.max(MIN_BACKGROUND_WIDTH, mBackgroundWidth);
-            int surfaceHeight = Math.max(MIN_BACKGROUND_HEIGHT, mBackgroundHeight);
+            // Expected surface size.
+            int surfaceWidth = Math.max(displayInfo.logicalWidth, mBackgroundWidth);
+            int surfaceHeight = Math.max(displayInfo.logicalHeight, mBackgroundHeight);
+
+            // Calculate the minimum drawing area of the surface, which saves memory and does not
+            // distort the image.
+            final float scale = Math.min(
+                    (float) mBackgroundHeight / (float) surfaceHeight,
+                    (float) mBackgroundWidth / (float) surfaceWidth);
+            surfaceHeight = (int) (scale * surfaceHeight);
+            surfaceWidth = (int) (scale * surfaceWidth);
+
+            // Set surface size to at least MIN size.
+            if (surfaceWidth < MIN_BACKGROUND_WIDTH || surfaceHeight < MIN_BACKGROUND_HEIGHT) {
+                final float scaleUp = Math.max(
+                        (float) MIN_BACKGROUND_WIDTH / (float) surfaceWidth,
+                        (float) MIN_BACKGROUND_HEIGHT / (float) surfaceHeight);
+                surfaceWidth = (int) ((float) surfaceWidth * scaleUp);
+                surfaceHeight = (int) ((float) surfaceHeight * scaleUp);
+            }
 
             // Used a fixed size surface, because we are special.  We can do
             // this because we know the current design of window animations doesn't
@@ -267,8 +297,8 @@
         }
 
         @VisibleForTesting
-        DisplayInfo getDefaultDisplayInfo() {
-            mDefaultDisplay.getDisplayInfo(mTmpDisplayInfo);
+        DisplayInfo getDisplayInfo() {
+            mDisplay.getDisplayInfo(mTmpDisplayInfo);
             return mTmpDisplayInfo;
         }
 
@@ -278,7 +308,7 @@
             }
             try {
                 Trace.traceBegin(Trace.TRACE_TAG_VIEW, "drawWallpaper");
-                DisplayInfo displayInfo = getDefaultDisplayInfo();
+                DisplayInfo displayInfo = getDisplayInfo();
                 int newRotation = displayInfo.rotation;
 
                 // Sometimes a wallpaper is not large enough to cover the screen in one dimension.
@@ -445,7 +475,7 @@
             if (DEBUG) {
                 Log.d(TAG, "Wallpaper loaded: " + mBackground);
             }
-            updateSurfaceSize(getSurfaceHolder(), getDefaultDisplayInfo(),
+            updateSurfaceSize(getSurfaceHolder(), getDisplayInfo(),
                     false /* forDraw */);
         }
 
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/SwipeHelper.java b/packages/SystemUI/src/com/android/systemui/SwipeHelper.java
index 3666400..9efa656 100644
--- a/packages/SystemUI/src/com/android/systemui/SwipeHelper.java
+++ b/packages/SystemUI/src/com/android/systemui/SwipeHelper.java
@@ -604,8 +604,14 @@
                         if (absDelta >= size) {
                             delta = delta > 0 ? maxScrollDistance : -maxScrollDistance;
                         } else {
-                            delta = maxScrollDistance * (float) Math.sin(
-                                    (delta / size) * (Math.PI / 2));
+                            int startPosition = mCallback.getConstrainSwipeStartPosition();
+                            if (absDelta > startPosition) {
+                                int signedStartPosition =
+                                        (int) (startPosition * Math.signum(delta));
+                                delta = signedStartPosition
+                                        + maxScrollDistance * (float) Math.sin(
+                                        ((delta - signedStartPosition) / size) * (Math.PI / 2));
+                            }
                         }
                     }
 
@@ -742,6 +748,14 @@
         float getFalsingThresholdFactor();
 
         /**
+         * @return The position, in pixels, at which a constrained swipe should start being
+         * constrained.
+         */
+        default int getConstrainSwipeStartPosition() {
+            return 0;
+        }
+
+        /**
          * @return If true, the given view is draggable.
          */
         default boolean canChildBeDragged(@NonNull View animView) { return true; }
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 957d772..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);
                 }
@@ -267,22 +260,18 @@
             BubbleView bubble = (BubbleView) mInflater.inflate(
                     R.layout.bubble_view, mStackView, false /* attachToRoot */);
             bubble.setNotif(notif);
-            if (shouldUseActivityView(mContext)) {
-                bubble.setAppOverlayIntent(getAppOverlayIntent(notif));
+            PendingIntent bubbleIntent = getValidBubbleIntent(notif);
+            if (shouldUseActivityView(mContext) || bubbleIntent != null) {
+                bubble.setBubbleIntent(getValidBubbleIntent(notif));
             }
             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();
     }
 
     @Nullable
-    private PendingIntent getAppOverlayIntent(NotificationEntry notif) {
+    private PendingIntent getValidBubbleIntent(NotificationEntry notif) {
         Notification notification = notif.notification.getNotification();
         if (canLaunchInActivityView(notification.getBubbleMetadata() != null
                 ? notification.getBubbleMetadata().getIntent() : null)) {
@@ -422,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 9a11b96..b584f67 100644
--- a/packages/SystemUI/src/com/android/systemui/bubbles/BubbleStackView.java
+++ b/packages/SystemUI/src/com/android/systemui/bubbles/BubbleStackView.java
@@ -16,57 +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;
@@ -108,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();
@@ -118,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 */);
@@ -126,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);
     }
@@ -142,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();
@@ -226,6 +251,19 @@
     }
 
     /**
+     * Sets the entry that should be expanded and expands if needed.
+     */
+    @VisibleForTesting
+    public void setExpandedBubble(NotificationEntry entry) {
+        for (int i = 0; i < mBubbleContainer.getChildCount(); i++) {
+            BubbleView bv = (BubbleView) mBubbleContainer.getChildAt(i);
+            if (entry.equals(bv.getEntry())) {
+                setExpandedBubble(bv);
+            }
+        }
+    }
+
+    /**
      * Adds a bubble to the top of the stack.
      *
      * @param bubbleView the view to add to the stack.
@@ -278,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)) {
@@ -344,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);
         }
     }
 
@@ -386,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;
@@ -405,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) {
@@ -456,28 +626,13 @@
         if (mExpandedBubble.hasAppOverlayIntent()) {
             // Bubble with activity view expanded state
             ActivityView expandedView = mExpandedBubble.getActivityView();
-            expandedView.setLayoutParams(new ViewGroup.LayoutParams(
+            // XXX: gets added to linear layout
+            expandedView.setLayoutParams(new LinearLayout.LayoutParams(
                     ViewGroup.LayoutParams.MATCH_PARENT, mExpandedBubbleHeight));
 
             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();
@@ -494,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() {
@@ -506,7 +660,6 @@
         if (!mIsExpanded) {
             mExpandedViewContainer.setExpandedView(null);
         } else {
-            mExpandedViewContainer.setTranslationY(mBubbleContainer.getHeight());
             View expandedView = mExpandedViewContainer.getExpandedView();
             if (expandedView instanceof ActivityView) {
                 if (expandedView.isAttachedToWindow()) {
@@ -521,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 91893ef..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());
     }
 
     /**
@@ -298,7 +312,7 @@
 
     }
 
-    public void setAppOverlayIntent(PendingIntent intent) {
+    public void setBubbleIntent(PendingIntent intent) {
         mAppOverlayIntent = intent;
     }
 }
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/dock/DockManager.java b/packages/SystemUI/src/com/android/systemui/dock/DockManager.java
index fa5a114..d332f59 100644
--- a/packages/SystemUI/src/com/android/systemui/dock/DockManager.java
+++ b/packages/SystemUI/src/com/android/systemui/dock/DockManager.java
@@ -48,6 +48,11 @@
      */
     void removeListener(DockEventListener callback);
 
+    /**
+    * Returns true if the device is in docking state.
+    */
+    boolean isDocked();
+
     /** Callback for receiving dock events */
     interface DockEventListener {
         /**
diff --git a/packages/SystemUI/src/com/android/systemui/doze/DozeDockHandler.java b/packages/SystemUI/src/com/android/systemui/doze/DozeDockHandler.java
index 9fc2234..5353ee6 100644
--- a/packages/SystemUI/src/com/android/systemui/doze/DozeDockHandler.java
+++ b/packages/SystemUI/src/com/android/systemui/doze/DozeDockHandler.java
@@ -22,7 +22,6 @@
 import android.util.Log;
 
 import com.android.internal.hardware.AmbientDisplayConfiguration;
-import com.android.systemui.SysUiServiceProvider;
 import com.android.systemui.dock.DockManager;
 import com.android.systemui.doze.DozeMachine.State;
 
@@ -46,12 +45,12 @@
     private int mDockState = DockManager.STATE_NONE;
 
     public DozeDockHandler(Context context, DozeMachine machine, DozeHost dozeHost,
-            AmbientDisplayConfiguration config, Handler handler) {
+            AmbientDisplayConfiguration config, Handler handler, DockManager dockManager) {
         mMachine = machine;
         mDozeHost = dozeHost;
         mConfig = config;
         mHandler = handler;
-        mDockManager = SysUiServiceProvider.getComponent(context, DockManager.class);
+        mDockManager = dockManager;
     }
 
     @Override
diff --git a/packages/SystemUI/src/com/android/systemui/doze/DozeFactory.java b/packages/SystemUI/src/com/android/systemui/doze/DozeFactory.java
index 58ae555..e338a34 100644
--- a/packages/SystemUI/src/com/android/systemui/doze/DozeFactory.java
+++ b/packages/SystemUI/src/com/android/systemui/doze/DozeFactory.java
@@ -27,8 +27,10 @@
 import com.android.keyguard.KeyguardUpdateMonitor;
 import com.android.systemui.Dependency;
 import com.android.systemui.R;
+import com.android.systemui.SysUiServiceProvider;
 import com.android.systemui.SystemUIApplication;
 import com.android.systemui.classifier.FalsingManager;
+import com.android.systemui.dock.DockManager;
 import com.android.systemui.statusbar.phone.DozeParameters;
 import com.android.systemui.util.AsyncSensorManager;
 import com.android.systemui.util.wakelock.DelayedWakeLock;
@@ -44,6 +46,7 @@
         Context context = dozeService;
         SensorManager sensorManager = Dependency.get(AsyncSensorManager.class);
         AlarmManager alarmManager = context.getSystemService(AlarmManager.class);
+        DockManager dockManager = SysUiServiceProvider.getComponent(context, DockManager.class);
 
         DozeHost host = getHost(dozeService);
         AmbientDisplayConfiguration config = new AmbientDisplayConfiguration(context);
@@ -63,13 +66,13 @@
                 new DozePauser(handler, machine, alarmManager, params.getPolicy()),
                 new DozeFalsingManagerAdapter(FalsingManager.getInstance(context)),
                 createDozeTriggers(context, sensorManager, host, alarmManager, config, params,
-                        handler, wakeLock, machine),
+                        handler, wakeLock, machine, dockManager),
                 createDozeUi(context, host, wakeLock, machine, handler, alarmManager, params),
                 new DozeScreenState(wrappedService, handler, params, wakeLock),
                 createDozeScreenBrightness(context, wrappedService, sensorManager, host, params,
                         handler),
                 new DozeWallpaperState(context),
-                new DozeDockHandler(context, machine, host, config, handler)
+                new DozeDockHandler(context, machine, host, config, handler, dockManager)
         });
 
         return machine;
@@ -86,10 +89,11 @@
 
     private DozeTriggers createDozeTriggers(Context context, SensorManager sensorManager,
             DozeHost host, AlarmManager alarmManager, AmbientDisplayConfiguration config,
-            DozeParameters params, Handler handler, WakeLock wakeLock, DozeMachine machine) {
+            DozeParameters params, Handler handler, WakeLock wakeLock, DozeMachine machine,
+            DockManager dockManager) {
         boolean allowPulseTriggers = true;
         return new DozeTriggers(context, machine, host, alarmManager, config, params,
-                sensorManager, handler, wakeLock, allowPulseTriggers);
+                sensorManager, handler, wakeLock, allowPulseTriggers, dockManager);
     }
 
     private DozeMachine.Part createDozeUi(Context context, DozeHost host, WakeLock wakeLock,
diff --git a/packages/SystemUI/src/com/android/systemui/doze/DozeSensors.java b/packages/SystemUI/src/com/android/systemui/doze/DozeSensors.java
index 78374a0..562edd6 100644
--- a/packages/SystemUI/src/com/android/systemui/doze/DozeSensors.java
+++ b/packages/SystemUI/src/com/android/systemui/doze/DozeSensors.java
@@ -214,6 +214,15 @@
         mPickupSensor.setDisabled(disable);
     }
 
+    /** Ignore the setting value of only the sensors that require the touchscreen. */
+    public void ignoreTouchScreenSensorsSettingInterferingWithDocking(boolean ignore) {
+        for (TriggerSensor sensor : mSensors) {
+            if (sensor.mRequiresTouchscreen) {
+                sensor.ignoreSetting(ignore);
+            }
+        }
+    }
+
     /** Dump current state */
     public void dump(PrintWriter pw) {
         for (TriggerSensor s : mSensors) {
@@ -323,6 +332,7 @@
         protected boolean mRequested;
         protected boolean mRegistered;
         protected boolean mDisabled;
+        protected boolean mIgnoresSetting;
 
         public TriggerSensor(Sensor sensor, String setting, boolean configured, int pulseReason,
                 boolean reportsTouchCoordinates, boolean requiresTouchscreen) {
@@ -333,6 +343,13 @@
         public TriggerSensor(Sensor sensor, String setting, boolean settingDef,
                 boolean configured, int pulseReason, boolean reportsTouchCoordinates,
                 boolean requiresTouchscreen) {
+            this(sensor, setting, settingDef, configured, pulseReason, reportsTouchCoordinates,
+                    requiresTouchscreen, false /* ignoresSetting */);
+        }
+
+        private TriggerSensor(Sensor sensor, String setting, boolean settingDef,
+                boolean configured, int pulseReason, boolean reportsTouchCoordinates,
+                boolean requiresTouchscreen, boolean ignoresSetting) {
             mSensor = sensor;
             mSetting = setting;
             mSettingDefault = settingDef;
@@ -340,6 +357,7 @@
             mPulseReason = pulseReason;
             mReportsTouchCoordinates = reportsTouchCoordinates;
             mRequiresTouchscreen = requiresTouchscreen;
+            mIgnoresSetting = ignoresSetting;
         }
 
         public void setListening(boolean listen) {
@@ -354,9 +372,16 @@
             updateListener();
         }
 
+        public void ignoreSetting(boolean ignored) {
+            if (mIgnoresSetting == ignored) return;
+            mIgnoresSetting = ignored;
+            updateListener();
+        }
+
         public void updateListener() {
             if (!mConfigured || mSensor == null) return;
-            if (mRequested && !mDisabled && enabledBySetting() && !mRegistered) {
+            if (mRequested && !mDisabled && (enabledBySetting() || mIgnoresSetting)
+                    && !mRegistered) {
                 mRegistered = mSensorManager.requestTriggerSensor(this, mSensor);
                 if (DEBUG) Log.d(TAG, "requestTriggerSensor " + mRegistered);
             } else if (mRegistered) {
@@ -382,6 +407,7 @@
                     .append(", mRequested=").append(mRequested)
                     .append(", mDisabled=").append(mDisabled)
                     .append(", mConfigured=").append(mConfigured)
+                    .append(", mIgnoresSetting=").append(mIgnoresSetting)
                     .append(", mSensor=").append(mSensor).append("}").toString();
         }
 
@@ -464,7 +490,8 @@
         public void updateListener() {
             if (!mConfigured) return;
             AsyncSensorManager asyncSensorManager = (AsyncSensorManager) mSensorManager;
-            if (mRequested && !mDisabled && enabledBySetting() && !mRegistered) {
+            if (mRequested && !mDisabled && (enabledBySetting() || mIgnoresSetting)
+                    && !mRegistered) {
                 asyncSensorManager.registerPluginListener(mPluginSensor, mTriggerEventListener);
                 mRegistered = true;
                 if (DEBUG) Log.d(TAG, "registerPluginListener");
@@ -481,6 +508,7 @@
                     .append(", mRequested=").append(mRequested)
                     .append(", mDisabled=").append(mDisabled)
                     .append(", mConfigured=").append(mConfigured)
+                    .append(", mIgnoresSetting=").append(mIgnoresSetting)
                     .append(", mSensor=").append(mPluginSensor).append("}").toString();
         }
 
diff --git a/packages/SystemUI/src/com/android/systemui/doze/DozeTriggers.java b/packages/SystemUI/src/com/android/systemui/doze/DozeTriggers.java
index e2e448b..dc505b5 100644
--- a/packages/SystemUI/src/com/android/systemui/doze/DozeTriggers.java
+++ b/packages/SystemUI/src/com/android/systemui/doze/DozeTriggers.java
@@ -33,8 +33,10 @@
 import android.text.format.Formatter;
 import android.util.Log;
 
+import com.android.internal.annotations.VisibleForTesting;
 import com.android.internal.hardware.AmbientDisplayConfiguration;
 import com.android.internal.util.Preconditions;
+import com.android.systemui.dock.DockManager;
 import com.android.systemui.statusbar.phone.DozeParameters;
 import com.android.systemui.util.Assert;
 import com.android.systemui.util.wakelock.WakeLock;
@@ -71,6 +73,8 @@
     private final boolean mAllowPulseTriggers;
     private final UiModeManager mUiModeManager;
     private final TriggerReceiver mBroadcastReceiver = new TriggerReceiver();
+    private final DockEventListener mDockEventListener = new DockEventListener();
+    private final DockManager mDockManager;
 
     private long mNotificationPulseTime;
     private boolean mPulsePending;
@@ -79,7 +83,7 @@
     public DozeTriggers(Context context, DozeMachine machine, DozeHost dozeHost,
             AlarmManager alarmManager, AmbientDisplayConfiguration config,
             DozeParameters dozeParameters, SensorManager sensorManager, Handler handler,
-            WakeLock wakeLock, boolean allowPulseTriggers) {
+            WakeLock wakeLock, boolean allowPulseTriggers, DockManager dockManager) {
         mContext = context;
         mMachine = machine;
         mDozeHost = dozeHost;
@@ -93,6 +97,7 @@
                 config, wakeLock, this::onSensor, this::onProximityFar,
                 dozeParameters.getPolicy());
         mUiModeManager = mContext.getSystemService(UiModeManager.class);
+        mDockManager = dockManager;
     }
 
     private void onNotification() {
@@ -129,7 +134,8 @@
         }
     }
 
-    private void onSensor(int pulseReason, boolean sensorPerformedProxCheck,
+    @VisibleForTesting
+    void onSensor(int pulseReason, boolean sensorPerformedProxCheck,
             float screenX, float screenY, float[] rawValues) {
         boolean isDoubleTap = pulseReason == DozeLog.PULSE_REASON_SENSOR_DOUBLE_TAP;
         boolean isTap = pulseReason == DozeLog.PULSE_REASON_SENSOR_TAP;
@@ -159,7 +165,7 @@
                 } else {
                     mDozeHost.extendPulse();
                 }
-            }, sensorPerformedProxCheck, pulseReason);
+            }, sensorPerformedProxCheck || mDockManager.isDocked(), pulseReason);
         }
 
         if (isPickup) {
@@ -223,6 +229,7 @@
             case INITIALIZED:
                 mBroadcastReceiver.register(mContext);
                 mDozeHost.addCallback(mHostCallback);
+                mDockManager.addListener(mDockEventListener);
                 checkTriggersAtInit();
                 break;
             case DOZE:
@@ -248,6 +255,7 @@
             case FINISH:
                 mBroadcastReceiver.unregister(mContext);
                 mDozeHost.removeCallback(mHostCallback);
+                mDockManager.removeListener(mDockEventListener);
                 mDozeSensors.setListening(false);
                 mDozeSensors.setProxListening(false);
                 break;
@@ -423,6 +431,24 @@
         }
     }
 
+    private class DockEventListener implements DockManager.DockEventListener {
+        @Override
+        public void onEvent(int event) {
+            if (DEBUG) Log.d(TAG, "dock event = " + event);
+            switch (event) {
+                case DockManager.STATE_DOCKED:
+                case DockManager.STATE_DOCKED_HIDE:
+                    mDozeSensors.ignoreTouchScreenSensorsSettingInterferingWithDocking(true);
+                    break;
+                case DockManager.STATE_NONE:
+                    mDozeSensors.ignoreTouchScreenSensorsSettingInterferingWithDocking(false);
+                    break;
+                default:
+                    // no-op
+            }
+        }
+    }
+
     private DozeHost.Callback mHostCallback = new DozeHost.Callback() {
         @Override
         public void onNotificationAlerted() {
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 77e25e3..26c6d50 100644
--- a/packages/SystemUI/src/com/android/systemui/privacy/OngoingPrivacyDialog.kt
+++ b/packages/SystemUI/src/com/android/systemui/privacy/OngoingPrivacyDialog.kt
@@ -30,6 +30,7 @@
 import com.android.systemui.Dependency
 import com.android.systemui.R
 import com.android.systemui.plugins.ActivityStarter
+import java.util.concurrent.TimeUnit
 
 class OngoingPrivacyDialog constructor(
     val context: Context,
@@ -60,7 +61,8 @@
             setNegativeButton(R.string.ongoing_privacy_dialog_cancel, null)
             setPositiveButton(R.string.ongoing_privacy_dialog_open_settings,
                     object : DialogInterface.OnClickListener {
-                        val intent = Intent(Intent.ACTION_REVIEW_PERMISSION_USAGE)
+                        val intent = Intent(Intent.ACTION_REVIEW_PERMISSION_USAGE).putExtra(
+                                Intent.EXTRA_DURATION_MILLIS, TimeUnit.MINUTES.toMillis(1))
 
                         @Suppress("DEPRECATION")
                         override fun onClick(dialog: DialogInterface?, which: 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..2339fae 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,
@@ -56,9 +60,10 @@
     private val uiHandler = Dependency.get(Dependency.MAIN_HANDLER)
     private var listening = false
     val systemApp = PrivacyApplication(context.getString(R.string.device_services), 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 +93,8 @@
             registerReceiver()
         }
 
-    init {
-        registerReceiver()
+    private fun unregisterReceiver() {
+        context.unregisterReceiver(userSwitcherReceiver)
     }
 
     private fun registerReceiver() {
@@ -108,17 +113,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()
@@ -149,4 +178,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/CellularTile.java b/packages/SystemUI/src/com/android/systemui/qs/tiles/CellularTile.java
index 1155a41..e1becdb 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/tiles/CellularTile.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/CellularTile.java
@@ -188,7 +188,7 @@
             state.secondaryLabel = r.getString(R.string.status_bar_airplane);
         } else if (mobileDataEnabled) {
             state.state = Tile.STATE_ACTIVE;
-            state.secondaryLabel = getMobileDataDescription(cb);
+            state.secondaryLabel = getMobileDataSubscriptionName(cb);
         } else {
             state.state = Tile.STATE_INACTIVE;
             state.secondaryLabel = r.getString(R.string.cell_data_off);
@@ -207,16 +207,16 @@
         state.contentDescription = state.label + ", " + contentDescriptionSuffix;
     }
 
-    private CharSequence getMobileDataDescription(CallbackInfo cb) {
-        if (cb.roaming && !TextUtils.isEmpty(cb.dataContentDescription)) {
+    private CharSequence getMobileDataSubscriptionName(CallbackInfo cb) {
+        if (cb.roaming && !TextUtils.isEmpty(cb.dataSubscriptionName)) {
             String roaming = mContext.getString(R.string.data_connection_roaming);
-            String dataDescription = cb.dataContentDescription;
+            String dataDescription = cb.dataSubscriptionName.toString();
             return mContext.getString(R.string.mobile_data_text_format, roaming, dataDescription);
         }
         if (cb.roaming) {
             return mContext.getString(R.string.data_connection_roaming);
         }
-        return cb.dataContentDescription;
+        return cb.dataSubscriptionName;
     }
 
     @Override
@@ -231,7 +231,7 @@
 
     private static final class CallbackInfo {
         boolean airplaneModeEnabled;
-        String dataContentDescription;
+        CharSequence dataSubscriptionName;
         boolean activityIn;
         boolean activityOut;
         boolean noSim;
@@ -249,7 +249,7 @@
                 // Not data sim, don't display.
                 return;
             }
-            mInfo.dataContentDescription = typeContentDescription;
+            mInfo.dataSubscriptionName = mController.getMobileDataNetworkName();
             mInfo.activityIn = activityIn;
             mInfo.activityOut = activityOut;
             mInfo.roaming = roaming;
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/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/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 d5f4d04..4f9d428 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/NotificationLockscreenUserManagerImpl.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/NotificationLockscreenUserManagerImpl.java
@@ -49,11 +49,14 @@
 import com.android.systemui.statusbar.notification.NotificationEntryManager;
 import com.android.systemui.statusbar.notification.NotificationUtils;
 import com.android.systemui.statusbar.notification.collection.NotificationEntry;
+import com.android.systemui.statusbar.notification.logging.NotificationLogger;
 import com.android.systemui.statusbar.policy.DeviceProvisionedController;
 import com.android.systemui.statusbar.policy.KeyguardMonitor;
 
 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
@@ -77,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;
@@ -111,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)) {
@@ -130,8 +138,11 @@
                     final int count =
                             getEntryManager().getNotificationData().getActiveNotifications().size();
                     final int rank = getEntryManager().getNotificationData().getRank(notificationKey);
+                    NotificationVisibility.NotificationLocation location =
+                            NotificationLogger.getNotificationLocation(
+                                    getEntryManager().getNotificationData().get(notificationKey));
                     final NotificationVisibility nv = NotificationVisibility.obtain(notificationKey,
-                            rank, count, true);
+                            rank, count, true, location);
                     try {
                         mBarService.onNotificationClick(notificationKey, nv);
                     } catch (RemoteException e) {
@@ -498,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
@@ -537,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/NotificationRemoteInputManager.java b/packages/SystemUI/src/com/android/systemui/statusbar/NotificationRemoteInputManager.java
index 7d6231f..31d1621 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/NotificationRemoteInputManager.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/NotificationRemoteInputManager.java
@@ -54,6 +54,7 @@
 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.logging.NotificationLogger;
 import com.android.systemui.statusbar.notification.row.ExpandableNotificationRow;
 import com.android.systemui.statusbar.phone.ShadeController;
 import com.android.systemui.statusbar.policy.RemoteInputView;
@@ -181,7 +182,11 @@
             final int rank = mEntryManager.getNotificationData().getRank(key);
             final Notification.Action action =
                     statusBarNotification.getNotification().actions[actionIndex];
-            final NotificationVisibility nv = NotificationVisibility.obtain(key, rank, count, true);
+            NotificationVisibility.NotificationLocation location =
+                    NotificationLogger.getNotificationLocation(
+                            mEntryManager.getNotificationData().get(key));
+            final NotificationVisibility nv =
+                    NotificationVisibility.obtain(key, rank, count, true, location);
             try {
                 mBarService.onNotificationActionClick(key, buttonIndex, action, nv, false);
             } catch (RemoteException e) {
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/NotificationShelf.java b/packages/SystemUI/src/com/android/systemui/statusbar/NotificationShelf.java
index 9ef9c94..546b2e9 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/NotificationShelf.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/NotificationShelf.java
@@ -538,6 +538,7 @@
                     - getIntrinsicHeight());
         }
         float viewEnd = viewStart + fullHeight;
+        // TODO: fix this check for anchor scrolling.
         if (expandingAnimated && mAmbientState.getScrollY() == 0
                 && !mAmbientState.isOnKeyguard() && !iconState.isLastExpandIcon) {
             // We are expanding animated. Because we switch to a linear interpolation in this case,
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/SmartReplyController.java b/packages/SystemUI/src/com/android/systemui/statusbar/SmartReplyController.java
index 573c1f8..a2abcd2 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/SmartReplyController.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/SmartReplyController.java
@@ -23,6 +23,7 @@
 import com.android.internal.statusbar.NotificationVisibility;
 import com.android.systemui.statusbar.notification.NotificationEntryManager;
 import com.android.systemui.statusbar.notification.collection.NotificationEntry;
+import com.android.systemui.statusbar.notification.logging.NotificationLogger;
 
 import java.util.Set;
 
@@ -74,8 +75,10 @@
             boolean generatedByAssistant) {
         final int count = mEntryManager.getNotificationData().getActiveNotifications().size();
         final int rank = mEntryManager.getNotificationData().getRank(entry.key);
-        final NotificationVisibility nv =
-                NotificationVisibility.obtain(entry.key, rank, count, true);
+        NotificationVisibility.NotificationLocation location =
+                NotificationLogger.getNotificationLocation(entry);
+        final NotificationVisibility nv = NotificationVisibility.obtain(
+                entry.key, rank, count, true, location);
         try {
             mBarService.onNotificationActionClick(
                     entry.key, actionIndex, action, nv, generatedByAssistant);
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/NotificationEntryManager.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/NotificationEntryManager.java
index 989e781..ef5e936 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/NotificationEntryManager.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/NotificationEntryManager.java
@@ -36,6 +36,7 @@
 import com.android.systemui.statusbar.notification.collection.NotificationData;
 import com.android.systemui.statusbar.notification.collection.NotificationData.KeyguardEnvironment;
 import com.android.systemui.statusbar.notification.collection.NotificationEntry;
+import com.android.systemui.statusbar.notification.logging.NotificationLogger;
 import com.android.systemui.statusbar.notification.row.NotificationInflater;
 import com.android.systemui.statusbar.notification.row.NotificationInflater.InflationFlag;
 import com.android.systemui.statusbar.notification.stack.NotificationListContainer;
@@ -160,8 +161,10 @@
     public void performRemoveNotification(StatusBarNotification n) {
         final int rank = mNotificationData.getRank(n.getKey());
         final int count = mNotificationData.getActiveNotifications().size();
+        NotificationVisibility.NotificationLocation location =
+                NotificationLogger.getNotificationLocation(getNotificationData().get(n.getKey()));
         final NotificationVisibility nv = NotificationVisibility.obtain(n.getKey(), rank, count,
-                true);
+                true, location);
         removeNotificationInternal(
                 n.getKey(), null, nv, false /* forceRemove */, true /* removedByUser */);
     }
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 5ba9b4b..35b7ef4 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,6 +39,8 @@
 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;
 
@@ -127,7 +129,8 @@
                 NotificationEntry entry = activeNotifications.get(i);
                 String key = entry.notification.getKey();
                 boolean isVisible = mListContainer.isInVisibleLocation(entry);
-                NotificationVisibility visObj = NotificationVisibility.obtain(key, i, N, isVisible);
+                NotificationVisibility visObj = NotificationVisibility.obtain(key, i, N, isVisible,
+                        getNotificationLocation(entry));
                 boolean previouslyVisible = mCurrentlyVisibleNotifications.contains(visObj);
                 if (isVisible) {
                     // Build new set of visible notifications.
@@ -159,6 +162,37 @@
         }
     };
 
+    /**
+     * Returns the location of the notification referenced by the given {@link NotificationEntry}.
+     */
+    public static NotificationVisibility.NotificationLocation getNotificationLocation(
+            NotificationEntry entry) {
+        if (entry == null || entry.getRow() == null || entry.getRow().getViewState() == null) {
+            return NotificationVisibility.NotificationLocation.LOCATION_UNKNOWN;
+        }
+        return convertNotificationLocation(entry.getRow().getViewState().location);
+    }
+
+    private static NotificationVisibility.NotificationLocation convertNotificationLocation(
+            int location) {
+        switch (location) {
+            case ExpandableViewState.LOCATION_FIRST_HUN:
+                return NotificationVisibility.NotificationLocation.LOCATION_FIRST_HEADS_UP;
+            case ExpandableViewState.LOCATION_HIDDEN_TOP:
+                return NotificationVisibility.NotificationLocation.LOCATION_HIDDEN_TOP;
+            case ExpandableViewState.LOCATION_MAIN_AREA:
+                return NotificationVisibility.NotificationLocation.LOCATION_MAIN_AREA;
+            case ExpandableViewState.LOCATION_BOTTOM_STACK_PEEKING:
+                return NotificationVisibility.NotificationLocation.LOCATION_BOTTOM_STACK_PEEKING;
+            case ExpandableViewState.LOCATION_BOTTOM_STACK_HIDDEN:
+                return NotificationVisibility.NotificationLocation.LOCATION_BOTTOM_STACK_HIDDEN;
+            case ExpandableViewState.LOCATION_GONE:
+                return NotificationVisibility.NotificationLocation.LOCATION_GONE;
+            default:
+                return NotificationVisibility.NotificationLocation.LOCATION_UNKNOWN;
+        }
+    }
+
     @Inject
     public NotificationLogger(NotificationListener notificationListener,
             UiOffloadThread uiOffloadThread,
@@ -362,7 +396,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);
     }
 
     /**
@@ -396,10 +432,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);
         }
 
@@ -415,6 +453,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) {
@@ -459,8 +498,8 @@
             final State stateToBeLogged = new State(state);
             mUiOffloadThread.submit(() -> {
                 try {
-                    mBarService.onNotificationExpansionChanged(
-                            key, stateToBeLogged.mIsUserAction, stateToBeLogged.mIsExpanded);
+                    mBarService.onNotificationExpansionChanged(key, stateToBeLogged.mIsUserAction,
+                            stateToBeLogged.mIsExpanded, stateToBeLogged.mLocation.ordinal());
                 } catch (RemoteException e) {
                     Log.e(TAG, "Failed to call onNotificationExpansionChanged: ", e);
                 }
@@ -474,6 +513,8 @@
             Boolean mIsExpanded;
             @Nullable
             Boolean mIsVisible;
+            @Nullable
+            NotificationVisibility.NotificationLocation mLocation;
 
             private State() {}
 
@@ -481,10 +522,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/AmbientState.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/AmbientState.java
index 1b5013d..c246af5 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/AmbientState.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/AmbientState.java
@@ -42,6 +42,8 @@
 
     private ArrayList<ExpandableView> mDraggedViews = new ArrayList<>();
     private int mScrollY;
+    private int mAnchorViewIndex;
+    private int mAnchorViewY;
     private boolean mDimmed;
     private ActivatableNotificationView mActivatedChild;
     private float mOverScrollTopAmount;
@@ -130,6 +132,27 @@
         this.mScrollY = scrollY;
     }
 
+    /**
+     * Index of the child view whose Y position on screen is returned by {@link #getAnchorViewY()}.
+     * Other views are laid out outwards from this view in both directions.
+     */
+    public int getAnchorViewIndex() {
+        return mAnchorViewIndex;
+    }
+
+    public void setAnchorViewIndex(int anchorViewIndex) {
+        mAnchorViewIndex = anchorViewIndex;
+    }
+
+    /** Current Y position of the view at {@link #getAnchorViewIndex()}. */
+    public int getAnchorViewY() {
+        return mAnchorViewY;
+    }
+
+    public void setAnchorViewY(int anchorViewY) {
+        mAnchorViewY = anchorViewY;
+    }
+
     /** Call when dragging begins. */
     public void onBeginDrag(ExpandableView view) {
         mDraggedViews.add(view);
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 d066567..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
@@ -18,6 +18,7 @@
 
 import static com.android.systemui.Dependency.ALLOW_NOTIFICATION_LONG_PRESS_NAME;
 import static com.android.systemui.statusbar.notification.ActivityLaunchAnimator.ExpandAnimationParameters;
+import static com.android.systemui.statusbar.notification.stack.StackScrollAlgorithm.ANCHOR_SCROLLING;
 import static com.android.systemui.statusbar.notification.stack.StackStateAnimator.ANIMATION_DURATION_SWIPE;
 import static com.android.systemui.statusbar.phone.NotificationIconAreaController.LOW_PRIORITY;
 import static com.android.systemui.util.InjectionInflationController.VIEW_CONTEXT;
@@ -173,13 +174,22 @@
     private final boolean mShouldDrawNotificationBackground;
     private boolean mLowPriorityBeforeSpeedBump;
     private final boolean mAllowLongPress;
+    private boolean mDismissRtl;
 
     private float mExpandedHeight;
     private int mOwnScrollY;
+    private View mScrollAnchorView;
+    private int mScrollAnchorViewY;
     private int mMaxLayoutHeight;
 
     private VelocityTracker mVelocityTracker;
     private OverScroller mScroller;
+    /** Last Y position reported by {@link #mScroller}, used to calculate scroll delta. */
+    private int mLastScrollerY;
+    /**
+     * True if the max position was set to a known position on the last call to {@link #mScroller}.
+     */
+    private boolean mIsScrollerBoundSet;
     private Runnable mFinishScrollingCallback;
     private int mTouchSlop;
     private int mMinimumVelocity;
@@ -417,7 +427,12 @@
     private int mStatusBarState;
     private int mCachedBackgroundColor;
     private boolean mHeadsUpGoingAwayAnimationsAllowed = true;
-    private Runnable mAnimateScroll = this::animateScroll;
+    private Runnable mReflingAndAnimateScroll = () -> {
+        if (ANCHOR_SCROLLING) {
+            maybeReflingScroller();
+        }
+        animateScroll();
+    };
     private int mCornerRadius;
     private int mSidePaddings;
     private final Rect mBackgroundAnimationRect = new Rect();
@@ -511,6 +526,7 @@
             mDebugPaint.setColor(0xffff0000);
             mDebugPaint.setStrokeWidth(2);
             mDebugPaint.setStyle(Paint.Style.STROKE);
+            mDebugPaint.setTextSize(25f);
         }
         mClearAllEnabled = res.getBoolean(R.bool.config_enableNotificationsClearAll);
 
@@ -518,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
@@ -533,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() {
@@ -674,6 +702,30 @@
         }
     }
 
+    @Override
+    public void draw(Canvas canvas) {
+        super.draw(canvas);
+
+        if (DEBUG && ANCHOR_SCROLLING) {
+            if (mScrollAnchorView instanceof ExpandableNotificationRow) {
+                canvas.drawRect(0,
+                        mScrollAnchorView.getTranslationY(),
+                        getWidth(),
+                        mScrollAnchorView.getTranslationY()
+                                + ((ExpandableNotificationRow) mScrollAnchorView).getActualHeight(),
+                        mDebugPaint);
+                canvas.drawText(Integer.toString(mScrollAnchorViewY), getWidth() - 200,
+                        mScrollAnchorView.getTranslationY() + 30, mDebugPaint);
+                int y = (int) mShelf.getTranslationY();
+                canvas.drawLine(0, y, getWidth(), y, mDebugPaint);
+            }
+            canvas.drawText(Integer.toString(getMaxNegativeScrollAmount()), getWidth() - 100,
+                    getIntrinsicPadding() + 30, mDebugPaint);
+            canvas.drawText(Integer.toString(getMaxPositiveScrollAmount()), getWidth() - 100,
+                    getHeight() - 30, mDebugPaint);
+        }
+    }
+
     @ShadeViewRefactor(RefactorComponent.DECORATOR)
     private void drawBackground(Canvas canvas) {
         int lockScreenLeft = mSidePaddings;
@@ -970,7 +1022,12 @@
         mAmbientState.setCurrentScrollVelocity(mScroller.isFinished()
                 ? 0
                 : mScroller.getCurrVelocity());
-        mAmbientState.setScrollY(mOwnScrollY);
+        if (ANCHOR_SCROLLING) {
+            mAmbientState.setAnchorViewIndex(indexOfChild(mScrollAnchorView));
+            mAmbientState.setAnchorViewY(mScrollAnchorViewY);
+        } else {
+            mAmbientState.setScrollY(mOwnScrollY);
+        }
         mStackScrollAlgorithm.resetViewStates(mAmbientState);
         if (!isCurrentlyAnimating() && !mNeedsAnimation) {
             applyCurrentState();
@@ -1004,7 +1061,7 @@
             float end = start + child.getActualHeight();
             boolean clip = clipStart > start && clipStart < end
                     || clipEnd >= start && clipEnd <= end;
-            clip &= !(first && mOwnScrollY == 0);
+            clip &= !(first && isScrolledToTop());
             child.setDistanceToTopRoundness(clip ? Math.max(start - clipStart, 0)
                     : ExpandableView.NO_ROUNDNESS);
             first = false;
@@ -1016,19 +1073,21 @@
         if (mChildrenToAddAnimated.isEmpty()) {
             return;
         }
-        for (int i = 0; i < getChildCount(); i++) {
-            ExpandableView child = (ExpandableView) getChildAt(i);
-            if (mChildrenToAddAnimated.contains(child)) {
-                int startingPosition = getPositionInLinearLayout(child);
-                float increasedPaddingAmount = child.getIncreasedPaddingAmount();
-                int padding = increasedPaddingAmount == 1.0f ? mIncreasedPaddingBetweenElements
-                        : increasedPaddingAmount == -1.0f ? 0 : mPaddingBetweenElements;
-                int childHeight = getIntrinsicHeight(child) + padding;
-                if (startingPosition < mOwnScrollY) {
-                    // This child starts off screen, so let's keep it offscreen to keep the
-                    // others visible
+        if (!ANCHOR_SCROLLING) {
+            for (int i = 0; i < getChildCount(); i++) {
+                ExpandableView child = (ExpandableView) getChildAt(i);
+                if (mChildrenToAddAnimated.contains(child)) {
+                    int startingPosition = getPositionInLinearLayout(child);
+                    float increasedPaddingAmount = child.getIncreasedPaddingAmount();
+                    int padding = increasedPaddingAmount == 1.0f ? mIncreasedPaddingBetweenElements
+                            : increasedPaddingAmount == -1.0f ? 0 : mPaddingBetweenElements;
+                    int childHeight = getIntrinsicHeight(child) + padding;
+                    if (startingPosition < mOwnScrollY) {
+                        // This child starts off screen, so let's keep it offscreen to keep the
+                        // others visible
 
-                    setOwnScrollY(mOwnScrollY + childHeight);
+                        setOwnScrollY(mOwnScrollY + childHeight);
+                    }
                 }
             }
         }
@@ -1047,12 +1106,16 @@
             int targetScroll = targetScrollForView(expandableView, positionInLinearLayout);
             int outOfViewScroll = positionInLinearLayout + expandableView.getIntrinsicHeight();
 
-            targetScroll = Math.max(0, Math.min(targetScroll, getScrollRange()));
+            if (ANCHOR_SCROLLING) {
+                // TODO
+            } else {
+                targetScroll = Math.max(0, Math.min(targetScroll, getScrollRange()));
 
-            // Only apply the scroll if we're scrolling the view upwards, or the view is so far up
-            // that it is not visible anymore.
-            if (mOwnScrollY < targetScroll || outOfViewScroll < mOwnScrollY) {
-                setOwnScrollY(targetScroll);
+                // Only apply the scroll if we're scrolling the view upwards, or the view is so
+                // far up that it is not visible anymore.
+                if (mOwnScrollY < targetScroll || outOfViewScroll < mOwnScrollY) {
+                    setOwnScrollY(targetScroll);
+                }
             }
         }
     }
@@ -1073,9 +1136,13 @@
 
     @ShadeViewRefactor(RefactorComponent.COORDINATOR)
     private void clampScrollPosition() {
-        int scrollRange = getScrollRange();
-        if (scrollRange < mOwnScrollY) {
-            setOwnScrollY(scrollRange);
+        if (ANCHOR_SCROLLING) {
+            // TODO
+        } else {
+            int scrollRange = getScrollRange();
+            if (scrollRange < mOwnScrollY) {
+                setOwnScrollY(scrollRange);
+            }
         }
     }
 
@@ -1453,17 +1520,21 @@
     @ShadeViewRefactor(RefactorComponent.SHADE_VIEW)
     public boolean scrollTo(View v) {
         ExpandableView expandableView = (ExpandableView) v;
-        int positionInLinearLayout = getPositionInLinearLayout(v);
-        int targetScroll = targetScrollForView(expandableView, positionInLinearLayout);
-        int outOfViewScroll = positionInLinearLayout + expandableView.getIntrinsicHeight();
+        if (ANCHOR_SCROLLING) {
+            // TODO
+        } else {
+            int positionInLinearLayout = getPositionInLinearLayout(v);
+            int targetScroll = targetScrollForView(expandableView, positionInLinearLayout);
+            int outOfViewScroll = positionInLinearLayout + expandableView.getIntrinsicHeight();
 
-        // Only apply the scroll if we're scrolling the view upwards, or the view is so far up
-        // that it is not visible anymore.
-        if (mOwnScrollY < targetScroll || outOfViewScroll < mOwnScrollY) {
-            mScroller.startScroll(mScrollX, mOwnScrollY, 0, targetScroll - mOwnScrollY);
-            mDontReportNextOverScroll = true;
-            animateScroll();
-            return true;
+            // Only apply the scroll if we're scrolling the view upwards, or the view is so far up
+            // that it is not visible anymore.
+            if (mOwnScrollY < targetScroll || outOfViewScroll < mOwnScrollY) {
+                mScroller.startScroll(mScrollX, mOwnScrollY, 0, targetScroll - mOwnScrollY);
+                mDontReportNextOverScroll = true;
+                animateScroll();
+                return true;
+            }
         }
         return false;
     }
@@ -1484,16 +1555,20 @@
     public WindowInsets onApplyWindowInsets(WindowInsets insets) {
         mBottomInset = insets.getSystemWindowInsetBottom();
 
-        int range = getScrollRange();
-        if (mOwnScrollY > range) {
-            // HACK: We're repeatedly getting staggered insets here while the IME is
-            // animating away. To work around that we'll wait until things have settled.
-            removeCallbacks(mReclamp);
-            postDelayed(mReclamp, 50);
-        } else if (mForcedScroll != null) {
-            // The scroll was requested before we got the actual inset - in case we need
-            // to scroll up some more do so now.
-            scrollTo(mForcedScroll);
+        if (ANCHOR_SCROLLING) {
+            // TODO
+        } else {
+            int range = getScrollRange();
+            if (mOwnScrollY > range) {
+                // HACK: We're repeatedly getting staggered insets here while the IME is
+                // animating away. To work around that we'll wait until things have settled.
+                removeCallbacks(mReclamp);
+                postDelayed(mReclamp, 50);
+            } else if (mForcedScroll != null) {
+                // The scroll was requested before we got the actual inset - in case we need
+                // to scroll up some more do so now.
+                scrollTo(mForcedScroll);
+            }
         }
         return insets;
     }
@@ -1502,8 +1577,12 @@
     private Runnable mReclamp = new Runnable() {
         @Override
         public void run() {
-            int range = getScrollRange();
-            mScroller.startScroll(mScrollX, mOwnScrollY, 0, range - mOwnScrollY);
+            if (ANCHOR_SCROLLING) {
+                // TODO
+            } else {
+                int range = getScrollRange();
+                mScroller.startScroll(mScrollX, mOwnScrollY, 0, range - mOwnScrollY);
+            }
             mDontReportNextOverScroll = true;
             mDontClampNextScroll = true;
             animateScroll();
@@ -1581,20 +1660,39 @@
         }
         // Top overScroll might not grab all scrolling motion,
         // we have to scroll as well.
-        float scrollAmount = newTopAmount < 0 ? -newTopAmount : 0.0f;
-        float newScrollY = mOwnScrollY + scrollAmount;
-        if (newScrollY > range) {
-            if (!mExpandedInThisMotion) {
-                float currentBottomPixels = getCurrentOverScrolledPixels(false);
-                // We overScroll on the top
-                setOverScrolledPixels(currentBottomPixels + newScrollY - range,
-                        false /* onTop */,
-                        false /* animate */);
+        if (ANCHOR_SCROLLING) {
+            float scrollAmount = newTopAmount < 0 ? -newTopAmount : 0.0f;
+            // TODO: once we're recycling this will need to check the adapter position of the child
+            ExpandableView lastRow = getLastRowNotGone();
+            if (lastRow != null && !lastRow.isInShelf()) {
+                float distanceToMax = Math.max(0, getMaxPositiveScrollAmount());
+                if (scrollAmount > distanceToMax) {
+                    float currentBottomPixels = getCurrentOverScrolledPixels(false);
+                    // We overScroll on the bottom
+                    setOverScrolledPixels(currentBottomPixels + (scrollAmount - distanceToMax),
+                            false /* onTop */,
+                            false /* animate */);
+                    mScrollAnchorViewY -= distanceToMax;
+                    scrollAmount = 0f;
+                }
             }
-            setOwnScrollY(range);
-            scrollAmount = 0.0f;
+            return scrollAmount;
+        } else {
+            float scrollAmount = newTopAmount < 0 ? -newTopAmount : 0.0f;
+            float newScrollY = mOwnScrollY + scrollAmount;
+            if (newScrollY > range) {
+                if (!mExpandedInThisMotion) {
+                    float currentBottomPixels = getCurrentOverScrolledPixels(false);
+                    // We overScroll on the bottom
+                    setOverScrolledPixels(currentBottomPixels + newScrollY - range,
+                            false /* onTop */,
+                            false /* animate */);
+                }
+                setOwnScrollY(range);
+                scrollAmount = 0.0f;
+            }
+            return scrollAmount;
         }
-        return scrollAmount;
     }
 
     /**
@@ -1615,18 +1713,37 @@
         }
         // Bottom overScroll might not grab all scrolling motion,
         // we have to scroll as well.
-        float scrollAmount = newBottomAmount < 0 ? newBottomAmount : 0.0f;
-        float newScrollY = mOwnScrollY + scrollAmount;
-        if (newScrollY < 0) {
-            float currentTopPixels = getCurrentOverScrolledPixels(true);
-            // We overScroll on the top
-            setOverScrolledPixels(currentTopPixels - newScrollY,
-                    true /* onTop */,
-                    false /* animate */);
-            setOwnScrollY(0);
-            scrollAmount = 0.0f;
+        if (ANCHOR_SCROLLING) {
+            float scrollAmount = newBottomAmount < 0 ? newBottomAmount : 0.0f;
+            // TODO: once we're recycling this will need to check the adapter position of the child
+            ExpandableView firstChild = getFirstChildNotGone();
+            float top = firstChild.getTranslationY();
+            float distanceToTop = mScrollAnchorView.getTranslationY() - top - mScrollAnchorViewY;
+            if (distanceToTop < -scrollAmount) {
+                float currentTopPixels = getCurrentOverScrolledPixels(true);
+                // We overScroll on the top
+                setOverScrolledPixels(currentTopPixels + (-scrollAmount - distanceToTop),
+                        true /* onTop */,
+                        false /* animate */);
+                mScrollAnchorView = firstChild;
+                mScrollAnchorViewY = 0;
+                scrollAmount = 0f;
+            }
+            return scrollAmount;
+        } else {
+            float scrollAmount = newBottomAmount < 0 ? newBottomAmount : 0.0f;
+            float newScrollY = mOwnScrollY + scrollAmount;
+            if (newScrollY < 0) {
+                float currentTopPixels = getCurrentOverScrolledPixels(true);
+                // We overScroll on the top
+                setOverScrolledPixels(currentTopPixels - newScrollY,
+                        true /* onTop */,
+                        false /* animate */);
+                setOwnScrollY(0);
+                scrollAmount = 0.0f;
+            }
+            return scrollAmount;
         }
-        return scrollAmount;
     }
 
     @ShadeViewRefactor(RefactorComponent.STATE_RESOLVER)
@@ -1661,26 +1778,43 @@
     @ShadeViewRefactor(RefactorComponent.STATE_RESOLVER)
     private void animateScroll() {
         if (mScroller.computeScrollOffset()) {
-            int oldY = mOwnScrollY;
-            int y = mScroller.getCurrY();
-
-            if (oldY != y) {
-                int range = getScrollRange();
-                if (y < 0 && oldY >= 0 || y > range && oldY <= range) {
-                    float currVelocity = mScroller.getCurrVelocity();
-                    if (currVelocity >= mMinimumVelocity) {
-                        mMaxOverScroll = Math.abs(currVelocity) / 1000 * mOverflingDistance;
+            if (ANCHOR_SCROLLING) {
+                int oldY = mLastScrollerY;
+                int y = mScroller.getCurrY();
+                int deltaY = y - oldY;
+                if (deltaY != 0) {
+                    int maxNegativeScrollAmount = getMaxNegativeScrollAmount();
+                    int maxPositiveScrollAmount = getMaxPositiveScrollAmount();
+                    if ((maxNegativeScrollAmount < 0 && deltaY < maxNegativeScrollAmount)
+                            || (maxPositiveScrollAmount > 0 && deltaY > maxPositiveScrollAmount)) {
+                        // This frame takes us into overscroll, so set the max overscroll based on
+                        // the current velocity
+                        setMaxOverScrollFromCurrentVelocity();
                     }
+                    customOverScrollBy(deltaY, oldY, 0, (int) mMaxOverScroll);
+                    mLastScrollerY = y;
                 }
+            } else {
+                int oldY = mOwnScrollY;
+                int y = mScroller.getCurrY();
 
-                if (mDontClampNextScroll) {
-                    range = Math.max(range, oldY);
+                if (oldY != y) {
+                    int range = getScrollRange();
+                    if (y < 0 && oldY >= 0 || y > range && oldY <= range) {
+                        // This frame takes us into overscroll, so set the max overscroll based on
+                        // the current velocity
+                        setMaxOverScrollFromCurrentVelocity();
+                    }
+
+                    if (mDontClampNextScroll) {
+                        range = Math.max(range, oldY);
+                    }
+                    customOverScrollBy(y - oldY, oldY, range,
+                            (int) (mMaxOverScroll));
                 }
-                customOverScrollBy(y - oldY, oldY, range,
-                        (int) (mMaxOverScroll));
             }
 
-            postOnAnimation(mAnimateScroll);
+            postOnAnimation(mReflingAndAnimateScroll);
         } else {
             mDontClampNextScroll = false;
             if (mFinishScrollingCallback != null) {
@@ -1689,26 +1823,67 @@
         }
     }
 
-    @ShadeViewRefactor(RefactorComponent.STATE_RESOLVER)
-    private boolean customOverScrollBy(int deltaY, int scrollY, int scrollRangeY,
-            int maxOverScrollY) {
-
-        int newScrollY = scrollY + deltaY;
-        final int top = -maxOverScrollY;
-        final int bottom = maxOverScrollY + scrollRangeY;
-
-        boolean clampedY = false;
-        if (newScrollY > bottom) {
-            newScrollY = bottom;
-            clampedY = true;
-        } else if (newScrollY < top) {
-            newScrollY = top;
-            clampedY = true;
+    private void setMaxOverScrollFromCurrentVelocity() {
+        float currVelocity = mScroller.getCurrVelocity();
+        if (currVelocity >= mMinimumVelocity) {
+            mMaxOverScroll = Math.abs(currVelocity) / 1000 * mOverflingDistance;
         }
+    }
 
-        onCustomOverScrolled(newScrollY, clampedY);
+    /**
+     * Scrolls by the given delta, overscrolling if needed.  If called during a fling and the delta
+     * would cause us to exceed the provided maximum overscroll, springs back instead.
+     *
+     * This method performs the determination of whether we're exceeding the overscroll and clamps
+     * the scroll amount if so.  The actual scrolling/overscrolling happens in
+     * {@link #onCustomOverScrolled(int, boolean)} (absolute scrolling) or
+     * {@link #onCustomOverScrolledBy(int, boolean)} (anchor scrolling).
+     *
+     * @param deltaY         The (signed) number of pixels to scroll.
+     * @param scrollY        The current scroll position (absolute scrolling only).
+     * @param scrollRangeY   The maximum allowable scroll position (absolute scrolling only).
+     * @param maxOverScrollY The current (unsigned) limit on number of pixels to overscroll by.
+     */
+    @ShadeViewRefactor(RefactorComponent.STATE_RESOLVER)
+    private void customOverScrollBy(int deltaY, int scrollY, int scrollRangeY, int maxOverScrollY) {
+        if (ANCHOR_SCROLLING) {
+            boolean clampedY = false;
+            if (deltaY < 0) {
+                int maxScrollAmount = getMaxNegativeScrollAmount();
+                if (maxScrollAmount > Integer.MIN_VALUE) {
+                    maxScrollAmount -= maxOverScrollY;
+                    if (deltaY < maxScrollAmount) {
+                        deltaY = maxScrollAmount;
+                        clampedY = true;
+                    }
+                }
+            } else {
+                int maxScrollAmount = getMaxPositiveScrollAmount();
+                if (maxScrollAmount < Integer.MAX_VALUE) {
+                    maxScrollAmount += maxOverScrollY;
+                    if (deltaY > maxScrollAmount) {
+                        deltaY = maxScrollAmount;
+                        clampedY = true;
+                    }
+                }
+            }
+            onCustomOverScrolledBy(deltaY, clampedY);
+        } else {
+            int newScrollY = scrollY + deltaY;
+            final int top = -maxOverScrollY;
+            final int bottom = maxOverScrollY + scrollRangeY;
 
-        return clampedY;
+            boolean clampedY = false;
+            if (newScrollY > bottom) {
+                newScrollY = bottom;
+                clampedY = true;
+            } else if (newScrollY < top) {
+                newScrollY = top;
+                clampedY = true;
+            }
+
+            onCustomOverScrolled(newScrollY, clampedY);
+        }
     }
 
     /**
@@ -1826,8 +2001,46 @@
         }
     }
 
+    /**
+     * Scrolls by the given delta, overscrolling if needed.  If called during a fling and the delta
+     * would cause us to exceed the provided maximum overscroll, springs back instead.
+     *
+     * @param deltaY   The (signed) number of pixels to scroll.
+     * @param clampedY Whether this value was clamped by the calling method, meaning we've reached
+     *                 the overscroll limit.
+     */
+    private void onCustomOverScrolledBy(int deltaY, boolean clampedY) {
+        assert ANCHOR_SCROLLING;
+        mScrollAnchorViewY -= deltaY;
+        // Treat animating scrolls differently; see #computeScroll() for why.
+        if (!mScroller.isFinished()) {
+            if (clampedY) {
+                springBack();
+            } else {
+                float overScrollTop = getCurrentOverScrollAmount(true /* top */);
+                if (isScrolledToTop() && mScrollAnchorViewY > 0) {
+                    notifyOverscrollTopListener(mScrollAnchorViewY,
+                            isRubberbanded(true /* onTop */));
+                } else {
+                    notifyOverscrollTopListener(overScrollTop, isRubberbanded(true /* onTop */));
+                }
+            }
+        }
+        updateScrollAnchor();
+        updateOnScrollChange();
+    }
+
+    /**
+     * Scrolls to the given position, overscrolling if needed.  If called during a fling and the
+     * position exceeds the provided maximum overscroll, springs back instead.
+     *
+     * @param scrollY The target scroll position.
+     * @param clampedY Whether this value was clamped by the calling method, meaning we've reached
+     *                 the overscroll limit.
+     */
     @ShadeViewRefactor(RefactorComponent.COORDINATOR)
     private void onCustomOverScrolled(int scrollY, boolean clampedY) {
+        assert !ANCHOR_SCROLLING;
         // Treat animating scrolls differently; see #computeScroll() for why.
         if (!mScroller.isFinished()) {
             setOwnScrollY(scrollY);
@@ -1846,27 +2059,51 @@
         }
     }
 
+    /**
+     * Springs back from an overscroll by stopping the {@link #mScroller} and animating the
+     * overscroll amount back to zero.
+     */
     @ShadeViewRefactor(RefactorComponent.STATE_RESOLVER)
     private void springBack() {
-        int scrollRange = getScrollRange();
-        boolean overScrolledTop = mOwnScrollY <= 0;
-        boolean overScrolledBottom = mOwnScrollY >= scrollRange;
-        if (overScrolledTop || overScrolledBottom) {
-            boolean onTop;
-            float newAmount;
-            if (overScrolledTop) {
-                onTop = true;
-                newAmount = -mOwnScrollY;
-                setOwnScrollY(0);
-                mDontReportNextOverScroll = true;
-            } else {
-                onTop = false;
-                newAmount = mOwnScrollY - scrollRange;
-                setOwnScrollY(scrollRange);
+        if (ANCHOR_SCROLLING) {
+            boolean overScrolledTop = isScrolledToTop() && mScrollAnchorViewY > 0;
+            int maxPositiveScrollAmount = getMaxPositiveScrollAmount();
+            boolean overscrolledBottom = maxPositiveScrollAmount < 0;
+            if (overScrolledTop || overscrolledBottom) {
+                float newAmount;
+                if (overScrolledTop) {
+                    newAmount = mScrollAnchorViewY;
+                    mScrollAnchorViewY = 0;
+                    mDontReportNextOverScroll = true;
+                } else {
+                    newAmount = -maxPositiveScrollAmount;
+                    mScrollAnchorViewY -= maxPositiveScrollAmount;
+                }
+                setOverScrollAmount(newAmount, overScrolledTop, false);
+                setOverScrollAmount(0.0f, overScrolledTop, true);
+                mScroller.forceFinished(true);
             }
-            setOverScrollAmount(newAmount, onTop, false);
-            setOverScrollAmount(0.0f, onTop, true);
-            mScroller.forceFinished(true);
+        } else {
+            int scrollRange = getScrollRange();
+            boolean overScrolledTop = mOwnScrollY <= 0;
+            boolean overScrolledBottom = mOwnScrollY >= scrollRange;
+            if (overScrolledTop || overScrolledBottom) {
+                boolean onTop;
+                float newAmount;
+                if (overScrolledTop) {
+                    onTop = true;
+                    newAmount = -mOwnScrollY;
+                    setOwnScrollY(0);
+                    mDontReportNextOverScroll = true;
+                } else {
+                    onTop = false;
+                    newAmount = mOwnScrollY - scrollRange;
+                    setOwnScrollY(scrollRange);
+                }
+                setOverScrollAmount(newAmount, onTop, false);
+                setOverScrollAmount(0.0f, onTop, true);
+                mScroller.forceFinished(true);
+            }
         }
     }
 
@@ -1971,6 +2208,17 @@
         return null;
     }
 
+    private ExpandableNotificationRow getLastRowNotGone() {
+        int childCount = getChildCount();
+        for (int i = childCount - 1; i >= 0; i--) {
+            View child = getChildAt(i);
+            if (child instanceof ExpandableNotificationRow && child.getVisibility() != View.GONE) {
+                return (ExpandableNotificationRow) child;
+            }
+        }
+        return null;
+    }
+
     /**
      * @return the number of children which have visibility unequal to GONE
      */
@@ -2081,8 +2329,8 @@
 
     @ShadeViewRefactor(RefactorComponent.SHADE_VIEW)
     private void updateForwardAndBackwardScrollability() {
-        boolean forwardScrollable = mScrollable && mOwnScrollY < getScrollRange();
-        boolean backwardsScrollable = mScrollable && mOwnScrollY > 0;
+        boolean forwardScrollable = mScrollable && !isScrolledToBottom();
+        boolean backwardsScrollable = mScrollable && !isScrolledToTop();
         boolean changed = forwardScrollable != mForwardScrollable
                 || backwardsScrollable != mBackwardScrollable;
         mForwardScrollable = forwardScrollable;
@@ -2365,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;
@@ -2384,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;
                 }
             }
@@ -2403,18 +2649,24 @@
     @ShadeViewRefactor(RefactorComponent.SHADE_VIEW)
     protected void fling(int velocityY) {
         if (getChildCount() > 0) {
-            int scrollRange = getScrollRange();
-
             float topAmount = getCurrentOverScrollAmount(true);
             float bottomAmount = getCurrentOverScrollAmount(false);
             if (velocityY < 0 && topAmount > 0) {
-                setOwnScrollY(mOwnScrollY - (int) topAmount);
+                if (ANCHOR_SCROLLING) {
+                    mScrollAnchorViewY += topAmount;
+                } else {
+                    setOwnScrollY(mOwnScrollY - (int) topAmount);
+                }
                 mDontReportNextOverScroll = true;
                 setOverScrollAmount(0, true, false);
                 mMaxOverScroll = Math.abs(velocityY) / 1000f * getRubberBandFactor(true /* onTop */)
                         * mOverflingDistance + topAmount;
             } else if (velocityY > 0 && bottomAmount > 0) {
-                setOwnScrollY((int) (mOwnScrollY + bottomAmount));
+                if (ANCHOR_SCROLLING) {
+                    mScrollAnchorViewY -= bottomAmount;
+                } else {
+                    setOwnScrollY((int) (mOwnScrollY + bottomAmount));
+                }
                 setOverScrollAmount(0, false, false);
                 mMaxOverScroll = Math.abs(velocityY) / 1000f
                         * getRubberBandFactor(false /* onTop */) * mOverflingDistance
@@ -2423,18 +2675,138 @@
                 // it will be set once we reach the boundary
                 mMaxOverScroll = 0.0f;
             }
-            int minScrollY = Math.max(0, scrollRange);
-            if (mExpandedInThisMotion) {
-                minScrollY = Math.min(minScrollY, mMaxScrollAfterExpand);
+            if (ANCHOR_SCROLLING) {
+                flingScroller(velocityY);
+            } else {
+                int scrollRange = getScrollRange();
+                int minScrollY = Math.max(0, scrollRange);
+                if (mExpandedInThisMotion) {
+                    minScrollY = Math.min(minScrollY, mMaxScrollAfterExpand);
+                }
+                mScroller.fling(mScrollX, mOwnScrollY, 1, velocityY, 0, 0, 0, minScrollY, 0,
+                        mExpandedInThisMotion && mOwnScrollY >= 0 ? 0 : Integer.MAX_VALUE / 2);
             }
-            mScroller.fling(mScrollX, mOwnScrollY, 1, velocityY, 0, 0, 0, minScrollY, 0,
-                    mExpandedInThisMotion && mOwnScrollY >= 0 ? 0 : Integer.MAX_VALUE / 2);
 
             animateScroll();
         }
     }
 
     /**
+     * Flings the overscroller with the given velocity (anchor-based scrolling).
+     *
+     * Because anchor-based scrolling can't track the current scroll position, the overscroller is
+     * always started at startY = 0, and we interpret the positions it computes as relative to the
+     * start of the scroll.
+     */
+    private void flingScroller(int velocityY) {
+        assert ANCHOR_SCROLLING;
+        mIsScrollerBoundSet = false;
+        maybeFlingScroller(velocityY, true /* always fling */);
+    }
+
+    private void maybeFlingScroller(int velocityY, boolean alwaysFling) {
+        assert ANCHOR_SCROLLING;
+        // Attempt to determine the maximum amount to scroll before we reach the end.
+        // If the first view is not materialized (for an upwards scroll) or the last view is either
+        // not materialized or is pinned to the shade (for a downwards scroll), we don't know this
+        // amount, so we do an unbounded fling and rely on {@link #maybeReflingScroller()} to update
+        // the scroller once we approach the start/end of the list.
+        int minY = Integer.MIN_VALUE;
+        int maxY = Integer.MAX_VALUE;
+        if (velocityY < 0) {
+            minY = getMaxNegativeScrollAmount();
+            if (minY > Integer.MIN_VALUE) {
+                mIsScrollerBoundSet = true;
+            }
+        } else {
+            maxY = getMaxPositiveScrollAmount();
+            if (maxY < Integer.MAX_VALUE) {
+                mIsScrollerBoundSet = true;
+            }
+        }
+        if (mIsScrollerBoundSet || alwaysFling) {
+            mLastScrollerY = 0;
+            // x velocity is set to 1 to avoid overscroller bug
+            mScroller.fling(0, 0, 1, velocityY, 0, 0, minY, maxY, 0,
+                    mExpandedInThisMotion && !isScrolledToTop() ? 0 : Integer.MAX_VALUE / 2);
+        }
+    }
+
+    /**
+     * Returns the maximum number of pixels we can scroll in the positive direction (downwards)
+     * before reaching the bottom of the list (discounting overscroll).
+     *
+     * If the return value is negative then we have overscrolled; this is a transient state which
+     * should immediately be handled by adjusting the anchor position and adding the extra space to
+     * the bottom overscroll amount.
+     *
+     * If we don't know how many pixels we have left to scroll (because the last row has not been
+     * materialized, or it's in the shelf so it doesn't have its "natural" position), we return
+     * {@link Integer#MAX_VALUE}.
+     */
+    private int getMaxPositiveScrollAmount() {
+        assert ANCHOR_SCROLLING;
+        // TODO: once we're recycling we need to check the adapter position of the last child.
+        ExpandableNotificationRow lastRow = getLastRowNotGone();
+        if (mScrollAnchorView != null && lastRow != null && !lastRow.isInShelf()) {
+            // distance from bottom of last child to bottom of notifications area is:
+            // distance from bottom of last child
+            return (int) (lastRow.getTranslationY() + lastRow.getActualHeight()
+                    // to top of anchor view
+                    - mScrollAnchorView.getTranslationY()
+                    // plus distance from anchor view to top of notifications area
+                    + mScrollAnchorViewY
+                    // minus height of notifications area.
+                    - (mMaxLayoutHeight - getIntrinsicPadding() - mFooterView.getActualHeight()));
+        } else {
+            return Integer.MAX_VALUE;
+        }
+    }
+
+    /**
+     * Returns the maximum number of pixels (as a negative number) we can scroll in the negative
+     * direction (upwards) before reaching the top of the list (discounting overscroll).
+     *
+     * If the return value is positive then we have overscrolled; this is a transient state which
+     * should immediately be handled by adjusting the anchor position and adding the extra space to
+     * the top overscroll amount.
+     *
+     * If we don't know how many pixels we have left to scroll (because the first row has not been
+     * materialized), we return {@link Integer#MIN_VALUE}.
+     */
+    private int getMaxNegativeScrollAmount() {
+        assert ANCHOR_SCROLLING;
+        // TODO: once we're recycling we need to check the adapter position of the first child.
+        ExpandableView firstChild = getFirstChildNotGone();
+        if (mScrollAnchorView != null && firstChild != null) {
+            // distance from top of first child to top of notifications area is:
+            // distance from top of anchor view
+            return (int) -(mScrollAnchorView.getTranslationY()
+                    // to top of first child
+                    - firstChild.getTranslationY()
+                    // minus distance from top of anchor view to top of notifications area.
+                    - mScrollAnchorViewY);
+        } else {
+            return Integer.MIN_VALUE;
+        }
+    }
+
+    /**
+     * During a fling, if we were unable to set the bounds of the fling due to the top/bottom view
+     * not being materialized or being pinned to the shelf, we need to check on every frame if we're
+     * able to set the bounds.  If we are, we fling the scroller again with the newly computed
+     * bounds.
+     */
+    private void maybeReflingScroller() {
+        if (!mIsScrollerBoundSet) {
+            // Because mScroller is a flywheel scroller, we fling with the minimum possible
+            // velocity to establish direction, so as not to perceptibly affect the velocity.
+            maybeFlingScroller((int) Math.signum(mScroller.getCurrVelocity()),
+                    false /* alwaysFling */);
+        }
+    }
+
+    /**
      * @return Whether a fling performed on the top overscroll edge lead to the expanded
      * overScroll view (i.e QS).
      */
@@ -2485,25 +2857,10 @@
     }
 
     @ShadeViewRefactor(RefactorComponent.COORDINATOR)
-    public int getFirstChildIntrinsicHeight() {
-        final ExpandableView firstChild = getFirstChildNotGone();
-        int firstChildMinHeight = firstChild != null
-                ? firstChild.getIntrinsicHeight()
-                : mEmptyShadeView != null
-                        ? mEmptyShadeView.getIntrinsicHeight()
-                        : mCollapsedSize;
-        if (mOwnScrollY > 0) {
-            firstChildMinHeight = Math.max(firstChildMinHeight - mOwnScrollY, mCollapsedSize);
-        }
-        return firstChildMinHeight;
-    }
-
-    @ShadeViewRefactor(RefactorComponent.COORDINATOR)
     public float getTopPaddingOverflow() {
         return mTopPaddingOverflow;
     }
 
-
     @ShadeViewRefactor(RefactorComponent.COORDINATOR)
     public int getPeekHeight() {
         final ExpandableView firstChild = getFirstChildNotGone();
@@ -2711,30 +3068,51 @@
      */
     @ShadeViewRefactor(RefactorComponent.STATE_RESOLVER)
     private void updateScrollStateForRemovedChild(ExpandableView removedChild) {
-        int startingPosition = getPositionInLinearLayout(removedChild);
-        float increasedPaddingAmount = removedChild.getIncreasedPaddingAmount();
-        int padding;
-        if (increasedPaddingAmount >= 0) {
-            padding = (int) NotificationUtils.interpolate(
-                    mPaddingBetweenElements,
-                    mIncreasedPaddingBetweenElements,
-                    increasedPaddingAmount);
+        if (ANCHOR_SCROLLING) {
+            if (removedChild == mScrollAnchorView) {
+                ExpandableView firstChild = getFirstChildNotGone();
+                if (firstChild != null) {
+                    mScrollAnchorView = firstChild;
+                } else {
+                    mScrollAnchorView = mShelf;
+                }
+                // Adjust anchor view Y by the distance between the old and new anchors
+                // so that there's no visible change.
+                mScrollAnchorViewY +=
+                        mScrollAnchorView.getTranslationY() - removedChild.getTranslationY();
+            }
+            updateScrollAnchor();
+            // TODO: once we're recycling this will need to check the adapter position of the child
+            if (mScrollAnchorView == getFirstChildNotGone() && mScrollAnchorViewY > 0) {
+                mScrollAnchorViewY = 0;
+            }
+            updateOnScrollChange();
         } else {
-            padding = (int) NotificationUtils.interpolate(
-                    0,
-                    mPaddingBetweenElements,
-                    1.0f + increasedPaddingAmount);
-        }
-        int childHeight = getIntrinsicHeight(removedChild) + padding;
-        int endPosition = startingPosition + childHeight;
-        if (endPosition <= mOwnScrollY) {
-            // This child is fully scrolled of the top, so we have to deduct its height from the
-            // scrollPosition
-            setOwnScrollY(mOwnScrollY - childHeight);
-        } else if (startingPosition < mOwnScrollY) {
-            // This child is currently being scrolled into, set the scroll position to the start of
-            // this child
-            setOwnScrollY(startingPosition);
+            int startingPosition = getPositionInLinearLayout(removedChild);
+            float increasedPaddingAmount = removedChild.getIncreasedPaddingAmount();
+            int padding;
+            if (increasedPaddingAmount >= 0) {
+                padding = (int) NotificationUtils.interpolate(
+                        mPaddingBetweenElements,
+                        mIncreasedPaddingBetweenElements,
+                        increasedPaddingAmount);
+            } else {
+                padding = (int) NotificationUtils.interpolate(
+                        0,
+                        mPaddingBetweenElements,
+                        1.0f + increasedPaddingAmount);
+            }
+            int childHeight = getIntrinsicHeight(removedChild) + padding;
+            int endPosition = startingPosition + childHeight;
+            if (endPosition <= mOwnScrollY) {
+                // This child is fully scrolled of the top, so we have to deduct its height from the
+                // scrollPosition
+                setOwnScrollY(mOwnScrollY - childHeight);
+            } else if (startingPosition < mOwnScrollY) {
+                // This child is currently being scrolled into, set the scroll position to the
+                // start of this child
+                setOwnScrollY(startingPosition);
+            }
         }
     }
 
@@ -2888,6 +3266,17 @@
         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)) {
+                // New child was added at the top while we're scrolled to the top;
+                // make it the new anchor view so that we stay at the top.
+                mScrollAnchorView = child;
+            }
+        }
     }
 
     @ShadeViewRefactor(RefactorComponent.COORDINATOR)
@@ -3381,17 +3770,24 @@
                         final float vscroll = event.getAxisValue(MotionEvent.AXIS_VSCROLL);
                         if (vscroll != 0) {
                             final int delta = (int) (vscroll * getVerticalScrollFactor());
-                            final int range = getScrollRange();
-                            int oldScrollY = mOwnScrollY;
-                            int newScrollY = oldScrollY - delta;
-                            if (newScrollY < 0) {
-                                newScrollY = 0;
-                            } else if (newScrollY > range) {
-                                newScrollY = range;
-                            }
-                            if (newScrollY != oldScrollY) {
-                                setOwnScrollY(newScrollY);
-                                return true;
+                            if (ANCHOR_SCROLLING) {
+                                mScrollAnchorViewY -= delta;
+                                updateScrollAnchor();
+                                clampScrollPosition();
+                                updateOnScrollChange();
+                            } else {
+                                final int range = getScrollRange();
+                                int oldScrollY = mOwnScrollY;
+                                int newScrollY = oldScrollY - delta;
+                                if (newScrollY < 0) {
+                                    newScrollY = 0;
+                                } else if (newScrollY > range) {
+                                    newScrollY = range;
+                                }
+                                if (newScrollY != oldScrollY) {
+                                    setOwnScrollY(newScrollY);
+                                    return true;
+                                }
                             }
                         }
                     }
@@ -3459,12 +3855,16 @@
                 if (mIsBeingDragged) {
                     // Scroll to follow the motion event
                     mLastMotionY = y;
-                    int range = getScrollRange();
-                    if (mExpandedInThisMotion) {
-                        range = Math.min(range, mMaxScrollAfterExpand);
-                    }
-
                     float scrollAmount;
+                    int range;
+                    if (ANCHOR_SCROLLING) {
+                        range = 0;  // unused in the methods it's being passed to
+                    } else {
+                        range = getScrollRange();
+                        if (mExpandedInThisMotion) {
+                            range = Math.min(range, mMaxScrollAfterExpand);
+                        }
+                    }
                     if (deltaY < 0) {
                         scrollAmount = overScrollDown(deltaY);
                     } else {
@@ -3501,9 +3901,13 @@
                                     onOverScrollFling(false, initialVelocity);
                                 }
                             } else {
-                                if (mScroller.springBack(mScrollX, mOwnScrollY, 0, 0, 0,
-                                        getScrollRange())) {
-                                    animateScroll();
+                                if (ANCHOR_SCROLLING) {
+                                    // TODO
+                                } else {
+                                    if (mScroller.springBack(mScrollX, mOwnScrollY, 0, 0, 0,
+                                            getScrollRange())) {
+                                        animateScroll();
+                                    }
                                 }
                             }
                         }
@@ -3515,8 +3919,13 @@
                 break;
             case MotionEvent.ACTION_CANCEL:
                 if (mIsBeingDragged && getChildCount() > 0) {
-                    if (mScroller.springBack(mScrollX, mOwnScrollY, 0, 0, 0, getScrollRange())) {
-                        animateScroll();
+                    if (ANCHOR_SCROLLING) {
+                        // TODO
+                    } else {
+                        if (mScroller.springBack(mScrollX, mOwnScrollY, 0, 0, 0,
+                                getScrollRange())) {
+                            animateScroll();
+                        }
                     }
                     mActivePointerId = INVALID_POINTER;
                     endDrag();
@@ -3585,12 +3994,6 @@
         }
     }
 
-    @ShadeViewRefactor(RefactorComponent.INPUT)
-    private void transformTouchEvent(MotionEvent ev, View sourceView, View targetView) {
-        ev.offsetLocation(sourceView.getX(), sourceView.getY());
-        ev.offsetLocation(-targetView.getX(), -targetView.getY());
-    }
-
     @Override
     @ShadeViewRefactor(RefactorComponent.INPUT)
     public boolean onInterceptTouchEvent(MotionEvent ev) {
@@ -3763,8 +4166,12 @@
                 setIsBeingDragged(false);
                 mActivePointerId = INVALID_POINTER;
                 recycleVelocityTracker();
-                if (mScroller.springBack(mScrollX, mOwnScrollY, 0, 0, 0, getScrollRange())) {
-                    animateScroll();
+                if (ANCHOR_SCROLLING) {
+                    // TODO
+                } else {
+                    if (mScroller.springBack(mScrollX, mOwnScrollY, 0, 0, 0, getScrollRange())) {
+                        animateScroll();
+                    }
                 }
                 break;
             case MotionEvent.ACTION_POINTER_UP:
@@ -3839,14 +4246,20 @@
             case AccessibilityNodeInfo.ACTION_SCROLL_BACKWARD:
                 // fall through
             case android.R.id.accessibilityActionScrollUp:
-                final int viewportHeight = getHeight() - mPaddingBottom - mTopPadding - mPaddingTop
-                        - mShelf.getIntrinsicHeight();
-                final int targetScrollY = Math.max(0,
-                        Math.min(mOwnScrollY + direction * viewportHeight, getScrollRange()));
-                if (targetScrollY != mOwnScrollY) {
-                    mScroller.startScroll(mScrollX, mOwnScrollY, 0, targetScrollY - mOwnScrollY);
-                    animateScroll();
-                    return true;
+                if (ANCHOR_SCROLLING) {
+                    // TODO
+                } else {
+                    final int viewportHeight =
+                            getHeight() - mPaddingBottom - mTopPadding - mPaddingTop
+                                    - mShelf.getIntrinsicHeight();
+                    final int targetScrollY = Math.max(0,
+                            Math.min(mOwnScrollY + direction * viewportHeight, getScrollRange()));
+                    if (targetScrollY != mOwnScrollY) {
+                        mScroller.startScroll(mScrollX, mOwnScrollY, 0,
+                                targetScrollY - mOwnScrollY);
+                        animateScroll();
+                        return true;
+                    }
                 }
                 break;
         }
@@ -3905,13 +4318,23 @@
     @Override
     @ShadeViewRefactor(RefactorComponent.COORDINATOR)
     public boolean isScrolledToTop() {
-        return mOwnScrollY == 0;
+        if (ANCHOR_SCROLLING) {
+            updateScrollAnchor();
+            // TODO: once we're recycling this will need to check the adapter position of the child
+            return mScrollAnchorView == getFirstChildNotGone() && mScrollAnchorViewY >= 0;
+        } else {
+            return mOwnScrollY == 0;
+        }
     }
 
     @Override
     @ShadeViewRefactor(RefactorComponent.COORDINATOR)
     public boolean isScrolledToBottom() {
-        return mOwnScrollY >= getScrollRange();
+        if (ANCHOR_SCROLLING) {
+            return getMaxPositiveScrollAmount() <= 0;
+        } else {
+            return mOwnScrollY >= getScrollRange();
+        }
     }
 
     @Override
@@ -3953,7 +4376,7 @@
         resetCheckSnoozeLeavebehind();
         mAmbientState.setExpansionChanging(false);
         if (!mIsExpanded) {
-            setOwnScrollY(0);
+            resetScrollPosition();
             mStatusBar.resetUserExpandedStates();
             clearTemporaryViews();
             clearUserLockedViews();
@@ -4012,7 +4435,14 @@
     @ShadeViewRefactor(RefactorComponent.STATE_RESOLVER)
     public void resetScrollPosition() {
         mScroller.abortAnimation();
-        setOwnScrollY(0);
+        if (ANCHOR_SCROLLING) {
+            // TODO: once we're recycling this will need to modify the adapter position instead
+            mScrollAnchorView = getFirstChildNotGone();
+            mScrollAnchorViewY = 0;
+            updateOnScrollChange();
+        } else {
+            setOwnScrollY(0);
+        }
     }
 
     @ShadeViewRefactor(RefactorComponent.COORDINATOR)
@@ -4081,6 +4511,7 @@
     private void updateScrollPositionOnExpandInBottom(ExpandableView view) {
         if (view instanceof ExpandableNotificationRow && !onKeyguard()) {
             ExpandableNotificationRow row = (ExpandableNotificationRow) view;
+            // TODO: once we're recycling this will need to check the adapter position of the child
             if (row.isUserLocked() && row != getFirstChildNotGone()) {
                 if (row.isSummaryWithChildren()) {
                     return;
@@ -4098,7 +4529,13 @@
                     layoutEnd -= mShelf.getIntrinsicHeight() + mPaddingBetweenElements;
                 }
                 if (endPosition > layoutEnd) {
-                    setOwnScrollY((int) (mOwnScrollY + endPosition - layoutEnd));
+                    if (ANCHOR_SCROLLING) {
+                        mScrollAnchorViewY -= (endPosition - layoutEnd);
+                        updateScrollAnchor();
+                        updateOnScrollChange();
+                    } else {
+                        setOwnScrollY((int) (mOwnScrollY + endPosition - layoutEnd));
+                    }
                     mDisallowScrollingInThisMotion = true;
                 }
             }
@@ -4663,9 +5100,13 @@
         super.onInitializeAccessibilityEventInternal(event);
         event.setScrollable(mScrollable);
         event.setScrollX(mScrollX);
-        event.setScrollY(mOwnScrollY);
         event.setMaxScrollX(mScrollX);
-        event.setMaxScrollY(getScrollRange());
+        if (ANCHOR_SCROLLING) {
+            // TODO
+        } else {
+            event.setScrollY(mOwnScrollY);
+            event.setMaxScrollY(getScrollRange());
+        }
     }
 
     @Override
@@ -4850,13 +5291,63 @@
     }
 
     @ShadeViewRefactor(RefactorComponent.COORDINATOR)
-    public void setOwnScrollY(int ownScrollY) {
+    private void setOwnScrollY(int ownScrollY) {
+        assert !ANCHOR_SCROLLING;
         if (ownScrollY != mOwnScrollY) {
             // We still want to call the normal scrolled changed for accessibility reasons
             onScrollChanged(mScrollX, ownScrollY, mScrollX, mOwnScrollY);
             mOwnScrollY = ownScrollY;
-            updateForwardAndBackwardScrollability();
-            requestChildrenUpdate();
+            updateOnScrollChange();
+        }
+    }
+
+    private void updateOnScrollChange() {
+        updateForwardAndBackwardScrollability();
+        requestChildrenUpdate();
+    }
+
+    private void updateScrollAnchor() {
+        int anchorIndex = indexOfChild(mScrollAnchorView);
+        // If the anchor view has been scrolled off the top, move to the next view.
+        while (mScrollAnchorViewY < 0) {
+            View nextAnchor = null;
+            for (int i = anchorIndex + 1; i < getChildCount(); i++) {
+                View child = getChildAt(i);
+                if (child.getVisibility() != View.GONE
+                        && child instanceof ExpandableNotificationRow) {
+                    anchorIndex = i;
+                    nextAnchor = child;
+                    break;
+                }
+            }
+            if (nextAnchor == null) {
+                break;
+            }
+            mScrollAnchorViewY +=
+                    (int) (nextAnchor.getTranslationY() - mScrollAnchorView.getTranslationY());
+            mScrollAnchorView = nextAnchor;
+        }
+        // If the view above the anchor view is fully visible, make it the anchor view.
+        while (anchorIndex > 0 && mScrollAnchorViewY > 0) {
+            View prevAnchor = null;
+            for (int i = anchorIndex - 1; i >= 0; i--) {
+                View child = getChildAt(i);
+                if (child.getVisibility() != View.GONE
+                        && child instanceof ExpandableNotificationRow) {
+                    anchorIndex = i;
+                    prevAnchor = child;
+                    break;
+                }
+            }
+            if (prevAnchor == null) {
+                break;
+            }
+            float distanceToPreviousAnchor =
+                    mScrollAnchorView.getTranslationY() - prevAnchor.getTranslationY();
+            if (distanceToPreviousAnchor < mScrollAnchorViewY) {
+                mScrollAnchorViewY -= (int) distanceToPreviousAnchor;
+                mScrollAnchorView = prevAnchor;
+            }
         }
     }
 
@@ -4872,6 +5363,9 @@
         mAmbientState.setShelf(shelf);
         mStateAnimator.setShelf(shelf);
         shelf.bind(mAmbientState, this);
+        if (ANCHOR_SCROLLING) {
+            mScrollAnchorView = mShelf;
+        }
     }
 
     @ShadeViewRefactor(RefactorComponent.SHADE_VIEW)
@@ -5276,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;
@@ -5304,8 +5796,7 @@
                     continue;
                 }
                 ExpandableNotificationRow row = (ExpandableNotificationRow) view;
-                if (!mEntryManager.getNotificationData().isHighPriority(
-                        row.getStatusBarNotification())) {
+                if (!row.getEntry().isHighPriority()) {
                     if (currentIndex > 0) {
                         gapIndex = currentIndex;
                     }
@@ -5818,6 +6309,15 @@
         }
 
         @Override
+        public int getConstrainSwipeStartPosition() {
+            NotificationMenuRowPlugin menuRow = mSwipeHelper.getCurrentMenuRow();
+            if (menuRow != null) {
+                return Math.abs(menuRow.getMenuSnapTarget());
+            }
+            return 0;
+        }
+
+                @Override
         public boolean canChildBeDismissed(View v) {
             return NotificationStackScrollLayout.this.canChildBeDismissed(v);
         }
@@ -5826,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;
             }
@@ -6013,7 +6513,11 @@
         public void expansionStateChanged(boolean isExpanding) {
             mExpandingNotification = isExpanding;
             if (!mExpandedInThisMotion) {
-                mMaxScrollAfterExpand = mOwnScrollY;
+                if (ANCHOR_SCROLLING) {
+                    // TODO
+                } else {
+                    mMaxScrollAfterExpand = mOwnScrollY;
+                }
                 mExpandedInThisMotion = true;
             }
         }
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/StackScrollAlgorithm.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/StackScrollAlgorithm.java
index 25fb7f9..2a88080 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/StackScrollAlgorithm.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/StackScrollAlgorithm.java
@@ -41,6 +41,8 @@
  */
 public class StackScrollAlgorithm {
 
+    static final boolean ANCHOR_SCROLLING = false;
+
     private static final String LOG_TAG = "StackScrollAlgorithm";
     private final ViewGroup mHostView;
 
@@ -236,6 +238,10 @@
         scrollY = Math.max(0, scrollY);
         state.scrollY = (int) (scrollY + bottomOverScroll);
 
+        if (ANCHOR_SCROLLING) {
+            state.anchorViewY = (int) (ambientState.getAnchorViewY() - bottomOverScroll);
+        }
+
         //now init the visible children and update paddings
         int childCount = hostView.getChildCount();
         state.visibleChildren.clear();
@@ -252,6 +258,11 @@
         // iterating over it again, it's filled with the actual resolved value.
 
         for (int i = 0; i < childCount; i++) {
+            if (ANCHOR_SCROLLING) {
+                if (i == ambientState.getAnchorViewIndex()) {
+                    state.anchorViewIndex = state.visibleChildren.size();
+                }
+            }
             ExpandableView v = (ExpandableView) hostView.getChildAt(i);
             if (v.getVisibility() != View.GONE) {
                 if (v == ambientState.getShelf()) {
@@ -350,28 +361,74 @@
     private void updatePositionsForState(StackScrollAlgorithmState algorithmState,
             AmbientState ambientState) {
 
-        // The y coordinate of the current child.
-        float currentYPosition = -algorithmState.scrollY;
-        int childCount = algorithmState.visibleChildren.size();
-        for (int i = 0; i < childCount; i++) {
-            currentYPosition = updateChild(i, algorithmState, ambientState, currentYPosition);
+        if (ANCHOR_SCROLLING) {
+            float currentYPosition = algorithmState.anchorViewY;
+            int childCount = algorithmState.visibleChildren.size();
+            for (int i = algorithmState.anchorViewIndex; i < childCount; i++) {
+                if (i > algorithmState.anchorViewIndex && ambientState.beginsNewSection(i)) {
+                    currentYPosition += mGapHeight;
+                }
+                currentYPosition = updateChild(i, algorithmState, ambientState, currentYPosition,
+                        false /* reverse */);
+            }
+            currentYPosition = algorithmState.anchorViewY;
+            for (int i = algorithmState.anchorViewIndex - 1; i >= 0; i--) {
+                if (ambientState.beginsNewSection(i + 1)) {
+                    currentYPosition -= mGapHeight;
+                }
+                currentYPosition = updateChild(i, algorithmState, ambientState, currentYPosition,
+                        true /* reverse */);
+            }
+        } else {
+            // The y coordinate of the current child.
+            float currentYPosition = -algorithmState.scrollY;
+            int childCount = algorithmState.visibleChildren.size();
+            for (int i = 0; i < childCount; i++) {
+                if (ambientState.beginsNewSection(i)) {
+                    currentYPosition += mGapHeight;
+                }
+                currentYPosition = updateChild(i, algorithmState, ambientState, currentYPosition,
+                        false /* reverse */);
+            }
         }
     }
 
+    /**
+     * Populates the {@link ExpandableViewState} for a single child.
+     *
+     * @param i                The index of the child in
+     * {@link StackScrollAlgorithmState#visibleChildren}.
+     * @param algorithmState   The overall output state of the algorithm.
+     * @param ambientState     The input state provided to the algorithm.
+     * @param currentYPosition The Y position of the current pass of the algorithm.  For a forward
+     *                         pass, this should be the top of the child; for a reverse pass, the
+     *                         bottom of the child.
+     * @param reverse          Whether we're laying out children in the reverse direction (Y
+     *                         positions
+     *                         decreasing) instead of the forward direction (Y positions
+     *                         increasing).
+     * @return The Y position after laying out the child.  This will be the {@code currentYPosition}
+     * for the next call to this method, after adjusting for any gaps between children.
+     */
     protected float updateChild(
             int i,
             StackScrollAlgorithmState algorithmState,
             AmbientState ambientState,
-            float currentYPosition) {
+            float currentYPosition,
+            boolean reverse) {
         ExpandableView child = algorithmState.visibleChildren.get(i);
         ExpandableViewState childViewState = child.getViewState();
         childViewState.location = ExpandableViewState.LOCATION_UNKNOWN;
         int paddingAfterChild = getPaddingAfterChild(algorithmState, child);
         int childHeight = getMaxAllowedChildHeight(child);
-        if (ambientState.beginsNewSection(i)) {
-            currentYPosition += mGapHeight;
+        if (reverse) {
+            childViewState.yTranslation = currentYPosition - (childHeight + paddingAfterChild);
+            if (currentYPosition <= 0) {
+                childViewState.location = ExpandableViewState.LOCATION_HIDDEN_TOP;
+            }
+        } else {
+            childViewState.yTranslation = currentYPosition;
         }
-        childViewState.yTranslation = currentYPosition;
         boolean isFooterView = child instanceof FooterView;
         boolean isEmptyShadeView = child instanceof EmptyShadeView;
 
@@ -396,9 +453,13 @@
             clampPositionToShelf(child, childViewState, ambientState);
         }
 
-        currentYPosition = childViewState.yTranslation + childHeight + paddingAfterChild;
-        if (currentYPosition <= 0) {
-            childViewState.location = ExpandableViewState.LOCATION_HIDDEN_TOP;
+        if (reverse) {
+            currentYPosition = childViewState.yTranslation;
+        } else {
+            currentYPosition = childViewState.yTranslation + childHeight + paddingAfterChild;
+            if (currentYPosition <= 0) {
+                childViewState.location = ExpandableViewState.LOCATION_HIDDEN_TOP;
+            }
         }
         if (childViewState.location == ExpandableViewState.LOCATION_UNKNOWN) {
             Log.wtf(LOG_TAG, "Failed to assign location for child " + i);
@@ -464,6 +525,7 @@
                 // To check if the row need to do translation according to scroll Y
                 // heads up show full of row's content and any scroll y indicate that the
                 // translationY need to move up the HUN.
+                // TODO: fix this check for anchor scrolling.
                 if (!mIsExpanded && isTopEntry && ambientState.getScrollY() > 0) {
                     childState.yTranslation -= ambientState.getScrollY();
                 }
@@ -607,10 +669,18 @@
     public class StackScrollAlgorithmState {
 
         /**
-         * The scroll position of the algorithm
+         * The scroll position of the algorithm (absolute scrolling).
          */
         public int scrollY;
 
+        /** The index of the anchor view (anchor scrolling). */
+        public int anchorViewIndex;
+
+        /**
+         * The Y position, relative to the top of the screen, of the anchor view (anchor scrolling).
+         */
+        public int anchorViewY;
+
         /**
          * The children from the host view which are not gone.
          */
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/NearestTouchFrame.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/NearestTouchFrame.java
index 2a11c26..d022808 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/NearestTouchFrame.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/NearestTouchFrame.java
@@ -97,10 +97,11 @@
         }
         return mClickableChildren
                 .stream()
-                .filter(v -> v.isAttachedToWindow())
+                .filter(View::isAttachedToWindow)
                 .map(v -> new Pair<>(distance(v, event), v))
                 .min(Comparator.comparingInt(f -> f.first))
-                .get().second;
+                .map(data -> data.second)
+                .orElse(null);
     }
 
     private int distance(View v, MotionEvent event) {
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/phone/StatusBarNotificationActivityStarter.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarNotificationActivityStarter.java
index 4f61009..86326be 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarNotificationActivityStarter.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarNotificationActivityStarter.java
@@ -64,6 +64,7 @@
 import com.android.systemui.statusbar.notification.NotificationEntryManager;
 import com.android.systemui.statusbar.notification.NotificationInterruptionStateProvider;
 import com.android.systemui.statusbar.notification.collection.NotificationEntry;
+import com.android.systemui.statusbar.notification.logging.NotificationLogger;
 import com.android.systemui.statusbar.notification.row.ExpandableNotificationRow;
 import com.android.systemui.statusbar.policy.HeadsUpUtil;
 import com.android.systemui.statusbar.policy.KeyguardMonitor;
@@ -304,8 +305,11 @@
         final int count =
                 mEntryManager.getNotificationData().getActiveNotifications().size();
         final int rank = mEntryManager.getNotificationData().getRank(notificationKey);
+        NotificationVisibility.NotificationLocation location =
+                NotificationLogger.getNotificationLocation(
+                        mEntryManager.getNotificationData().get(notificationKey));
         final NotificationVisibility nv = NotificationVisibility.obtain(notificationKey,
-                rank, count, true);
+                rank, count, true, location);
         try {
             mBarService.onNotificationClick(notificationKey, nv);
         } catch (RemoteException ex) {
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarWindowController.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarWindowController.java
index 88f9048..ffaa236 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarWindowController.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarWindowController.java
@@ -69,13 +69,13 @@
     private final WindowManager mWindowManager;
     private final IActivityManager mActivityManager;
     private final DozeParameters mDozeParameters;
+    private final WindowManager.LayoutParams mLpChanged;
+    private final boolean mKeyguardScreenRotation;
     private ViewGroup mStatusBarView;
     private WindowManager.LayoutParams mLp;
-    private WindowManager.LayoutParams mLpChanged;
     private boolean mHasTopUi;
     private boolean mHasTopUiChanged;
     private int mBarHeight;
-    private final boolean mKeyguardScreenRotation;
     private float mScreenBrightnessDoze;
     private final State mCurrentState = new State();
     private OtherwisedCollapsedListener mListener;
@@ -97,6 +97,7 @@
         mKeyguardScreenRotation = shouldEnableKeyguardScreenRotation();
         mDozeParameters = dozeParameters;
         mScreenBrightnessDoze = mDozeParameters.getScreenBrightnessDoze();
+        mLpChanged = new WindowManager.LayoutParams();
         Dependency.get(StatusBarStateController.class).addCallback(
                 mStateListener, StatusBarStateController.RANK_STATUS_BAR_WINDOW_CONTROLLER);
         Dependency.get(ConfigurationController.class).addCallback(this);
@@ -138,7 +139,6 @@
         mStatusBarView = statusBarView;
         mBarHeight = barHeight;
         mWindowManager.addView(mStatusBarView, mLp);
-        mLpChanged = new WindowManager.LayoutParams();
         mLpChanged.copyFrom(mLp);
         onThemeChanged();
     }
@@ -228,7 +228,9 @@
     private void applyHeight(State state) {
         boolean expanded = isExpanded(state);
         if (state.forcePluginOpen) {
-            mListener.setWouldOtherwiseCollapse(expanded);
+            if (mListener != null) {
+                mListener.setWouldOtherwiseCollapse(expanded);
+            }
             expanded = true;
         }
         if (expanded) {
@@ -247,7 +249,7 @@
 
     private void applyFitsSystemWindows(State state) {
         boolean fitsSystemWindows = !state.isKeyguardShowingAndNotOccluded();
-        if (mStatusBarView.getFitsSystemWindows() != fitsSystemWindows) {
+        if (mStatusBarView != null && mStatusBarView.getFitsSystemWindows() != fitsSystemWindows) {
             mStatusBarView.setFitsSystemWindows(fitsSystemWindows);
             mStatusBarView.requestApplyInsets();
         }
@@ -289,7 +291,7 @@
         applyBrightness(state);
         applyHasTopUi(state);
         applyNotTouchable(state);
-        if (mLp.copyFrom(mLpChanged) != 0) {
+        if (mLp != null && mLp.copyFrom(mLpChanged) != 0) {
             mWindowManager.updateViewLayout(mStatusBarView, mLp);
         }
         if (mHasTopUi != mHasTopUiChanged) {
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/policy/RemoteInputView.java b/packages/SystemUI/src/com/android/systemui/statusbar/policy/RemoteInputView.java
index 9c4db34..7881df9 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/policy/RemoteInputView.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/RemoteInputView.java
@@ -188,6 +188,7 @@
                 LayoutInflater.from(context).inflate(R.layout.remote_input, root, false);
         v.mController = controller;
         v.mEntry = entry;
+        v.mEditText.setTextOperationUser(computeTextOperationUser(entry.notification.getUser()));
         v.setTag(VIEW_TAG);
 
         return v;
@@ -298,7 +299,6 @@
         if (mWrapper != null) {
             mWrapper.setRemoteInputVisible(true);
         }
-        mEditText.setTextOperationUser(computeTextOperationUser(mEntry.notification.getUser()));
         mEditText.setInnerFocusable(true);
         mEditText.mShowImeOnInputConnection = true;
         mEditText.setText(mEntry.remoteInputText);
@@ -328,7 +328,6 @@
         mResetting = true;
         mEntry.remoteInputTextWhenReset = SpannedString.valueOf(mEditText.getText());
 
-        mEditText.setTextOperationUser(null);
         mEditText.getText().clear();
         mEditText.setEnabled(true);
         mSendButton.setVisibility(VISIBLE);
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/policy/SmartReplyConstants.java b/packages/SystemUI/src/com/android/systemui/statusbar/policy/SmartReplyConstants.java
index 3bd0d45..db04620 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/policy/SmartReplyConstants.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/SmartReplyConstants.java
@@ -46,18 +46,21 @@
     private static final String KEY_EDIT_CHOICES_BEFORE_SENDING =
             "edit_choices_before_sending";
     private static final String KEY_SHOW_IN_HEADS_UP = "show_in_heads_up";
+    private static final String KEY_MIN_NUM_REPLIES = "min_num_system_generated_replies";
 
     private final boolean mDefaultEnabled;
     private final boolean mDefaultRequiresP;
     private final int mDefaultMaxSqueezeRemeasureAttempts;
     private final boolean mDefaultEditChoicesBeforeSending;
     private final boolean mDefaultShowInHeadsUp;
+    private final int mDefaultMinNumSystemGeneratedReplies;
 
     private boolean mEnabled;
     private boolean mRequiresTargetingP;
     private int mMaxSqueezeRemeasureAttempts;
     private boolean mEditChoicesBeforeSending;
     private boolean mShowInHeadsUp;
+    private int mMinNumSystemGeneratedReplies;
 
     private final Context mContext;
     private final KeyValueListParser mParser = new KeyValueListParser(',');
@@ -78,6 +81,8 @@
                 R.bool.config_smart_replies_in_notifications_edit_choices_before_sending);
         mDefaultShowInHeadsUp = resources.getBoolean(
                 R.bool.config_smart_replies_in_notifications_show_in_heads_up);
+        mDefaultMinNumSystemGeneratedReplies = resources.getInteger(
+                R.integer.config_smart_replies_in_notifications_min_num_system_generated_replies);
 
         mContext.getContentResolver().registerContentObserver(
                 Settings.Global.getUriFor(Settings.Global.SMART_REPLIES_IN_NOTIFICATIONS_FLAGS),
@@ -105,6 +110,8 @@
             mEditChoicesBeforeSending = mParser.getBoolean(
                     KEY_EDIT_CHOICES_BEFORE_SENDING, mDefaultEditChoicesBeforeSending);
             mShowInHeadsUp = mParser.getBoolean(KEY_SHOW_IN_HEADS_UP, mDefaultShowInHeadsUp);
+            mMinNumSystemGeneratedReplies =
+                    mParser.getInt(KEY_MIN_NUM_REPLIES, mDefaultMinNumSystemGeneratedReplies);
         }
     }
 
@@ -155,4 +162,12 @@
     public boolean getShowInHeadsUp() {
         return mShowInHeadsUp;
     }
+
+    /**
+     * Returns the minimum number of system generated replies to show in a notification.
+     * If we cannot show at least this many system generated replies we should show none.
+     */
+    public int getMinNumSystemGeneratedReplies() {
+        return mMinNumSystemGeneratedReplies;
+    }
 }
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 d6eff94..c4f027f 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/policy/SmartReplyView.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/SmartReplyView.java
@@ -88,6 +88,12 @@
 
     private View mSmartReplyContainer;
 
+    /**
+     * Whether the smart replies in this view were generated by the notification assistant. If not
+     * they're provided by the app.
+     */
+    private boolean mSmartRepliesGeneratedByAssistant = false;
+
     @ColorInt
     private int mCurrentBackgroundColor;
     @ColorInt
@@ -202,6 +208,7 @@
                             getContext(), this, i, smartReplies, smartReplyController, entry);
                     addView(replyButton);
                 }
+                this.mSmartRepliesGeneratedByAssistant = smartReplies.fromAssistant;
             }
         }
         reallocateCandidateButtonQueueForSqueezing();
@@ -344,10 +351,11 @@
             mCandidateButtonQueueForSqueezing.clear();
         }
 
-        int measuredWidth = mPaddingLeft + mPaddingRight;
-        int maxChildHeight = 0;
+        SmartSuggestionMeasures accumulatedMeasures = new SmartSuggestionMeasures(
+                mPaddingLeft + mPaddingRight,
+                0 /* maxChildHeight */,
+                mSingleLineButtonPaddingHorizontal);
         int displayedChildCount = 0;
-        int buttonPaddingHorizontal = mSingleLineButtonPaddingHorizontal;
 
         // Set up a list of suggestions where actions come before replies. Note that the Buttons
         // themselves have already been added to the view hierarchy in an order such that Smart
@@ -360,11 +368,15 @@
         smartSuggestions.addAll(smartReplies);
         List<View> coveredSuggestions = new ArrayList<>();
 
+        // SmartSuggestionMeasures for all action buttons, this will be filled in when the first
+        // reply button is added.
+        SmartSuggestionMeasures actionsMeasures = null;
+
         for (View child : smartSuggestions) {
             final LayoutParams lp = (LayoutParams) child.getLayoutParams();
 
-            child.setPadding(buttonPaddingHorizontal, child.getPaddingTop(),
-                    buttonPaddingHorizontal, child.getPaddingBottom());
+            child.setPadding(accumulatedMeasures.mButtonPaddingHorizontal, child.getPaddingTop(),
+                    accumulatedMeasures.mButtonPaddingHorizontal, child.getPaddingBottom());
             child.measure(MEASURE_SPEC_ANY_LENGTH, heightMeasureSpec);
 
             coveredSuggestions.add(child);
@@ -380,45 +392,52 @@
             }
 
             // Remember the current measurements in case the current button doesn't fit in.
-            final int originalMaxChildHeight = maxChildHeight;
-            final int originalMeasuredWidth = measuredWidth;
-            final int originalButtonPaddingHorizontal = buttonPaddingHorizontal;
+            SmartSuggestionMeasures originalMeasures = accumulatedMeasures.clone();
+            if (actionsMeasures == null && lp.buttonType == SmartButtonType.REPLY) {
+                // We've added all actions (we go through actions first), now add their
+                // measurements.
+                actionsMeasures = accumulatedMeasures.clone();
+            }
 
             final int spacing = displayedChildCount == 0 ? 0 : mSpacing;
             final int childWidth = child.getMeasuredWidth();
             final int childHeight = child.getMeasuredHeight();
-            measuredWidth += spacing + childWidth;
-            maxChildHeight = Math.max(maxChildHeight, childHeight);
+            accumulatedMeasures.mMeasuredWidth += spacing + childWidth;
+            accumulatedMeasures.mMaxChildHeight =
+                    Math.max(accumulatedMeasures.mMaxChildHeight, childHeight);
 
             // Do we need to increase the number of lines in smart reply buttons to two?
             final boolean increaseToTwoLines =
-                    buttonPaddingHorizontal == mSingleLineButtonPaddingHorizontal
-                            && (lineCount == 2 || measuredWidth > targetWidth);
+                    (accumulatedMeasures.mButtonPaddingHorizontal
+                            == mSingleLineButtonPaddingHorizontal)
+                    && (lineCount == 2 || accumulatedMeasures.mMeasuredWidth > targetWidth);
             if (increaseToTwoLines) {
-                measuredWidth += (displayedChildCount + 1) * mSingleToDoubleLineButtonWidthIncrease;
-                buttonPaddingHorizontal = mDoubleLineButtonPaddingHorizontal;
+                accumulatedMeasures.mMeasuredWidth +=
+                        (displayedChildCount + 1) * mSingleToDoubleLineButtonWidthIncrease;
+                accumulatedMeasures.mButtonPaddingHorizontal =
+                        mDoubleLineButtonPaddingHorizontal;
             }
 
             // If the last button doesn't fit into the remaining width, try squeezing preceding
             // smart reply buttons.
-            if (measuredWidth > targetWidth) {
+            if (accumulatedMeasures.mMeasuredWidth > targetWidth) {
                 // Keep squeezing preceding and current smart reply buttons until they all fit.
-                while (measuredWidth > targetWidth
+                while (accumulatedMeasures.mMeasuredWidth > targetWidth
                         && !mCandidateButtonQueueForSqueezing.isEmpty()) {
                     final Button candidate = mCandidateButtonQueueForSqueezing.poll();
                     final int squeezeReduction = squeezeButton(candidate, heightMeasureSpec);
                     if (squeezeReduction != SQUEEZE_FAILED) {
-                        maxChildHeight = Math.max(maxChildHeight, candidate.getMeasuredHeight());
-                        measuredWidth -= squeezeReduction;
+                        accumulatedMeasures.mMaxChildHeight =
+                                Math.max(accumulatedMeasures.mMaxChildHeight,
+                                        candidate.getMeasuredHeight());
+                        accumulatedMeasures.mMeasuredWidth -= squeezeReduction;
                     }
                 }
 
                 // If the current button still doesn't fit after squeezing all buttons, undo the
                 // last squeezing round.
-                if (measuredWidth > targetWidth) {
-                    measuredWidth = originalMeasuredWidth;
-                    maxChildHeight = originalMaxChildHeight;
-                    buttonPaddingHorizontal = originalButtonPaddingHorizontal;
+                if (accumulatedMeasures.mMeasuredWidth > targetWidth) {
+                    accumulatedMeasures = originalMeasures;
 
                     // Mark all buttons from the last squeezing round as "failed to squeeze", so
                     // that they're re-measured without squeezing later.
@@ -440,16 +459,75 @@
             displayedChildCount++;
         }
 
+        if (mSmartRepliesGeneratedByAssistant) {
+            if (!gotEnoughSmartReplies(smartReplies)) {
+                // We don't have enough smart replies - hide all of them.
+                for (View smartReplyButton : smartReplies) {
+                    final LayoutParams lp = (LayoutParams) smartReplyButton.getLayoutParams();
+                    lp.show = false;
+                }
+                // Reset our measures back to when we had only added actions (before adding
+                // replies).
+                accumulatedMeasures = actionsMeasures;
+            }
+        }
+
         // We're done squeezing buttons, so we can clear the priority queue.
         mCandidateButtonQueueForSqueezing.clear();
 
         // Finally, we need to re-measure some buttons.
-        remeasureButtonsIfNecessary(buttonPaddingHorizontal, maxChildHeight);
+        remeasureButtonsIfNecessary(accumulatedMeasures.mButtonPaddingHorizontal,
+                                    accumulatedMeasures.mMaxChildHeight);
 
         setMeasuredDimension(
-                resolveSize(Math.max(getSuggestedMinimumWidth(), measuredWidth), widthMeasureSpec),
-                resolveSize(Math.max(getSuggestedMinimumHeight(),
-                        mPaddingTop + maxChildHeight + mPaddingBottom), heightMeasureSpec));
+                resolveSize(Math.max(getSuggestedMinimumWidth(),
+                                     accumulatedMeasures.mMeasuredWidth),
+                            widthMeasureSpec),
+                resolveSize(Math.max(getSuggestedMinimumHeight(), mPaddingTop
+                        + accumulatedMeasures.mMaxChildHeight + mPaddingBottom),
+                            heightMeasureSpec));
+    }
+
+    /**
+     * Fields we keep track of inside onMeasure() to correctly measure the SmartReplyView depending
+     * on which suggestions are added.
+     */
+    private static class SmartSuggestionMeasures {
+        int mMeasuredWidth = -1;
+        int mMaxChildHeight = -1;
+        int mButtonPaddingHorizontal = -1;
+
+        SmartSuggestionMeasures(int measuredWidth, int maxChildHeight,
+                int buttonPaddingHorizontal) {
+            this.mMeasuredWidth = measuredWidth;
+            this.mMaxChildHeight = maxChildHeight;
+            this.mButtonPaddingHorizontal = buttonPaddingHorizontal;
+        }
+
+        public SmartSuggestionMeasures clone() {
+            return new SmartSuggestionMeasures(
+                    mMeasuredWidth, mMaxChildHeight, mButtonPaddingHorizontal);
+        }
+    }
+
+    /**
+     * Returns whether our notification contains at least N smart replies (or 0) where N is
+     * determined by {@link SmartReplyConstants}.
+     */
+    private boolean gotEnoughSmartReplies(List<View> smartReplies) {
+        int numShownReplies = 0;
+        for (View smartReplyButton : smartReplies) {
+            final LayoutParams lp = (LayoutParams) smartReplyButton.getLayoutParams();
+            if (lp.show) {
+                numShownReplies++;
+            }
+        }
+        if (numShownReplies == 0
+                || numShownReplies >= mConstants.getMinNumSystemGeneratedReplies()) {
+            // We have enough replies, yay!
+            return true;
+        }
+        return false;
     }
 
     private List<View> filterActionsOrReplies(SmartButtonType buttonType) {
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/src/com/android/systemui/usb/UsbDebuggingActivity.java b/packages/SystemUI/src/com/android/systemui/usb/UsbDebuggingActivity.java
index ed2ad79..12006fa 100644
--- a/packages/SystemUI/src/com/android/systemui/usb/UsbDebuggingActivity.java
+++ b/packages/SystemUI/src/com/android/systemui/usb/UsbDebuggingActivity.java
@@ -75,7 +75,7 @@
         final AlertController.AlertParams ap = mAlertParams;
         ap.mTitle = getString(R.string.usb_debugging_title);
         ap.mMessage = getString(R.string.usb_debugging_message, fingerprints);
-        ap.mPositiveButtonText = getString(android.R.string.ok);
+        ap.mPositiveButtonText = getString(R.string.usb_debugging_allow);
         ap.mNegativeButtonText = getString(android.R.string.cancel);
         ap.mPositiveButtonListener = this;
         ap.mNegativeButtonListener = this;
diff --git a/packages/SystemUI/src/com/android/systemui/volume/MediaSessions.java b/packages/SystemUI/src/com/android/systemui/volume/MediaSessions.java
index 712ea27..8b00eee 100644
--- a/packages/SystemUI/src/com/android/systemui/volume/MediaSessions.java
+++ b/packages/SystemUI/src/com/android/systemui/volume/MediaSessions.java
@@ -25,9 +25,9 @@
 import android.content.pm.ResolveInfo;
 import android.media.IRemoteVolumeController;
 import android.media.MediaMetadata;
-import android.media.session.ISessionController;
 import android.media.session.MediaController;
 import android.media.session.MediaController.PlaybackInfo;
+import android.media.session.MediaSession;
 import android.media.session.MediaSession.QueueItem;
 import android.media.session.MediaSession.Token;
 import android.media.session.MediaSessionManager;
@@ -113,17 +113,17 @@
         r.controller.setVolumeTo(level, 0);
     }
 
-    private void onRemoteVolumeChangedH(ISessionController session, int flags) {
-        final MediaController controller = new MediaController(mContext, session);
+    private void onRemoteVolumeChangedH(MediaSession.Token sessionToken, int flags) {
+        final MediaController controller = new MediaController(mContext, sessionToken);
         if (D.BUG) Log.d(TAG, "remoteVolumeChangedH " + controller.getPackageName() + " "
                 + Util.audioManagerFlagsToString(flags));
         final Token token = controller.getSessionToken();
         mCallbacks.onRemoteVolumeChanged(token, flags);
     }
 
-    private void onUpdateRemoteControllerH(ISessionController session) {
-        final MediaController controller = session != null ? new MediaController(mContext, session)
-                : null;
+    private void onUpdateRemoteControllerH(MediaSession.Token sessionToken) {
+        final MediaController controller =
+                sessionToken != null ? new MediaController(mContext, sessionToken) : null;
         final String pkg = controller != null ? controller.getPackageName() : null;
         if (D.BUG) Log.d(TAG, "updateRemoteControllerH " + pkg);
         // this may be our only indication that a remote session is changed, refresh
@@ -332,15 +332,16 @@
 
     private final IRemoteVolumeController mRvc = new IRemoteVolumeController.Stub() {
         @Override
-        public void remoteVolumeChanged(ISessionController session, int flags)
+        public void remoteVolumeChanged(MediaSession.Token sessionToken, int flags)
                 throws RemoteException {
-            mHandler.obtainMessage(H.REMOTE_VOLUME_CHANGED, flags, 0, session).sendToTarget();
+            mHandler.obtainMessage(H.REMOTE_VOLUME_CHANGED, flags, 0,
+                    sessionToken).sendToTarget();
         }
 
         @Override
-        public void updateRemoteController(final ISessionController session)
+        public void updateRemoteController(final MediaSession.Token sessionToken)
                 throws RemoteException {
-            mHandler.obtainMessage(H.UPDATE_REMOTE_CONTROLLER, session).sendToTarget();
+            mHandler.obtainMessage(H.UPDATE_REMOTE_CONTROLLER, sessionToken).sendToTarget();
         }
     };
 
@@ -360,10 +361,10 @@
                     onActiveSessionsUpdatedH(mMgr.getActiveSessions(null));
                     break;
                 case REMOTE_VOLUME_CHANGED:
-                    onRemoteVolumeChangedH((ISessionController) msg.obj, msg.arg1);
+                    onRemoteVolumeChangedH((MediaSession.Token) msg.obj, msg.arg1);
                     break;
                 case UPDATE_REMOTE_CONTROLLER:
-                    onUpdateRemoteControllerH((ISessionController) msg.obj);
+                    onUpdateRemoteControllerH((MediaSession.Token) msg.obj);
                     break;
             }
         }
diff --git a/packages/SystemUI/tests/src/com/android/keyguard/KeyguardClockSwitchTest.java b/packages/SystemUI/tests/src/com/android/keyguard/KeyguardClockSwitchTest.java
index fbc1c20..d80b444 100644
--- a/packages/SystemUI/tests/src/com/android/keyguard/KeyguardClockSwitchTest.java
+++ b/packages/SystemUI/tests/src/com/android/keyguard/KeyguardClockSwitchTest.java
@@ -39,6 +39,7 @@
 import android.widget.FrameLayout;
 import android.widget.TextClock;
 
+import com.android.keyguard.clock.ClockManager;
 import com.android.systemui.SysuiTestCase;
 import com.android.systemui.plugins.ClockPlugin;
 import com.android.systemui.statusbar.StatusBarState;
@@ -51,8 +52,6 @@
 import org.mockito.Mock;
 import org.mockito.MockitoAnnotations;
 
-import java.util.function.Consumer;
-
 @SmallTest
 @RunWith(AndroidTestingRunner.class)
 // Need to run on the main thread because KeyguardSliceView$Row init checks for
@@ -85,7 +84,7 @@
         TextClock pluginView = new TextClock(getContext());
         when(plugin.getView()).thenReturn(pluginView);
 
-        mKeyguardClockSwitch.getClockPluginConsumer().accept(plugin);
+        mKeyguardClockSwitch.getClockChangedListener().onClockChanged(plugin);
 
         verify(mClockView).setVisibility(GONE);
         assertThat(plugin.getView().getParent()).isEqualTo(mClockContainer);
@@ -102,7 +101,7 @@
         TextClock pluginView = new TextClock(getContext());
         when(plugin.getBigClockView()).thenReturn(pluginView);
         // WHEN the plugin is connected
-        mKeyguardClockSwitch.getClockPluginConsumer().accept(plugin);
+        mKeyguardClockSwitch.getClockChangedListener().onClockChanged(plugin);
         // THEN the big clock container is visible and it is the parent of the
         // big clock view.
         assertThat(bigClockContainer.getVisibility()).isEqualTo(VISIBLE);
@@ -112,7 +111,7 @@
     @Test
     public void onPluginConnected_nullView() {
         ClockPlugin plugin = mock(ClockPlugin.class);
-        mKeyguardClockSwitch.getClockPluginConsumer().accept(plugin);
+        mKeyguardClockSwitch.getClockChangedListener().onClockChanged(plugin);
         verify(mClockView, never()).setVisibility(GONE);
     }
 
@@ -121,11 +120,11 @@
         // GIVEN a plugin has already connected
         ClockPlugin plugin1 = mock(ClockPlugin.class);
         when(plugin1.getView()).thenReturn(new TextClock(getContext()));
-        mKeyguardClockSwitch.getClockPluginConsumer().accept(plugin1);
+        mKeyguardClockSwitch.getClockChangedListener().onClockChanged(plugin1);
         // WHEN a second plugin is connected
         ClockPlugin plugin2 = mock(ClockPlugin.class);
         when(plugin2.getView()).thenReturn(new TextClock(getContext()));
-        mKeyguardClockSwitch.getClockPluginConsumer().accept(plugin2);
+        mKeyguardClockSwitch.getClockChangedListener().onClockChanged(plugin2);
         // THEN only the view from the second plugin should be a child of KeyguardClockSwitch.
         assertThat(plugin2.getView().getParent()).isEqualTo(mClockContainer);
         assertThat(plugin1.getView().getParent()).isNull();
@@ -137,7 +136,7 @@
         mKeyguardClockSwitch.setDarkAmount(0.5f);
         // WHEN a plugin is connected
         ClockPlugin plugin = mock(ClockPlugin.class);
-        mKeyguardClockSwitch.getClockPluginConsumer().accept(plugin);
+        mKeyguardClockSwitch.getClockChangedListener().onClockChanged(plugin);
         // THEN dark amount should be initalized on the plugin.
         verify(plugin).setDarkAmount(0.5f);
     }
@@ -149,8 +148,8 @@
         when(plugin.getView()).thenReturn(pluginView);
         mClockView.setVisibility(GONE);
 
-        mKeyguardClockSwitch.getClockPluginConsumer().accept(plugin);
-        mKeyguardClockSwitch.getClockPluginConsumer().accept(null);
+        mKeyguardClockSwitch.getClockChangedListener().onClockChanged(plugin);
+        mKeyguardClockSwitch.getClockChangedListener().onClockChanged(null);
 
         verify(mClockView).setVisibility(VISIBLE);
         assertThat(plugin.getView().getParent()).isNull();
@@ -167,8 +166,8 @@
         TextClock pluginView = new TextClock(getContext());
         when(plugin.getBigClockView()).thenReturn(pluginView);
         // WHEN the plugin is connected and then disconnected
-        mKeyguardClockSwitch.getClockPluginConsumer().accept(plugin);
-        mKeyguardClockSwitch.getClockPluginConsumer().accept(null);
+        mKeyguardClockSwitch.getClockChangedListener().onClockChanged(plugin);
+        mKeyguardClockSwitch.getClockChangedListener().onClockChanged(null);
         // THEN the big lock container is GONE and the big clock view doesn't have
         // a parent.
         assertThat(bigClockContainer.getVisibility()).isEqualTo(GONE);
@@ -178,8 +177,8 @@
     @Test
     public void onPluginDisconnected_nullView() {
         ClockPlugin plugin = mock(ClockPlugin.class);
-        mKeyguardClockSwitch.getClockPluginConsumer().accept(plugin);
-        mKeyguardClockSwitch.getClockPluginConsumer().accept(null);
+        mKeyguardClockSwitch.getClockChangedListener().onClockChanged(plugin);
+        mKeyguardClockSwitch.getClockChangedListener().onClockChanged(null);
         verify(mClockView, never()).setVisibility(GONE);
     }
 
@@ -188,13 +187,13 @@
         // GIVEN two plugins are connected
         ClockPlugin plugin1 = mock(ClockPlugin.class);
         when(plugin1.getView()).thenReturn(new TextClock(getContext()));
-        Consumer<ClockPlugin> consumer = mKeyguardClockSwitch.getClockPluginConsumer();
-        consumer.accept(plugin1);
+        ClockManager.ClockChangedListener listener = mKeyguardClockSwitch.getClockChangedListener();
+        listener.onClockChanged(plugin1);
         ClockPlugin plugin2 = mock(ClockPlugin.class);
         when(plugin2.getView()).thenReturn(new TextClock(getContext()));
-        consumer.accept(plugin2);
+        listener.onClockChanged(plugin2);
         // WHEN the second plugin is disconnected
-        consumer.accept(null);
+        listener.onClockChanged(null);
         // THEN the default clock should be shown.
         verify(mClockView).setVisibility(VISIBLE);
         assertThat(plugin1.getView().getParent()).isNull();
@@ -213,7 +212,7 @@
         ClockPlugin plugin = mock(ClockPlugin.class);
         TextClock pluginView = new TextClock(getContext());
         when(plugin.getView()).thenReturn(pluginView);
-        mKeyguardClockSwitch.getClockPluginConsumer().accept(plugin);
+        mKeyguardClockSwitch.getClockChangedListener().onClockChanged(plugin);
 
         mKeyguardClockSwitch.setTextColor(Color.WHITE);
 
@@ -237,7 +236,7 @@
         TextClock pluginView = new TextClock(getContext());
         when(plugin.getView()).thenReturn(pluginView);
         Style style = mock(Style.class);
-        mKeyguardClockSwitch.getClockPluginConsumer().accept(plugin);
+        mKeyguardClockSwitch.getClockChangedListener().onClockChanged(plugin);
 
         mKeyguardClockSwitch.setStyle(style);
 
diff --git a/packages/SystemUI/tests/src/com/android/systemui/ImageWallpaperTest.java b/packages/SystemUI/tests/src/com/android/systemui/ImageWallpaperTest.java
index 53ad0b5..fc57909 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/ImageWallpaperTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/ImageWallpaperTest.java
@@ -68,7 +68,7 @@
             public Engine onCreateEngine() {
                 return new DrawableEngine() {
                     @Override
-                    DisplayInfo getDefaultDisplayInfo() {
+                    DisplayInfo getDisplayInfo() {
                         return mDisplayInfo;
                     }
 
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 fa5cf04..e802757 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/bubbles/BubbleControllerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/bubbles/BubbleControllerTest.java
@@ -16,6 +16,7 @@
 
 package com.android.systemui.bubbles;
 
+import static org.junit.Assert.assertEquals;
 import static org.junit.Assert.assertFalse;
 import static org.junit.Assert.assertTrue;
 import static org.mockito.Mockito.atLeastOnce;
@@ -60,7 +61,6 @@
     private IActivityManager mActivityManager;
     @Mock
     private DozeParameters mDozeParameters;
-    @Mock
     private FrameLayout mStatusBarView;
     @Captor
     private ArgumentCaptor<NotificationEntryListener> mEntryListenerCaptor;
@@ -79,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
@@ -160,6 +161,12 @@
         stackView.expandStack();
         assertTrue(mBubbleController.isStackExpanded());
 
+        stackView.setExpandedBubble(mRow.getEntry());
+        assertEquals(stackView.getExpandedBubble().getEntry(), mRow.getEntry());
+
+        stackView.setExpandedBubble(mRow2.getEntry());
+        assertEquals(stackView.getExpandedBubble().getEntry(), mRow2.getEntry());
+
         mBubbleController.collapseStack();
         assertFalse(mBubbleController.isStackExpanded());
     }
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/dock/DockManagerFake.java b/packages/SystemUI/tests/src/com/android/systemui/dock/DockManagerFake.java
index b368876..839b5e4 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/dock/DockManagerFake.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/dock/DockManagerFake.java
@@ -32,6 +32,11 @@
         this.mCallback = null;
     }
 
+    @Override
+    public boolean isDocked() {
+        return false;
+    }
+
     public void setDockEvent(int event) {
         mCallback.onEvent(event);
     }
diff --git a/packages/SystemUI/tests/src/com/android/systemui/doze/DozeConfigurationUtil.java b/packages/SystemUI/tests/src/com/android/systemui/doze/DozeConfigurationUtil.java
index f45500a..e4558df 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/doze/DozeConfigurationUtil.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/doze/DozeConfigurationUtil.java
@@ -36,6 +36,8 @@
         when(params.getPickupVibrationThreshold()).thenReturn(0);
         when(params.getProxCheckBeforePulse()).thenReturn(true);
         when(params.getPickupSubtypePerformsProxCheck(anyInt())).thenReturn(true);
+        when(params.getPolicy()).thenReturn(mock(AlwaysOnDisplayPolicy.class));
+        when(params.doubleTapReportsTouchCoordinates()).thenReturn(false);
 
         doneHolder[0] = true;
         return params;
@@ -48,9 +50,14 @@
         when(config.doubleTapGestureEnabled(anyInt())).thenReturn(false);
         when(config.pickupGestureEnabled(anyInt())).thenReturn(false);
         when(config.pulseOnNotificationEnabled(anyInt())).thenReturn(true);
+        when(config.alwaysOnEnabled(anyInt())).thenReturn(false);
 
         when(config.doubleTapSensorType()).thenReturn(null);
+        when(config.tapSensorType()).thenReturn(null);
+        when(config.longPressSensorType()).thenReturn(null);
+
         when(config.dozePickupSensorAvailable()).thenReturn(false);
+        when(config.wakeScreenGestureAvailable()).thenReturn(false);
 
         doneHolder[0] = true;
         return config;
diff --git a/packages/SystemUI/tests/src/com/android/systemui/doze/DozeDockHandlerTest.java b/packages/SystemUI/tests/src/com/android/systemui/doze/DozeDockHandlerTest.java
index 926ff69..0fc0953 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/doze/DozeDockHandlerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/doze/DozeDockHandlerTest.java
@@ -75,7 +75,7 @@
         mContext.putComponent(DockManager.class, mDockManagerFake);
 
         mDockHandler = new DozeDockHandler(mContext, mMachine, mHost, mConfig,
-                Handler.createAsync(Looper.myLooper()));
+                Handler.createAsync(Looper.myLooper()), mDockManagerFake);
     }
 
     @Test
diff --git a/packages/SystemUI/tests/src/com/android/systemui/doze/DozeTriggersTest.java b/packages/SystemUI/tests/src/com/android/systemui/doze/DozeTriggersTest.java
index 31fc625..7b358b9 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/doze/DozeTriggersTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/doze/DozeTriggersTest.java
@@ -18,8 +18,10 @@
 
 import static org.mockito.ArgumentMatchers.any;
 import static org.mockito.ArgumentMatchers.anyInt;
+import static org.mockito.Mockito.doReturn;
 import static org.mockito.Mockito.mock;
 import static org.mockito.Mockito.never;
+import static org.mockito.Mockito.spy;
 import static org.mockito.Mockito.verify;
 import static org.mockito.Mockito.when;
 
@@ -34,6 +36,8 @@
 
 import com.android.internal.hardware.AmbientDisplayConfiguration;
 import com.android.systemui.SysuiTestCase;
+import com.android.systemui.dock.DockManager;
+import com.android.systemui.dock.DockManagerFake;
 import com.android.systemui.statusbar.phone.DozeParameters;
 import com.android.systemui.util.wakelock.WakeLock;
 import com.android.systemui.util.wakelock.WakeLockFake;
@@ -59,6 +63,7 @@
     private WakeLock mWakeLock;
     private Instrumentation mInstrumentation;
     private AlarmManager mAlarmManager;
+    private DockManagerFake mDockManagerFake;
 
     @BeforeClass
     public static void setupSuite() {
@@ -76,9 +81,12 @@
         mParameters = DozeConfigurationUtil.createMockParameters();
         mSensors = new FakeSensorManager(mContext);
         mWakeLock = new WakeLockFake();
+        mDockManagerFake = spy(new DockManagerFake());
+        mContext.putComponent(DockManager.class, mDockManagerFake);
 
         mTriggers = new DozeTriggers(mContext, mMachine, mHost, mAlarmManager, mConfig, mParameters,
-                mSensors, Handler.createAsync(Looper.myLooper()), mWakeLock, true);
+                mSensors, Handler.createAsync(Looper.myLooper()), mWakeLock, true,
+                mDockManagerFake);
     }
 
     @Test
@@ -102,4 +110,38 @@
         verify(mMachine).requestPulse(anyInt());
     }
 
+    @Test
+    public void testDockEventListener_registerAndUnregister() {
+        mTriggers.transitionTo(DozeMachine.State.UNINITIALIZED, DozeMachine.State.INITIALIZED);
+
+        verify(mDockManagerFake).addListener(any());
+
+        mTriggers.transitionTo(DozeMachine.State.DOZE, DozeMachine.State.FINISH);
+
+        verify(mDockManagerFake).removeListener(any());
+    }
+
+    @Test
+    public void testOnSensor_whenUndockedWithNearAndDoubleTapScreen_shouldNotWakeUp() {
+        mSensors.getMockProximitySensor().sendProximityResult(false /* far */);
+
+        mTriggers.onSensor(DozeLog.PULSE_REASON_SENSOR_DOUBLE_TAP,
+                false /* sensorPerformedProxCheck */, 50 /* screenX */, 50 /* screenY */,
+                null /* rawValues */);
+
+        verify(mMachine, never()).wakeUp();
+    }
+
+    @Test
+    public void testOnSensor_whenDockedWithNearAndDoubleTapScreen_shouldWakeUp() {
+        doReturn(true).when(mDockManagerFake).isDocked();
+        mSensors.getMockProximitySensor().sendProximityResult(false /* far */);
+
+        mTriggers.onSensor(DozeLog.PULSE_REASON_SENSOR_DOUBLE_TAP,
+                false /* sensorPerformedProxCheck */, 50 /* screenX */, 50 /* screenY */,
+                null /* rawValues */);
+
+        verify(mMachine).wakeUp();
+    }
+
 }
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 4b03399..5ff9d14 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
@@ -17,6 +17,7 @@
 
 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.verify;
 
@@ -66,52 +67,61 @@
         waitForUiOffloadThread();
 
         verify(mBarService, Mockito.never()).onNotificationExpansionChanged(
-                eq(NOTIFICATION_KEY), anyBoolean(), anyBoolean());
+                eq(NOTIFICATION_KEY), anyBoolean(), anyBoolean(), anyInt());
     }
 
     @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(
-                eq(NOTIFICATION_KEY), anyBoolean(), anyBoolean());
+                eq(NOTIFICATION_KEY), anyBoolean(), anyBoolean(), anyInt());
     }
 
     @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());
         waitForUiOffloadThread();
 
         verify(mBarService, Mockito.never()).onNotificationExpansionChanged(
-                eq(NOTIFICATION_KEY), anyBoolean(), anyBoolean());
+                eq(NOTIFICATION_KEY), anyBoolean(), anyBoolean(), anyInt());
     }
 
     @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);
+                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);
+                NOTIFICATION_KEY, false, true,
+                // The last location seen should be logged (the one passed to onVisibilityChanged).
+                NotificationVisibility.NotificationLocation.LOCATION_MAIN_AREA.toMetricsEventEnum()
+        );
     }
 
     @Test
@@ -119,11 +129,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);
+                NOTIFICATION_KEY, false, true,
+                // The last location seen should be logged (the one passed to onExpansionChanged).
+                NotificationVisibility.NotificationLocation.LOCATION_FIRST_HEADS_UP.toMetricsEventEnum());
     }
 
     @Test
@@ -131,15 +144,24 @@
         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);
+                NOTIFICATION_KEY, false, true,
+                NotificationVisibility.NotificationLocation.LOCATION_UNKNOWN.toMetricsEventEnum());
     }
 
     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/NearestTouchFrameTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/NearestTouchFrameTest.java
index 667a508..4dee438 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/NearestTouchFrameTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/NearestTouchFrameTest.java
@@ -17,7 +17,6 @@
 import static org.mockito.ArgumentMatchers.any;
 import static org.mockito.ArgumentMatchers.eq;
 import static org.mockito.Mockito.doAnswer;
-import static org.mockito.Mockito.mock;
 import static org.mockito.Mockito.never;
 import static org.mockito.Mockito.spy;
 import static org.mockito.Mockito.verify;
@@ -171,6 +170,19 @@
         ev.recycle();
     }
 
+    @Test
+    public void testViewNotAttachedNoCrash() {
+        View view = mockViewAt(0, 20, 10, 10);
+        when(view.isAttachedToWindow()).thenReturn(false);
+        mNearestTouchFrame.addView(view);
+        mNearestTouchFrame.onMeasure(0, 0);
+
+        MotionEvent ev = MotionEvent.obtain(0, 0, 0, 5 /* x */, 18 /* y */, 0);
+        mNearestTouchFrame.onTouchEvent(ev);
+        verify(view, never()).onTouchEvent(eq(ev));
+        ev.recycle();
+    }
+
     private View mockViewAt(int x, int y, int width, int height) {
         View v = spy(new View(mContext));
         doAnswer(invocation -> {
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/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/StatusBarWindowControllerTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/StatusBarWindowControllerTest.java
index 98d0c6b..9996a9e 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/StatusBarWindowControllerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/StatusBarWindowControllerTest.java
@@ -95,4 +95,11 @@
     public void testAdd_updatesVisibilityFlags() {
         verify(mStatusBarView).setSystemUiVisibility(anyInt());
     }
+
+    @Test
+    public void testSetForcePluginOpen_beforeStatusBarInitialization() {
+        mStatusBarWindowController = new StatusBarWindowController(mContext, mWindowManager,
+                mActivityManager, mDozeParameters);
+        mStatusBarWindowController.setForcePluginOpen(true);
+    }
 }
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/SmartReplyConstantsTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/SmartReplyConstantsTest.java
index 3cbf902..03b7c9507 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/SmartReplyConstantsTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/SmartReplyConstantsTest.java
@@ -55,6 +55,9 @@
         resources.addOverride(
                 R.bool.config_smart_replies_in_notifications_edit_choices_before_sending, false);
         resources.addOverride(R.bool.config_smart_replies_in_notifications_show_in_heads_up, true);
+        resources.addOverride(
+                R.integer.config_smart_replies_in_notifications_min_num_system_generated_replies,
+                2);
         mConstants = new SmartReplyConstants(Handler.createAsync(Looper.myLooper()), mContext);
     }
 
@@ -178,6 +181,19 @@
                 Settings.Global.SMART_REPLIES_IN_NOTIFICATIONS_FLAGS, flags);
     }
 
+    @Test
+    public void testGetMinNumSystemGeneratedRepliesWithNoConfig() {
+        assertTrue(mConstants.isEnabled());
+        assertEquals(2, mConstants.getMinNumSystemGeneratedReplies());
+    }
+
+    @Test
+    public void testGetMinNumSystemGeneratedRepliesWithValidConfig() {
+        overrideSetting("enabled=true,min_num_system_generated_replies=5");
+        triggerConstantsOnChange();
+        assertEquals(5, mConstants.getMinNumSystemGeneratedReplies());
+    }
+
     private void triggerConstantsOnChange() {
         // Since Settings.Global is mocked in TestableContext, we need to manually trigger the
         // content observer.
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/SmartReplyViewTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/SmartReplyViewTest.java
index 1066bc1..d1c4d01 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/SmartReplyViewTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/SmartReplyViewTest.java
@@ -96,6 +96,7 @@
     @Mock private SmartReplyController mLogger;
     private NotificationEntry mEntry;
     private Notification mNotification;
+    @Mock private SmartReplyConstants mConstants;
 
     @Mock ActivityStarter mActivityStarter;
     @Mock HeadsUpManager mHeadsUpManager;
@@ -108,10 +109,14 @@
         mDependency.get(KeyguardDismissUtil.class).setDismissHandler(action -> action.onDismiss());
         mDependency.injectMockDependency(ShadeController.class);
         mDependency.injectTestDependency(ActivityStarter.class, mActivityStarter);
+        mDependency.injectTestDependency(SmartReplyConstants.class, mConstants);
 
         mContainer = new View(mContext, null);
         mView = SmartReplyView.inflate(mContext, null);
 
+        // Any number of replies are fine.
+        when(mConstants.getMinNumSystemGeneratedReplies()).thenReturn(0);
+        when(mConstants.getMaxSqueezeRemeasureAttempts()).thenReturn(3);
 
         final Resources res = mContext.getResources();
         mSingleLinePaddingHorizontal = res.getDimensionPixelSize(
@@ -403,7 +408,7 @@
     }
 
     private void setSmartReplies(CharSequence[] choices) {
-        setSmartReplies(choices, false);
+        setSmartReplies(choices, false /* fromAssistant */);
     }
 
     private void setSmartReplies(CharSequence[] choices, boolean fromAssistant) {
@@ -440,9 +445,14 @@
     }
 
     private void setSmartRepliesAndActions(CharSequence[] choices, String[] actionTitles) {
-        setSmartReplies(choices);
+        setSmartRepliesAndActions(choices, actionTitles, false /* fromAssistant */);
+    }
+
+    private void setSmartRepliesAndActions(
+            CharSequence[] choices, String[] actionTitles, boolean fromAssistant) {
+        setSmartReplies(choices, fromAssistant);
         mView.addSmartActions(
-                new SmartReplyView.SmartActions(createActions(actionTitles), false),
+                new SmartReplyView.SmartActions(createActions(actionTitles), fromAssistant),
                 mLogger,
                 mEntry,
                 mHeadsUpManager);
@@ -943,4 +953,78 @@
                 expectedView.getChildAt(3), mView.getChildAt(4)); // a1
         assertReplyButtonHidden(mView.getChildAt(5)); // long action
     }
+
+    @Test
+    public void testMeasure_minNumSystemGeneratedSmartReplies_notEnoughReplies() {
+        when(mConstants.getMinNumSystemGeneratedReplies()).thenReturn(3);
+
+        // Add 2 replies when the minimum is 3 -> we should end up with 0 replies.
+        String[] choices = new String[] {"reply1", "reply2"};
+        String[] actions = new String[] {"action1"};
+
+        ViewGroup expectedView = buildExpectedView(new String[] {}, 1,
+                createActions(new String[] {"action1"}));
+        expectedView.measure(MeasureSpec.UNSPECIFIED, MeasureSpec.UNSPECIFIED);
+
+        setSmartRepliesAndActions(choices, actions, true /* fromAssistant */);
+        mView.measure(MeasureSpec.UNSPECIFIED, MeasureSpec.UNSPECIFIED);
+
+        assertEqualMeasures(expectedView, mView);
+        // smart replies
+        assertReplyButtonHidden(mView.getChildAt(0));
+        assertReplyButtonHidden(mView.getChildAt(1));
+        // smart actions
+        assertReplyButtonShownWithEqualMeasures(expectedView.getChildAt(0), mView.getChildAt(2));
+    }
+
+    @Test
+    public void testMeasure_minNumSystemGeneratedSmartReplies_enoughReplies() {
+        when(mConstants.getMinNumSystemGeneratedReplies()).thenReturn(2);
+
+        // Add 2 replies when the minimum is 3 -> we should end up with 0 replies.
+        String[] choices = new String[] {"reply1", "reply2"};
+        String[] actions = new String[] {"action1"};
+
+        ViewGroup expectedView = buildExpectedView(new String[] {"reply1", "reply2"}, 1,
+                createActions(new String[] {"action1"}));
+        expectedView.measure(MeasureSpec.UNSPECIFIED, MeasureSpec.UNSPECIFIED);
+
+        setSmartRepliesAndActions(choices, actions, true /* fromAssistant */);
+        mView.measure(MeasureSpec.UNSPECIFIED, MeasureSpec.UNSPECIFIED);
+
+        assertEqualMeasures(expectedView, mView);
+        // smart replies
+        assertReplyButtonShownWithEqualMeasures(expectedView.getChildAt(0), mView.getChildAt(0));
+        assertReplyButtonShownWithEqualMeasures(expectedView.getChildAt(1), mView.getChildAt(1));
+        // smart actions
+        assertReplyButtonShownWithEqualMeasures(expectedView.getChildAt(2), mView.getChildAt(2));
+    }
+
+    /**
+     * Ensure actions that are squeezed when shown together with smart replies are unsqueezed if the
+     * replies are never added (because of the SmartReplyConstants.getMinNumSystemGeneratedReplies()
+     * flag).
+     */
+    @Test
+    public void testMeasure_minNumSystemGeneratedSmartReplies_unSqueezeActions() {
+        when(mConstants.getMinNumSystemGeneratedReplies()).thenReturn(2);
+
+        // Add 2 replies when the minimum is 3 -> we should end up with 0 replies.
+        String[] choices = new String[] {"This is a very long two-line reply."};
+        String[] actions = new String[] {"Short action"};
+
+        // The action should be displayed on one line only - since it fits!
+        ViewGroup expectedView = buildExpectedView(new String[] {}, 1 /* lineCount */,
+                createActions(new String[] {"Short action"}));
+        expectedView.measure(MeasureSpec.UNSPECIFIED, MeasureSpec.UNSPECIFIED);
+
+        setSmartRepliesAndActions(choices, actions, true /* fromAssistant */);
+        mView.measure(MeasureSpec.UNSPECIFIED, MeasureSpec.UNSPECIFIED);
+
+        assertEqualMeasures(expectedView, mView);
+        // smart replies
+        assertReplyButtonHidden(mView.getChildAt(0));
+        // smart actions
+        assertReplyButtonShownWithEqualMeasures(expectedView.getChildAt(0), mView.getChildAt(1));
+    }
 }
diff --git a/proto/src/metrics_constants/metrics_constants.proto b/proto/src/metrics_constants/metrics_constants.proto
index 8261fe8..73fcb01 100644
--- a/proto/src/metrics_constants/metrics_constants.proto
+++ b/proto/src/metrics_constants/metrics_constants.proto
@@ -73,6 +73,10 @@
 
     // The view switched to summary mode (most relevant for notifications)
     TYPE_COLLAPSE = 14;
+
+    // The notification was adjusted by the assistant. Enum value is
+    // out of sequence due to b/122737498.
+    TYPE_NOTIFICATION_ASSISTANT_ADJUSTMENT = 1573;
   }
 
   // Types of alerts, as bit field values
@@ -232,6 +236,17 @@
     BLOCKING_HELPER_CLICK_UNDO = 7;
   }
 
+  // The (visual) location of a Notification.
+  enum NotificationLocation {
+    LOCATION_UNKNOWN = 0;
+    LOCATION_FIRST_HEADS_UP = 1; // visible heads-up
+    LOCATION_HIDDEN_TOP = 2; // hidden/scrolled away on the top
+    LOCATION_MAIN_AREA = 3; // visible in the shade
+    LOCATION_BOTTOM_STACK_PEEKING = 4; // in the bottom stack, and peeking
+    LOCATION_BOTTOM_STACK_HIDDEN = 5; // in the bottom stack, and hidden
+    LOCATION_GONE = 6; // the view isn't laid out at all
+  }
+
   // Known visual elements: views or controls.
   enum View {
     // Unknown view
@@ -4049,6 +4064,8 @@
     // - AUTOFILL_INVALID_DATASET_AUTHENTICATION
     // NOTE: starting on OS Q, it also added the following fields:
     // Tag FIELD_AUTOFILL_TEXT_LEN: length of the error message provided by the service
+    // Tag FIELD_AUTOFILL_NUMBER_AUGMENTED_REQUESTS: number of requests made to the augmented
+    //     autofill service
     AUTOFILL_REQUEST = 907;
 
     // Tag of a field for a package of an autofill service
@@ -6763,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;
 
@@ -6820,6 +6837,73 @@
     // OS: Q
     MOBILE_NETWORK_LIST = 1627;
 
+    // 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
+    // enum).
+    // OS: Q
+    NOTIFICATION_LOCATION = 1629;
+
+    // The autofill system made request to the system-provided augmented autofill service.
+    // OS: Q
+    // Package: Package of app that is autofilled
+    // Tag FIELD_CLASS_NAME: Class name of the activity that is autofilled.
+    // Tag FIELD_AUTOFILL_SERVICE: Package of the augmented autofill service that processed the
+    // request
+    // Tag FIELD_AUTOFILL_SESSION_ID: id of the autofill session associated with this metric.
+    // Tag FIELD_AUTOFILL_COMPAT_MODE: package is being autofilled on compatibility mode.
+    AUTOFILL_AUGMENTED_REQUEST = 1630;
+
+    // Tag of a field for the number of augmented autofill requests in a session
+    // 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 1212676..3a89316 100644
--- a/proto/src/system_messages.proto
+++ b/proto/src/system_messages.proto
@@ -216,9 +216,15 @@
     // Package: android
     NOTE_SOFTAP_CONFIG_CHANGED = 50;
 
-    // Notify the user that connected to app suggested network.
+    // Notify the user that an app suggested network is available for connection.
     // Package: android
-    NOTE_CONNECTED_TO_NETWORK_SUGGESTION = 51;
+    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
@@ -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 bcc43a7..c063e82 100644
--- a/proto/src/wifi.proto
+++ b/proto/src/wifi.proto
@@ -488,6 +488,12 @@
 
   // Counts the occurrences of each Wifi usability score provided by external app
   repeated WifiUsabilityScoreCount wifi_usability_score_count = 127;
+
+  // 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.
@@ -959,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
@@ -1676,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 {
@@ -1797,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 {
@@ -1816,4 +1845,165 @@
 
   // The list of timestamped wifi usability stats
   repeated WifiUsabilityStatsEntry stats = 2;
-}
\ No newline at end of file
+}
+
+message DeviceMobilityStatePnoScanStats {
+  // see WifiManager.DEVICE_MOBILITY_STATE_* constants
+  enum DeviceMobilityState {
+    // Unknown mobility
+    UNKNOWN = 0;
+
+    // High movement
+    HIGH_MVMT = 1;
+
+    // Low movement
+    LOW_MVMT = 2;
+
+    // Stationary
+    STATIONARY = 3;
+  }
+
+  // The device mobility state
+  optional DeviceMobilityState device_mobility_state = 1;
+
+  // The number of times that this state was entered
+  optional int32 num_times_entered_state = 2;
+
+  // The total duration elapsed while in this mobility state, in ms
+  optional int64 total_duration_ms = 3;
+
+  // 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/AbstractAccessibilityServiceConnection.java b/services/accessibility/java/com/android/server/accessibility/AbstractAccessibilityServiceConnection.java
index 6eba914..2c075dc 100644
--- a/services/accessibility/java/com/android/server/accessibility/AbstractAccessibilityServiceConnection.java
+++ b/services/accessibility/java/com/android/server/accessibility/AbstractAccessibilityServiceConnection.java
@@ -53,6 +53,7 @@
 import android.view.accessibility.IAccessibilityInteractionConnection;
 import android.view.accessibility.IAccessibilityInteractionConnectionCallback;
 
+import com.android.internal.annotations.GuardedBy;
 import com.android.internal.os.SomeArgs;
 import com.android.internal.util.DumpUtils;
 import com.android.server.accessibility.AccessibilityManagerService.RemoteAccessibilityConnection;
@@ -751,7 +752,7 @@
     }
 
     @Override
-    public float getMagnificationScale() {
+    public float getMagnificationScale(int displayId) {
         synchronized (mLock) {
             if (!isCalledForCurrentUserLocked()) {
                 return 1.0f;
@@ -759,14 +760,14 @@
         }
         final long identity = Binder.clearCallingIdentity();
         try {
-            return mSystemSupport.getMagnificationController().getScale();
+            return mSystemSupport.getMagnificationController().getScale(displayId);
         } finally {
             Binder.restoreCallingIdentity(identity);
         }
     }
 
     @Override
-    public Region getMagnificationRegion() {
+    public Region getMagnificationRegion(int displayId) {
         synchronized (mLock) {
             final Region region = Region.obtain();
             if (!isCalledForCurrentUserLocked()) {
@@ -775,22 +776,22 @@
             MagnificationController magnificationController =
                     mSystemSupport.getMagnificationController();
             boolean registeredJustForThisCall =
-                    registerMagnificationIfNeeded(magnificationController);
+                    registerMagnificationIfNeeded(displayId, magnificationController);
             final long identity = Binder.clearCallingIdentity();
             try {
-                magnificationController.getMagnificationRegion(region);
+                magnificationController.getMagnificationRegion(displayId, region);
                 return region;
             } finally {
                 Binder.restoreCallingIdentity(identity);
                 if (registeredJustForThisCall) {
-                    magnificationController.unregister();
+                    magnificationController.unregister(displayId);
                 }
             }
         }
     }
 
     @Override
-    public float getMagnificationCenterX() {
+    public float getMagnificationCenterX(int displayId) {
         synchronized (mLock) {
             if (!isCalledForCurrentUserLocked()) {
                 return 0.0f;
@@ -798,21 +799,21 @@
             MagnificationController magnificationController =
                     mSystemSupport.getMagnificationController();
             boolean registeredJustForThisCall =
-                    registerMagnificationIfNeeded(magnificationController);
+                    registerMagnificationIfNeeded(displayId, magnificationController);
             final long identity = Binder.clearCallingIdentity();
             try {
-                return magnificationController.getCenterX();
+                return magnificationController.getCenterX(displayId);
             } finally {
                 Binder.restoreCallingIdentity(identity);
                 if (registeredJustForThisCall) {
-                    magnificationController.unregister();
+                    magnificationController.unregister(displayId);
                 }
             }
         }
     }
 
     @Override
-    public float getMagnificationCenterY() {
+    public float getMagnificationCenterY(int displayId) {
         synchronized (mLock) {
             if (!isCalledForCurrentUserLocked()) {
                 return 0.0f;
@@ -820,31 +821,31 @@
             MagnificationController magnificationController =
                     mSystemSupport.getMagnificationController();
             boolean registeredJustForThisCall =
-                    registerMagnificationIfNeeded(magnificationController);
+                    registerMagnificationIfNeeded(displayId, magnificationController);
             final long identity = Binder.clearCallingIdentity();
             try {
-                return magnificationController.getCenterY();
+                return magnificationController.getCenterY(displayId);
             } finally {
                 Binder.restoreCallingIdentity(identity);
                 if (registeredJustForThisCall) {
-                    magnificationController.unregister();
+                    magnificationController.unregister(displayId);
                 }
             }
         }
     }
 
-    private boolean registerMagnificationIfNeeded(
+    private boolean registerMagnificationIfNeeded(int displayId,
             MagnificationController magnificationController) {
-        if (!magnificationController.isRegisteredLocked()
+        if (!magnificationController.isRegistered(displayId)
                 && mSecurityPolicy.canControlMagnification(this)) {
-            magnificationController.register();
+            magnificationController.register(displayId);
             return true;
         }
         return false;
     }
 
     @Override
-    public boolean resetMagnification(boolean animate) {
+    public boolean resetMagnification(int displayId, boolean animate) {
         synchronized (mLock) {
             if (!isCalledForCurrentUserLocked()) {
                 return false;
@@ -857,16 +858,16 @@
         try {
             MagnificationController magnificationController =
                     mSystemSupport.getMagnificationController();
-            return (magnificationController.reset(animate)
-                    || !magnificationController.isMagnifying());
+            return (magnificationController.reset(displayId, animate)
+                    || !magnificationController.isMagnifying(displayId));
         } finally {
             Binder.restoreCallingIdentity(identity);
         }
     }
 
     @Override
-    public boolean setMagnificationScaleAndCenter(float scale, float centerX, float centerY,
-            boolean animate) {
+    public boolean setMagnificationScaleAndCenter(int displayId, float scale, float centerX,
+            float centerY, boolean animate) {
         synchronized (mLock) {
             if (!isCalledForCurrentUserLocked()) {
                 return false;
@@ -878,11 +879,11 @@
             try {
                 MagnificationController magnificationController =
                         mSystemSupport.getMagnificationController();
-                if (!magnificationController.isRegisteredLocked()) {
-                    magnificationController.register();
+                if (!magnificationController.isRegistered(displayId)) {
+                    magnificationController.register(displayId);
                 }
                 return magnificationController
-                        .setScaleAndCenter(scale, centerX, centerY, animate, mId);
+                        .setScaleAndCenter(displayId, scale, centerX, centerY, animate, mId);
             } finally {
                 Binder.restoreCallingIdentity(identity);
             }
@@ -890,12 +891,12 @@
     }
 
     @Override
-    public void setMagnificationCallbackEnabled(boolean enabled) {
-        mInvocationHandler.setMagnificationCallbackEnabled(enabled);
+    public void setMagnificationCallbackEnabled(int displayId, boolean enabled) {
+        mInvocationHandler.setMagnificationCallbackEnabled(displayId, enabled);
     }
 
-    public boolean isMagnificationCallbackEnabled() {
-        return mInvocationHandler.mIsMagnificationCallbackEnabled;
+    public boolean isMagnificationCallbackEnabled(int displayId) {
+        return mInvocationHandler.isMagnificationCallbackEnabled(displayId);
     }
 
     @Override
@@ -1106,10 +1107,10 @@
                 InvocationHandler.MSG_CLEAR_ACCESSIBILITY_CACHE);
     }
 
-    public void notifyMagnificationChangedLocked(@NonNull Region region,
+    public void notifyMagnificationChangedLocked(int displayId, @NonNull Region region,
             float scale, float centerX, float centerY) {
         mInvocationHandler
-                .notifyMagnificationChangedLocked(region, scale, centerX, centerY);
+                .notifyMagnificationChangedLocked(displayId, region, scale, centerX, centerY);
     }
 
     public void notifySoftKeyboardShowModeChangedLocked(int showState) {
@@ -1128,12 +1129,12 @@
      * Called by the invocation handler to notify the service that the
      * state of magnification has changed.
      */
-    private void notifyMagnificationChangedInternal(@NonNull Region region,
+    private void notifyMagnificationChangedInternal(int displayId, @NonNull Region region,
             float scale, float centerX, float centerY) {
         final IAccessibilityServiceClient listener = getServiceInterfaceSafely();
         if (listener != null) {
             try {
-                listener.onMagnificationChanged(region, scale, centerX, centerY);
+                listener.onMagnificationChanged(displayId, region, scale, centerX, centerY);
             } catch (RemoteException re) {
                 Slog.e(LOG_TAG, "Error sending magnification changes to " + mService, re);
             }
@@ -1251,7 +1252,9 @@
         private static final int MSG_ON_ACCESSIBILITY_BUTTON_CLICKED = 7;
         private static final int MSG_ON_ACCESSIBILITY_BUTTON_AVAILABILITY_CHANGED = 8;
 
-        private boolean mIsMagnificationCallbackEnabled = false;
+        /** List of magnification callback states, mapping from displayId -> Boolean */
+        @GuardedBy("mlock")
+        private final SparseArray<Boolean> mMagnificationCallbackState = new SparseArray<>(0);
         private boolean mIsSoftKeyboardCallbackEnabled = false;
 
         public InvocationHandler(Looper looper) {
@@ -1277,7 +1280,8 @@
                     final float scale = (float) args.arg2;
                     final float centerX = (float) args.arg3;
                     final float centerY = (float) args.arg4;
-                    notifyMagnificationChangedInternal(region, scale, centerX, centerY);
+                    final int displayId = args.argi1;
+                    notifyMagnificationChangedInternal(displayId, region, scale, centerX, centerY);
                     args.recycle();
                 } break;
 
@@ -1301,11 +1305,12 @@
             }
         }
 
-        public void notifyMagnificationChangedLocked(@NonNull Region region, float scale,
-                float centerX, float centerY) {
-            if (!mIsMagnificationCallbackEnabled) {
-                // Callback is disabled, don't bother packing args.
-                return;
+        public void notifyMagnificationChangedLocked(int displayId, @NonNull Region region,
+                float scale, float centerX, float centerY) {
+            synchronized (mLock) {
+                if (mMagnificationCallbackState.get(displayId) == null) {
+                    return;
+                }
             }
 
             final SomeArgs args = SomeArgs.obtain();
@@ -1313,13 +1318,26 @@
             args.arg2 = scale;
             args.arg3 = centerX;
             args.arg4 = centerY;
+            args.argi1 = displayId;
 
             final Message msg = obtainMessage(MSG_ON_MAGNIFICATION_CHANGED, args);
             msg.sendToTarget();
         }
 
-        public void setMagnificationCallbackEnabled(boolean enabled) {
-            mIsMagnificationCallbackEnabled = enabled;
+        public void setMagnificationCallbackEnabled(int displayId, boolean enabled) {
+            synchronized (mLock) {
+                if (enabled) {
+                    mMagnificationCallbackState.put(displayId, true);
+                } else {
+                    mMagnificationCallbackState.remove(displayId);
+                }
+            }
+        }
+
+        public boolean isMagnificationCallbackEnabled(int displayId) {
+            synchronized (mLock) {
+                return mMagnificationCallbackState.get(displayId) != null;
+            }
         }
 
         public void notifySoftKeyboardShowModeChangedLocked(int showState) {
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 763c16f..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;
@@ -212,6 +213,8 @@
 
     private final SecurityPolicy mSecurityPolicy;
 
+    private final AccessibilityDisplayListener mA11yDisplayListener;
+
     private final AppOpsManager mAppOpsManager;
 
     private final MainHandler mMainHandler;
@@ -304,6 +307,7 @@
         mAppOpsManager = (AppOpsManager) context.getSystemService(Context.APP_OPS_SERVICE);
         mMainHandler = new MainHandler(mContext.getMainLooper());
         mGlobalActionPerformer = new GlobalActionPerformer(mContext, mWindowManagerService);
+        mA11yDisplayListener = new AccessibilityDisplayListener(mContext, mMainHandler);
 
         registerBroadcastReceivers();
         new AccessibilityContentObserver(mMainHandler).register(
@@ -909,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);
         }
     }
 
@@ -968,17 +974,18 @@
      * Called by the MagnificationController when the state of display
      * magnification changes.
      *
+     * @param displayId The logical display id.
      * @param region the new magnified region, may be empty if
      *               magnification is not enabled (e.g. scale is 1)
      * @param scale the new scale
      * @param centerX the new screen-relative center X coordinate
      * @param centerY the new screen-relative center Y coordinate
      */
-    public void notifyMagnificationChanged(@NonNull Region region,
+    public void notifyMagnificationChanged(int displayId, @NonNull Region region,
             float scale, float centerX, float centerY) {
         synchronized (mLock) {
             notifyClearAccessibilityCacheLocked();
-            notifyMagnificationChangedLocked(region, scale, centerX, centerY);
+            notifyMagnificationChangedLocked(displayId, region, scale, centerX, centerY);
         }
     }
 
@@ -1203,12 +1210,12 @@
         }
     }
 
-    private void notifyMagnificationChangedLocked(@NonNull Region region,
+    private void notifyMagnificationChangedLocked(int displayId, @NonNull Region region,
             float scale, float centerX, float centerY) {
         final UserState state = getCurrentUserStateLocked();
         for (int i = state.mBoundServices.size() - 1; i >= 0; i--) {
             final AccessibilityServiceConnection service = state.mBoundServices.get(i);
-            service.notifyMagnificationChangedLocked(region, scale, centerX, centerY);
+            service.notifyMagnificationChangedLocked(displayId, region, scale, centerX, centerY);
         }
     }
 
@@ -1220,7 +1227,7 @@
         }
     }
 
-    private void notifyAccessibilityButtonClickedLocked() {
+    private void notifyAccessibilityButtonClickedLocked(int displayId) {
         final UserState state = getCurrentUserStateLocked();
 
         int potentialTargets = state.mIsNavBarMagnificationEnabled ? 1 : 0;
@@ -1237,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;
                     }
@@ -1252,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;
                     }
@@ -1270,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) {
@@ -2191,15 +2207,34 @@
             return;
         }
 
-        if (!mUiAutomationManager.suppressingAccessibilityServicesLocked()
-                && (userState.mIsDisplayMagnificationEnabled
-                        || userState.mIsNavBarMagnificationEnabled
-                        || userHasListeningMagnificationServicesLocked(userState))) {
-            // Initialize the magnification controller if necessary
-            getMagnificationController();
-            mMagnificationController.register();
-        } else if (mMagnificationController != null) {
-            mMagnificationController.unregister();
+        if (mUiAutomationManager.suppressingAccessibilityServicesLocked()
+                && mMagnificationController != null) {
+            mMagnificationController.unregisterAll();
+            return;
+        }
+
+        // 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.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.size(); i++) {
+            final Display display = displays.get(i);
+            final int displayId = display.getDisplayId();
+            if (userHasListeningMagnificationServicesLocked(userState, displayId)) {
+                getMagnificationController().register(displayId);
+            } else if (mMagnificationController != null) {
+                mMagnificationController.unregister(displayId);
+            }
         }
     }
 
@@ -2222,12 +2257,13 @@
      * Returns whether the specified user has any services that are capable of
      * controlling magnification and are actively listening for magnification updates.
      */
-    private boolean userHasListeningMagnificationServicesLocked(UserState userState) {
+    private boolean userHasListeningMagnificationServicesLocked(UserState userState,
+            int displayId) {
         final List<AccessibilityServiceConnection> services = userState.mBoundServices;
         for (int i = 0, count = services.size(); i < count; i++) {
             final AccessibilityServiceConnection service = services.get(i);
             if (mSecurityPolicy.canControlMagnification(service)
-                    && service.isMagnificationCallbackEnabled()) {
+                    && service.isMagnificationCallbackEnabled(displayId)) {
                 return true;
             }
         }
@@ -3753,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/AccessibilityServiceConnection.java b/services/accessibility/java/com/android/server/accessibility/AccessibilityServiceConnection.java
index 86132a8..a19a847 100644
--- a/services/accessibility/java/com/android/server/accessibility/AccessibilityServiceConnection.java
+++ b/services/accessibility/java/com/android/server/accessibility/AccessibilityServiceConnection.java
@@ -109,7 +109,7 @@
         UserState userState = mUserStateWeakReference.get();
         if (userState == null) return;
         userState.removeServiceLocked(this);
-        mSystemSupport.getMagnificationController().resetIfNeeded(mId);
+        mSystemSupport.getMagnificationController().resetAllIfNeeded(mId);
         resetLocked();
     }
 
@@ -256,7 +256,7 @@
                 userState.serviceDisconnectedLocked(this);
             }
             resetLocked();
-            mSystemSupport.getMagnificationController().resetIfNeeded(mId);
+            mSystemSupport.getMagnificationController().resetAllIfNeeded(mId);
             mSystemSupport.onClientChangeLocked(false);
         }
     }
diff --git a/services/accessibility/java/com/android/server/accessibility/MagnificationController.java b/services/accessibility/java/com/android/server/accessibility/MagnificationController.java
index 6a97fbb..e784056 100644
--- a/services/accessibility/java/com/android/server/accessibility/MagnificationController.java
+++ b/services/accessibility/java/com/android/server/accessibility/MagnificationController.java
@@ -19,7 +19,6 @@
 import android.animation.ValueAnimator;
 import android.annotation.NonNull;
 import android.content.BroadcastReceiver;
-import android.content.ContentResolver;
 import android.content.Context;
 import android.content.Intent;
 import android.content.IntentFilter;
@@ -32,6 +31,7 @@
 import android.text.TextUtils;
 import android.util.MathUtils;
 import android.util.Slog;
+import android.util.SparseArray;
 import android.view.Display;
 import android.view.MagnificationSpec;
 import android.view.View;
@@ -39,6 +39,7 @@
 
 import com.android.internal.R;
 import com.android.internal.annotations.GuardedBy;
+import com.android.internal.annotations.VisibleForTesting;
 import com.android.internal.util.function.pooled.PooledLambda;
 import com.android.server.LocalServices;
 import com.android.server.wm.WindowManagerInternal;
@@ -68,9 +69,7 @@
 
     private final Object mLock;
 
-    private final AccessibilityManagerService mAms;
-
-    private final SettingsBridge mSettingsBridge;
+    private final ControllerContext mControllerCtx;
 
     private final ScreenStateObserver mScreenStateObserver;
 
@@ -78,11 +77,9 @@
 
     private final long mMainThreadId;
 
-    private Handler mHandler;
-
-    private final WindowManagerInternal mWindowManager;
-
-    private final DisplayMagnification mDisplay;
+    /** List of display Magnification, mapping from displayId -> DisplayMagnification. */
+    @GuardedBy("mLock")
+    private final SparseArray<DisplayMagnification> mDisplays = new SparseArray<>(0);
 
     /**
      * This class implements {@link WindowManagerInternal.MagnificationCallbacks} and holds
@@ -107,46 +104,82 @@
         // Flag indicating that we are registered with window manager.
         private boolean mRegistered;
         private boolean mUnregisterPending;
+        private boolean mDeleteAfterUnregister;
 
         private final int mDisplayId;
 
         private static final int INVALID_ID = -1;
         private int mIdOfLastServiceToMagnify = INVALID_ID;
 
-
-        DisplayMagnification(int displayId, SpecAnimationBridge specAnimation) {
+        DisplayMagnification(int displayId) {
             mDisplayId = displayId;
-            mSpecAnimationBridge = specAnimation;
+            mSpecAnimationBridge = new SpecAnimationBridge(mControllerCtx, mLock, mDisplayId);
         }
 
-        void register() {
-            synchronized (mLock) {
-                if (!mRegistered) {
-                    mWindowManager.setMagnificationCallbacks(this);
-                    mSpecAnimationBridge.setEnabled(true);
-                    // Obtain initial state.
-                    mWindowManager.getMagnificationRegion(mMagnificationRegion);
-                    mMagnificationRegion.getBounds(mMagnificationBounds);
-                    mRegistered = true;
-                }
+        /**
+         * Registers magnification callback and get current magnification region from
+         * window manager.
+         *
+         * @return true if callback registers successful.
+         */
+        @GuardedBy("mLock")
+        boolean register() {
+            mRegistered = mControllerCtx.getWindowManager().setMagnificationCallbacks(
+                    mDisplayId, this);
+            if (!mRegistered) {
+                Slog.w(LOG_TAG, "set magnification callbacks fail, displayId:" + mDisplayId);
+                return false;
             }
+            mSpecAnimationBridge.setEnabled(true);
+            // Obtain initial state.
+            mControllerCtx.getWindowManager().getMagnificationRegion(
+                    mDisplayId, mMagnificationRegion);
+            mMagnificationRegion.getBounds(mMagnificationBounds);
+            return true;
         }
 
-        void unregister() {
-            synchronized (mLock) {
-                if (!isMagnifying()) {
-                    unregisterInternalLocked();
-                } else {
-                    mUnregisterPending = true;
-                    reset(true);
-                }
+        /**
+         * Unregisters magnification callback from window manager. Callbacks to
+         * {@link MagnificationController#unregisterCallbackLocked(int, boolean)} after
+         * unregistered.
+         *
+         * @param delete true if this instance should be removed from the SparseArray in
+         *               MagnificationController after unregistered, for example, display removed.
+         */
+        @GuardedBy("mLock")
+        void unregister(boolean delete) {
+            if (mRegistered) {
+                mSpecAnimationBridge.setEnabled(false);
+                mControllerCtx.getWindowManager().setMagnificationCallbacks(
+                        mDisplayId, null);
+                mMagnificationRegion.setEmpty();
+                mRegistered = false;
+                unregisterCallbackLocked(mDisplayId, delete);
             }
+            mUnregisterPending = false;
         }
 
-        boolean isRegisteredLocked() {
+        /**
+         * Reset magnification status with animation enabled. {@link #unregister(boolean)} will be
+         * called after animation finished.
+         *
+         * @param delete true if this instance should be removed from the SparseArray in
+         *               MagnificationController after unregistered, for example, display removed.
+         */
+        @GuardedBy("mLock")
+        void unregisterPending(boolean delete) {
+            mDeleteAfterUnregister = delete;
+            mUnregisterPending = true;
+            reset(true);
+        }
+
+        boolean isRegistered() {
             return mRegistered;
         }
 
+        boolean isMagnifying() {
+            return mCurrentMagnificationSpec.scale > 1.0f;
+        }
 
         float getScale() {
             return mCurrentMagnificationSpec.scale;
@@ -156,18 +189,20 @@
             return mCurrentMagnificationSpec.offsetX;
         }
 
-        float getCenterX() {
-            synchronized (mLock) {
-                return (mMagnificationBounds.width() / 2.0f
-                        + mMagnificationBounds.left - getOffsetX()) / getScale();
-            }
+        float getOffsetY() {
+            return mCurrentMagnificationSpec.offsetY;
         }
 
+        @GuardedBy("mLock")
+        float getCenterX() {
+            return (mMagnificationBounds.width() / 2.0f
+                    + mMagnificationBounds.left - getOffsetX()) / getScale();
+        }
+
+        @GuardedBy("mLock")
         float getCenterY() {
-            synchronized (mLock) {
-                return (mMagnificationBounds.height() / 2.0f
-                        + mMagnificationBounds.top - getOffsetY()) / getScale();
-            }
+            return (mMagnificationBounds.height() / 2.0f
+                    + mMagnificationBounds.top - getOffsetY()) / getScale();
         }
 
         /**
@@ -203,64 +238,35 @@
             return mSpecAnimationBridge.mSentMagnificationSpec.offsetY;
         }
 
-        boolean resetIfNeeded(boolean animate) {
-            synchronized (mLock) {
-                if (isMagnifying()) {
-                    reset(animate);
-                    return true;
-                }
-                return false;
-            }
-        }
-
-        float getOffsetY() {
-            return mCurrentMagnificationSpec.offsetY;
-        }
-
-        boolean isMagnifying() {
-            return mCurrentMagnificationSpec.scale > 1.0f;
-        }
-
-        void unregisterInternalLocked() {
-            if (mRegistered) {
-                mSpecAnimationBridge.setEnabled(false);
-                mWindowManager.setMagnificationCallbacks(null);
-                mMagnificationRegion.setEmpty();
-
-                mRegistered = false;
-            }
-            mUnregisterPending = false;
-        }
-
-
         @Override
         public void onMagnificationRegionChanged(Region magnificationRegion) {
             final Message m = PooledLambda.obtainMessage(
-                    DisplayMagnification.this::updateMagnificationRegion,
+                    DisplayMagnification::updateMagnificationRegion, this,
                     Region.obtain(magnificationRegion));
-            mHandler.sendMessage(m);
+            mControllerCtx.getHandler().sendMessage(m);
         }
 
         @Override
         public void onRectangleOnScreenRequested(int left, int top, int right, int bottom) {
             final Message m = PooledLambda.obtainMessage(
-                    DisplayMagnification.this::requestRectangleOnScreen, left, top, right, bottom);
-            mHandler.sendMessage(m);
+                    DisplayMagnification::requestRectangleOnScreen, this,
+                    left, top, right, bottom);
+            mControllerCtx.getHandler().sendMessage(m);
         }
 
         @Override
         public void onRotationChanged(int rotation) {
             // Treat as context change and reset
-            final Message m = PooledLambda.obtainMessage(DisplayMagnification.this::resetIfNeeded,
-                    true);
-            mHandler.sendMessage(m);
+            final Message m = PooledLambda.obtainMessage(MagnificationController::resetIfNeeded,
+                    MagnificationController.this, mDisplayId, true);
+            mControllerCtx.getHandler().sendMessage(m);
         }
 
         @Override
         public void onUserContextChanged() {
-            final Message m = PooledLambda.obtainMessage(DisplayMagnification.this::resetIfNeeded,
-                    true);
-            mHandler.sendMessage(m);
+            final Message m = PooledLambda.obtainMessage(MagnificationController::resetIfNeeded,
+                    MagnificationController.this, mDisplayId, true);
+            mControllerCtx.getHandler().sendMessage(m);
         }
 
         /**
@@ -298,8 +304,9 @@
                 mSpecAnimationBridge.updateSentSpecMainThread(spec, animate);
             } else {
                 final Message m = PooledLambda.obtainMessage(
-                        this.mSpecAnimationBridge::updateSentSpecMainThread, spec, animate);
-                mHandler.sendMessage(m);
+                        SpecAnimationBridge::updateSentSpecMainThread,
+                        mSpecAnimationBridge, spec, animate);
+                mControllerCtx.getHandler().sendMessage(m);
             }
         }
 
@@ -313,30 +320,26 @@
         }
 
         void onMagnificationChangedLocked() {
-            mAms.notifyMagnificationChanged(mMagnificationRegion,
+            mControllerCtx.getAms().notifyMagnificationChanged(mDisplayId, mMagnificationRegion,
                     getScale(), getCenterX(), getCenterY());
             if (mUnregisterPending && !isMagnifying()) {
-                unregisterInternalLocked();
+                unregister(mDeleteAfterUnregister);
             }
         }
 
+        @GuardedBy("mLock")
         boolean magnificationRegionContains(float x, float y) {
-            synchronized (mLock) {
-                return mMagnificationRegion.contains((int) x, (int) y);
-
-            }
+            return mMagnificationRegion.contains((int) x, (int) y);
         }
 
+        @GuardedBy("mLock")
         void getMagnificationBounds(@NonNull Rect outBounds) {
-            synchronized (mLock) {
-                outBounds.set(mMagnificationBounds);
-            }
+            outBounds.set(mMagnificationBounds);
         }
 
+        @GuardedBy("mLock")
         void getMagnificationRegion(@NonNull Region outRegion) {
-            synchronized (mLock) {
-                outRegion.set(mMagnificationRegion);
-            }
+            outRegion.set(mMagnificationRegion);
         }
 
         void requestRectangleOnScreen(int left, int top, int right, int bottom) {
@@ -392,94 +395,76 @@
             outFrame.scale(1.0f / scale);
         }
 
-        /**
-         * Resets magnification if last magnifying service is disabled.
-         *
-         * @param connectionId the connection ID be disabled.
-         * @return {@code true} on success, {@code false} on failure
-         */
-        boolean resetIfNeeded(int connectionId) {
-            if (mIdOfLastServiceToMagnify == connectionId) {
-                return resetIfNeeded(true /*animate*/);
-            }
-            return false;
-        }
-
+        @GuardedBy("mLock")
         void setForceShowMagnifiableBounds(boolean show) {
             if (mRegistered) {
-                mWindowManager.setForceShowMagnifiableBounds(show);
+                mControllerCtx.getWindowManager().setForceShowMagnifiableBounds(
+                        mDisplayId, show);
             }
         }
 
+        @GuardedBy("mLock")
         boolean reset(boolean animate) {
-            synchronized (mLock) {
-                if (!mRegistered) {
-                    return false;
-                }
-                final MagnificationSpec spec = mCurrentMagnificationSpec;
-                final boolean changed = !spec.isNop();
-                if (changed) {
-                    spec.clear();
-                    onMagnificationChangedLocked();
-                }
-                mIdOfLastServiceToMagnify = INVALID_ID;
-                sendSpecToAnimation(spec, animate);
-                return changed;
+            if (!mRegistered) {
+                return false;
             }
+            final MagnificationSpec spec = mCurrentMagnificationSpec;
+            final boolean changed = !spec.isNop();
+            if (changed) {
+                spec.clear();
+                onMagnificationChangedLocked();
+            }
+            mIdOfLastServiceToMagnify = INVALID_ID;
+            sendSpecToAnimation(spec, animate);
+            return changed;
         }
 
-
+        @GuardedBy("mLock")
         boolean setScale(float scale, float pivotX, float pivotY,
                 boolean animate, int id) {
-
-            synchronized (mLock) {
-                if (!mRegistered) {
-                    return false;
-                }
-                // Constrain scale immediately for use in the pivot calculations.
-                scale = MathUtils.constrain(scale, MIN_SCALE, MAX_SCALE);
-
-                final Rect viewport = mTempRect;
-                mMagnificationRegion.getBounds(viewport);
-                final MagnificationSpec spec = mCurrentMagnificationSpec;
-                final float oldScale = spec.scale;
-                final float oldCenterX
-                        = (viewport.width() / 2.0f - spec.offsetX + viewport.left) / oldScale;
-                final float oldCenterY
-                        = (viewport.height() / 2.0f - spec.offsetY + viewport.top) / oldScale;
-                final float normPivotX = (pivotX - spec.offsetX) / oldScale;
-                final float normPivotY = (pivotY - spec.offsetY) / oldScale;
-                final float offsetX = (oldCenterX - normPivotX) * (oldScale / scale);
-                final float offsetY = (oldCenterY - normPivotY) * (oldScale / scale);
-                final float centerX = normPivotX + offsetX;
-                final float centerY = normPivotY + offsetY;
-                mIdOfLastServiceToMagnify = id;
-
-                return setScaleAndCenter(scale, centerX, centerY, animate, id);
+            if (!mRegistered) {
+                return false;
             }
+            // Constrain scale immediately for use in the pivot calculations.
+            scale = MathUtils.constrain(scale, MIN_SCALE, MAX_SCALE);
+
+            final Rect viewport = mTempRect;
+            mMagnificationRegion.getBounds(viewport);
+            final MagnificationSpec spec = mCurrentMagnificationSpec;
+            final float oldScale = spec.scale;
+            final float oldCenterX =
+                    (viewport.width() / 2.0f - spec.offsetX + viewport.left) / oldScale;
+            final float oldCenterY =
+                    (viewport.height() / 2.0f - spec.offsetY + viewport.top) / oldScale;
+            final float normPivotX = (pivotX - spec.offsetX) / oldScale;
+            final float normPivotY = (pivotY - spec.offsetY) / oldScale;
+            final float offsetX = (oldCenterX - normPivotX) * (oldScale / scale);
+            final float offsetY = (oldCenterY - normPivotY) * (oldScale / scale);
+            final float centerX = normPivotX + offsetX;
+            final float centerY = normPivotY + offsetY;
+            mIdOfLastServiceToMagnify = id;
+            return setScaleAndCenter(scale, centerX, centerY, animate, id);
         }
 
+        @GuardedBy("mLock")
         boolean setScaleAndCenter(float scale, float centerX, float centerY,
                 boolean animate, int id) {
-
-            synchronized (mLock) {
-                if (!mRegistered) {
-                    return false;
-                }
-                if (DEBUG) {
-                    Slog.i(LOG_TAG,
-                            "setScaleAndCenterLocked(scale = " + scale + ", centerX = " + centerX
-                                    + ", centerY = " + centerY + ", animate = " + animate
-                                    + ", id = " + id
-                                    + ")");
-                }
-                final boolean changed = updateMagnificationSpecLocked(scale, centerX, centerY);
-                sendSpecToAnimation(mCurrentMagnificationSpec, animate);
-                if (isMagnifying() && (id != INVALID_ID)) {
-                    mIdOfLastServiceToMagnify = id;
-                }
-                return changed;
+            if (!mRegistered) {
+                return false;
             }
+            if (DEBUG) {
+                Slog.i(LOG_TAG,
+                        "setScaleAndCenterLocked(scale = " + scale + ", centerX = " + centerX
+                                + ", centerY = " + centerY + ", animate = " + animate
+                                + ", id = " + id
+                                + ")");
+            }
+            final boolean changed = updateMagnificationSpecLocked(scale, centerX, centerY);
+            sendSpecToAnimation(mCurrentMagnificationSpec, animate);
+            if (isMagnifying() && (id != INVALID_ID)) {
+                mIdOfLastServiceToMagnify = id;
+            }
+            return changed;
         }
 
         /**
@@ -527,22 +512,21 @@
             return changed;
         }
 
+        @GuardedBy("mLock")
         void offsetMagnifiedRegion(float offsetX, float offsetY, int id) {
-            synchronized (mLock) {
-                if (!mRegistered) {
-                    return;
-                }
-
-                final float nonNormOffsetX = mCurrentMagnificationSpec.offsetX - offsetX;
-                final float nonNormOffsetY = mCurrentMagnificationSpec.offsetY - offsetY;
-                if (updateCurrentSpecWithOffsetsLocked(nonNormOffsetX, nonNormOffsetY)) {
-                    onMagnificationChangedLocked();
-                }
-                if (id != INVALID_ID) {
-                    mIdOfLastServiceToMagnify = id;
-                }
-                sendSpecToAnimation(mCurrentMagnificationSpec, false);
+            if (!mRegistered) {
+                return;
             }
+
+            final float nonNormOffsetX = mCurrentMagnificationSpec.offsetX - offsetX;
+            final float nonNormOffsetY = mCurrentMagnificationSpec.offsetY - offsetY;
+            if (updateCurrentSpecWithOffsetsLocked(nonNormOffsetX, nonNormOffsetY)) {
+                onMagnificationChangedLocked();
+            }
+            if (id != INVALID_ID) {
+                mIdOfLastServiceToMagnify = id;
+            }
+            sendSpecToAnimation(mCurrentMagnificationSpec, false);
         }
 
         boolean updateCurrentSpecWithOffsetsLocked(float nonNormOffsetX, float nonNormOffsetY) {
@@ -593,44 +577,38 @@
 
         @Override
         public String toString() {
-            return "DisplayMagnification{" +
-                    "mCurrentMagnificationSpec=" + mCurrentMagnificationSpec +
-                    ", mMagnificationRegion=" + mMagnificationRegion +
-                    ", mMagnificationBounds=" + mMagnificationBounds +
-                    ", mDisplayId=" + mDisplayId +
-                    ", mUserId=" + mUserId +
-                    ", mIdOfLastServiceToMagnify=" + mIdOfLastServiceToMagnify +
-                    ", mRegistered=" + mRegistered +
-                    ", mUnregisterPending=" + mUnregisterPending +
-                    '}';
+            return "DisplayMagnification["
+                    + "mCurrentMagnificationSpec=" + mCurrentMagnificationSpec
+                    + ", mMagnificationRegion=" + mMagnificationRegion
+                    + ", mMagnificationBounds=" + mMagnificationBounds
+                    + ", mDisplayId=" + mDisplayId
+                    + ", mIdOfLastServiceToMagnify=" + mIdOfLastServiceToMagnify
+                    + ", mRegistered=" + mRegistered
+                    + ", mUnregisterPending=" + mUnregisterPending
+                    + ']';
         }
-
     }
 
-    public MagnificationController(Context context, AccessibilityManagerService ams, Object lock) {
-        this(context, ams, lock, null, LocalServices.getService(WindowManagerInternal.class),
-                new ValueAnimator(), new SettingsBridge(context.getContentResolver()));
-        mHandler = new Handler(context.getMainLooper());
+    /**
+     * MagnificationController Constructor
+     */
+    public MagnificationController(@NonNull Context context,
+            @NonNull AccessibilityManagerService ams, @NonNull Object lock) {
+        this(new ControllerContext(context, ams,
+                LocalServices.getService(WindowManagerInternal.class),
+                new Handler(context.getMainLooper()),
+                context.getResources().getInteger(R.integer.config_longAnimTime)), lock);
     }
 
-    public MagnificationController(
-            Context context,
-            AccessibilityManagerService ams,
-            Object lock,
-            Handler handler,
-            WindowManagerInternal windowManagerInternal,
-            ValueAnimator valueAnimator,
-            SettingsBridge settingsBridge) {
-        mHandler = handler;
-        mWindowManager = windowManagerInternal;
-        mMainThreadId = context.getMainLooper().getThread().getId();
-        mAms = ams;
-        mScreenStateObserver = new ScreenStateObserver(context, this);
+    /**
+     * Constructor for tests
+     */
+    @VisibleForTesting
+    public MagnificationController(@NonNull ControllerContext ctx, @NonNull Object lock) {
+        mControllerCtx = ctx;
         mLock = lock;
-        mSettingsBridge = settingsBridge;
-        //TODO (multidisplay): Magnification is supported only for the default display.
-        mDisplay =  new DisplayMagnification(Display.DEFAULT_DISPLAY,
-                new SpecAnimationBridge(context, mLock, mWindowManager, valueAnimator));
+        mMainThreadId = mControllerCtx.getContext().getMainLooper().getThread().getId();
+        mScreenStateObserver = new ScreenStateObserver(mControllerCtx.getContext(), this);
     }
 
     /**
@@ -639,54 +617,114 @@
      *
      * This tracking imposes a cost on the system, so we avoid tracking this data unless it's
      * required.
+     *
+     * @param displayId The logical display id.
      */
-    public void register() {
+    public void register(int displayId) {
         synchronized (mLock) {
-            mScreenStateObserver.register();
+            DisplayMagnification display = mDisplays.get(displayId);
+            if (display == null) {
+                display = new DisplayMagnification(displayId);
+            }
+            if (display.isRegistered()) {
+                return;
+            }
+            if (display.register()) {
+                mDisplays.put(displayId, display);
+                mScreenStateObserver.registerIfNecessary();
+            }
         }
-        mDisplay.register();
     }
 
     /**
      * Stop requiring tracking the magnification region. We may remain registered while we
      * reset magnification.
-     */
-    public void unregister() {
-        synchronized (mLock) {
-            mScreenStateObserver.unregister();
-        }
-        mDisplay.unregister();
-    }
-    
-    /**
-     * Check if we are registered. Note that we may be planning to unregister at any moment.
      *
-     * @return {@code true} if the controller is registered. {@code false} otherwise.
+     * @param displayId The logical display id.
      */
-    public boolean isRegisteredLocked() {
-        return mDisplay.isRegisteredLocked();
+    public void unregister(int displayId) {
+        synchronized (mLock) {
+            unregisterLocked(displayId, false);
+        }
     }
 
     /**
+     * Stop tracking all displays' magnification region.
+     */
+    public void unregisterAll() {
+        synchronized (mLock) {
+            // display will be removed from array after unregister, we need to clone it to
+            // prevent error.
+            final SparseArray<DisplayMagnification> displays = mDisplays.clone();
+            for (int i = 0; i < displays.size(); i++) {
+                unregisterLocked(displays.keyAt(i), false);
+            }
+        }
+    }
+
+    /**
+     * Remove the display magnification with given id.
+     *
+     * @param displayId The logical display id.
+     */
+    public void onDisplayRemoved(int displayId) {
+        synchronized (mLock) {
+            unregisterLocked(displayId, true);
+        }
+    }
+
+    /**
+     * Check if we are registered on specified display. Note that we may be planning to unregister
+     * at any moment.
+     *
+     * @return {@code true} if the controller is registered on specified display.
+     * {@code false} otherwise.
+     *
+     * @param displayId The logical display id.
+     */
+    public boolean isRegistered(int displayId) {
+        synchronized (mLock) {
+            final DisplayMagnification display = mDisplays.get(displayId);
+            if (display == null) {
+                return false;
+            }
+            return display.isRegistered();
+        }
+    }
+
+    /**
+     * @param displayId The logical display id.
      * @return {@code true} if magnification is active, e.g. the scale
      *         is > 1, {@code false} otherwise
      */
-    public boolean isMagnifying() {
-        return mDisplay.isMagnifying();
+    public boolean isMagnifying(int displayId) {
+        synchronized (mLock) {
+            final DisplayMagnification display = mDisplays.get(displayId);
+            if (display == null) {
+                return false;
+            }
+            return display.isMagnifying();
+        }
     }
 
     /**
      * Returns whether the magnification region contains the specified
      * screen-relative coordinates.
      *
+     * @param displayId The logical display id.
      * @param x the screen-relative X coordinate to check
      * @param y the screen-relative Y coordinate to check
      * @return {@code true} if the coordinate is contained within the
      *         magnified region, or {@code false} otherwise
      */
-    public boolean magnificationRegionContains(float x, float y) {
-        return mDisplay.magnificationRegionContains(x, y);
-
+    public boolean magnificationRegionContains(int displayId, float x, float y) {
+        synchronized (mLock) {
+            final DisplayMagnification display = mDisplays.get(displayId);
+            if (display == null) {
+                return false;
+            }
+            return display.magnificationRegionContains(x, y);
+        }
     }
 
     /**
@@ -694,11 +732,18 @@
      * magnification region. If magnification is not enabled, the returned
      * bounds will be empty.
      *
+     * @param displayId The logical display id.
      * @param outBounds rect to populate with the bounds of the magnified
      *                  region
      */
-    public void getMagnificationBounds(@NonNull Rect outBounds) {
-        mDisplay.getMagnificationBounds(outBounds);
+    public void getMagnificationBounds(int displayId, @NonNull Rect outBounds) {
+        synchronized (mLock) {
+            final DisplayMagnification display = mDisplays.get(displayId);
+            if (display == null) {
+                return;
+            }
+            display.getMagnificationBounds(outBounds);
+        }
     }
 
     /**
@@ -706,76 +751,122 @@
      * region. If magnification is not enabled, then the returned region
      * will be empty.
      *
+     * @param displayId The logical display id.
      * @param outRegion the region to populate
      */
-    public void getMagnificationRegion(@NonNull Region outRegion) {
-        mDisplay.getMagnificationRegion(outRegion);
+    public void getMagnificationRegion(int displayId, @NonNull Region outRegion) {
+        synchronized (mLock) {
+            final DisplayMagnification display = mDisplays.get(displayId);
+            if (display == null) {
+                return;
+            }
+            display.getMagnificationRegion(outRegion);
+        }
     }
 
     /**
      * Returns the magnification scale. If an animation is in progress,
      * this reflects the end state of the animation.
      *
+     * @param displayId The logical display id.
      * @return the scale
      */
-    public float getScale() {
-        return mDisplay.getScale();
+    public float getScale(int displayId) {
+        synchronized (mLock) {
+            final DisplayMagnification display = mDisplays.get(displayId);
+            if (display == null) {
+                return 1.0f;
+            }
+            return display.getScale();
+        }
     }
 
     /**
      * Returns the X offset of the magnification viewport. If an animation
      * is in progress, this reflects the end state of the animation.
      *
+     * @param displayId The logical display id.
      * @return the X offset
      */
-    public float getOffsetX() {
-        return mDisplay.getOffsetX();
+    public float getOffsetX(int displayId) {
+        synchronized (mLock) {
+            final DisplayMagnification display = mDisplays.get(displayId);
+            if (display == null) {
+                return 0.0f;
+            }
+            return display.getOffsetX();
+        }
     }
 
-
     /**
      * Returns the screen-relative X coordinate of the center of the
      * magnification viewport.
      *
+     * @param displayId The logical display id.
      * @return the X coordinate
      */
-    public float getCenterX() {
-        return mDisplay.getCenterX();
+    public float getCenterX(int displayId) {
+        synchronized (mLock) {
+            final DisplayMagnification display = mDisplays.get(displayId);
+            if (display == null) {
+                return 0.0f;
+            }
+            return display.getCenterX();
+        }
     }
 
     /**
      * Returns the Y offset of the magnification viewport. If an animation
      * is in progress, this reflects the end state of the animation.
      *
+     * @param displayId The logical display id.
      * @return the Y offset
      */
-    public float getOffsetY() {
-        return mDisplay.getOffsetY();
+    public float getOffsetY(int displayId) {
+        synchronized (mLock) {
+            final DisplayMagnification display = mDisplays.get(displayId);
+            if (display == null) {
+                return 0.0f;
+            }
+            return display.getOffsetY();
+        }
     }
 
     /**
      * Returns the screen-relative Y coordinate of the center of the
      * magnification viewport.
      *
+     * @param displayId The logical display id.
      * @return the Y coordinate
      */
-    public float getCenterY() {
-        return mDisplay.getCenterY();
+    public float getCenterY(int displayId) {
+        synchronized (mLock) {
+            final DisplayMagnification display = mDisplays.get(displayId);
+            if (display == null) {
+                return 0.0f;
+            }
+            return display.getCenterY();
+        }
     }
 
     /**
      * Resets the magnification scale and center, optionally animating the
      * transition.
      *
+     * @param displayId The logical display id.
      * @param animate {@code true} to animate the transition, {@code false}
      *                to transition immediately
      * @return {@code true} if the magnification spec changed, {@code false} if
      *         the spec did not change
      */
-    public boolean reset(boolean animate) {
-
-        return mDisplay.reset(animate);
-
+    public boolean reset(int displayId, boolean animate) {
+        synchronized (mLock) {
+            final DisplayMagnification display = mDisplays.get(displayId);
+            if (display == null) {
+                return false;
+            }
+            return display.reset(animate);
+        }
     }
 
     /**
@@ -783,6 +874,7 @@
      * optionally animating the transition. If animation is disabled, the
      * transition is immediate.
      *
+     * @param displayId The logical display id.
      * @param scale the target scale, must be >= 1
      * @param pivotX the screen-relative X coordinate around which to scale
      * @param pivotY the screen-relative Y coordinate around which to scale
@@ -792,15 +884,22 @@
      * @return {@code true} if the magnification spec changed, {@code false} if
      *         the spec did not change
      */
-    public boolean setScale(float scale, float pivotX, float pivotY, boolean animate, int id) {
-            return mDisplay.
-                    setScale(scale, pivotX, pivotY, animate, id);
+    public boolean setScale(int displayId, float scale, float pivotX, float pivotY,
+            boolean animate, int id) {
+        synchronized (mLock) {
+            final DisplayMagnification display = mDisplays.get(displayId);
+            if (display == null) {
+                return false;
+            }
+            return display.setScale(scale, pivotX, pivotY, animate, id);
+        }
     }
 
     /**
      * Sets the center of the magnified region, optionally animating the
      * transition. If animation is disabled, the transition is immediate.
      *
+     * @param displayId The logical display id.
      * @param centerX the screen-relative X coordinate around which to
      *                center
      * @param centerY the screen-relative Y coordinate around which to
@@ -811,9 +910,14 @@
      * @return {@code true} if the magnification spec changed, {@code false} if
      * the spec did not change
      */
-    public boolean setCenter(float centerX, float centerY, boolean animate, int id) {
-            return mDisplay.
-                    setScaleAndCenter(Float.NaN, centerX, centerY, animate, id);
+    public boolean setCenter(int displayId, float centerX, float centerY, boolean animate, int id) {
+        synchronized (mLock) {
+            final DisplayMagnification display = mDisplays.get(displayId);
+            if (display == null) {
+                return false;
+            }
+            return display.setScaleAndCenter(Float.NaN, centerX, centerY, animate, id);
+        }
     }
 
     /**
@@ -821,6 +925,7 @@
      * animating the transition. If animation is disabled, the transition
      * is immediate.
      *
+     * @param displayId The logical display id.
      * @param scale the target scale, or {@link Float#NaN} to leave unchanged
      * @param centerX the screen-relative X coordinate around which to
      *                center and scale, or {@link Float#NaN} to leave unchanged
@@ -832,53 +937,66 @@
      * @return {@code true} if the magnification spec changed, {@code false} if
      *         the spec did not change
      */
-    public boolean setScaleAndCenter(
-            float scale, float centerX, float centerY, boolean animate, int id) {
-        return mDisplay.
-                setScaleAndCenter(scale, centerX, centerY, animate, id);
+    public boolean setScaleAndCenter(int displayId, float scale, float centerX, float centerY,
+            boolean animate, int id) {
+        synchronized (mLock) {
+            final DisplayMagnification display = mDisplays.get(displayId);
+            if (display == null) {
+                return false;
+            }
+            return display.setScaleAndCenter(scale, centerX, centerY, animate, id);
+        }
     }
 
     /**
      * Offsets the magnified region. Note that the offsetX and offsetY values actually move in the
      * opposite direction as the offsets passed in here.
      *
+     * @param displayId The logical display id.
      * @param offsetX the amount in pixels to offset the region in the X direction, in current
      *                screen pixels.
      * @param offsetY the amount in pixels to offset the region in the Y direction, in current
      *                screen pixels.
      * @param id      the ID of the service requesting the change
      */
-    public void offsetMagnifiedRegion(float offsetX, float offsetY, int id) {
-        mDisplay.offsetMagnifiedRegion(offsetX, offsetY,
-                id);
+    public void offsetMagnifiedRegion(int displayId, float offsetX, float offsetY, int id) {
+        synchronized (mLock) {
+            final DisplayMagnification display = mDisplays.get(displayId);
+            if (display == null) {
+                return;
+            }
+            display.offsetMagnifiedRegion(offsetX, offsetY, id);
+        }
     }
 
     /**
      * Get the ID of the last service that changed the magnification spec.
      *
+     * @param displayId The logical display id.
      * @return The id
      */
-    public int getIdOfLastServiceToMagnify() {
-        return mDisplay.getIdOfLastServiceToMagnify();
+    public int getIdOfLastServiceToMagnify(int displayId) {
+        synchronized (mLock) {
+            final DisplayMagnification display = mDisplays.get(displayId);
+            if (display == null) {
+                return -1;
+            }
+            return display.getIdOfLastServiceToMagnify();
+        }
     }
 
     /**
-     * Persists the current magnification scale to the current user's settings.
+     * Persists the default display magnification scale to the current user's settings.
      */
     public void persistScale() {
-        persistScale(Display.DEFAULT_DISPLAY);
-    }
-    /**
-     * Persists the current magnification scale to the current user's settings.
-     */
-    public void persistScale(int displayId) {
-        final float scale = mDisplay.getScale();
+        // TODO: b/123047354, Need support multi-display?
+        final float scale = getScale(Display.DEFAULT_DISPLAY);
         final int userId = mUserId;
 
         new AsyncTask<Void, Void, Void>() {
             @Override
             protected Void doInBackground(Void... params) {
-                mSettingsBridge.putMagnificationScale(scale, displayId, userId);
+                mControllerCtx.putMagnificationScale(scale, userId);
                 return null;
             }
         }.execute();
@@ -892,7 +1010,7 @@
      *         scale if none is available
      */
     public float getPersistedScale() {
-        return mSettingsBridge.getMagnificationScale(Display.DEFAULT_DISPLAY, mUserId);
+        return mControllerCtx.getMagnificationScale(mUserId);
     }
 
     /**
@@ -901,50 +1019,136 @@
      * @param userId the currently active user ID
      */
     public void setUserId(int userId) {
-        if (mUserId != userId) {
-            mUserId = userId;
+        if (mUserId == userId) {
+            return;
+        }
+        mUserId = userId;
+        resetAllIfNeeded(false);
+    }
 
-            synchronized (mLock) {
-                if (isMagnifying()) {
-                    reset(false);
-                }
+    /**
+     * Resets all displays' magnification if last magnifying service is disabled.
+     *
+     * @param connectionId
+     */
+    public void resetAllIfNeeded(int connectionId) {
+        synchronized (mLock) {
+            for (int i = 0; i < mDisplays.size(); i++) {
+                resetIfNeeded(mDisplays.keyAt(i), connectionId);
             }
         }
     }
 
-   /**
+    /**
      * Resets magnification if magnification and auto-update are both enabled.
      *
+     * @param displayId The logical display id.
      * @param animate whether the animate the transition
-     * @return whether was {@link #isMagnifying magnifying}
+     * @return whether was {@link #isMagnifying(int) magnifying}
      */
-    public boolean resetIfNeeded(boolean animate) {
-        return mDisplay.resetIfNeeded(animate);
+    boolean resetIfNeeded(int displayId, boolean animate) {
+        synchronized (mLock) {
+            final DisplayMagnification display = mDisplays.get(displayId);
+            if (display == null || !display.isMagnifying()) {
+                return false;
+            }
+            display.reset(animate);
+            return true;
+        }
     }
 
     /**
      * Resets magnification if last magnifying service is disabled.
      *
+     * @param displayId The logical display id.
      * @param connectionId the connection ID be disabled.
      * @return {@code true} on success, {@code false} on failure
      */
-    public boolean resetIfNeeded(int connectionId) {
-        return mDisplay.resetIfNeeded(connectionId);
+    boolean resetIfNeeded(int displayId, int connectionId) {
+        synchronized (mLock) {
+            final DisplayMagnification display = mDisplays.get(displayId);
+            if (display == null || !display.isMagnifying()
+                    || connectionId != display.getIdOfLastServiceToMagnify()) {
+                return false;
+            }
+            display.reset(true);
+            return true;
+        }
     }
 
-    void setForceShowMagnifiableBounds(boolean show) {
-        mDisplay.setForceShowMagnifiableBounds(show);
+    void setForceShowMagnifiableBounds(int displayId, boolean show) {
+        synchronized (mLock) {
+            final DisplayMagnification display = mDisplays.get(displayId);
+            if (display == null) {
+                return;
+            }
+            display.setForceShowMagnifiableBounds(show);
+        }
     }
 
     private void onScreenTurnedOff() {
         final Message m = PooledLambda.obtainMessage(
-                mDisplay::resetIfNeeded, false);
-        mHandler.sendMessage(m);
+                MagnificationController::resetAllIfNeeded, this, false);
+        mControllerCtx.getHandler().sendMessage(m);
+    }
+
+    private void resetAllIfNeeded(boolean animate) {
+        synchronized (mLock) {
+            for (int i = 0; i < mDisplays.size(); i++) {
+                resetIfNeeded(mDisplays.keyAt(i), animate);
+            }
+        }
+    }
+
+    private void unregisterLocked(int displayId, boolean delete) {
+        final DisplayMagnification display = mDisplays.get(displayId);
+        if (display == null) {
+            return;
+        }
+        if (!display.isRegistered()) {
+            if (delete) {
+                mDisplays.remove(displayId);
+            }
+            return;
+        }
+        if (!display.isMagnifying()) {
+            display.unregister(delete);
+        } else {
+            display.unregisterPending(delete);
+        }
+    }
+
+    /**
+     * Callbacks from DisplayMagnification after display magnification unregistered. It will remove
+     * DisplayMagnification instance if delete is true, and unregister screen state if
+     * there is no registered display magnification.
+     */
+    private void unregisterCallbackLocked(int displayId, boolean delete) {
+        if (delete) {
+            mDisplays.remove(displayId);
+        }
+        // unregister screen state if necessary
+        boolean hasRegister = false;
+        for (int i = 0; i < mDisplays.size(); i++) {
+            final DisplayMagnification display = mDisplays.valueAt(i);
+            hasRegister = display.isRegistered();
+            if (hasRegister) {
+                break;
+            }
+        }
+        if (!hasRegister) {
+            mScreenStateObserver.unregister();
+        }
     }
 
     @Override
     public String toString() {
-        return mDisplay.toString();
+        StringBuilder builder = new StringBuilder();
+        builder.append("MagnificationController[");
+        builder.append("mUserId=").append(mUserId);
+        builder.append(", mDisplays=").append(mDisplays);
+        builder.append("]");
+        return builder.toString();
     }
 
     /**
@@ -952,7 +1156,7 @@
      * updates to the window manager.
      */
     private static class SpecAnimationBridge implements ValueAnimator.AnimatorUpdateListener {
-        private final WindowManagerInternal mWindowManager;
+        private final ControllerContext mControllerCtx;
 
         /**
          * The magnification spec that was sent to the window manager. This should
@@ -973,16 +1177,17 @@
 
         private final Object mLock;
 
+        private final int mDisplayId;
+
         @GuardedBy("mLock")
         private boolean mEnabled = false;
 
-        private SpecAnimationBridge(Context context, Object lock, WindowManagerInternal wm,
-                ValueAnimator animator) {
+        private SpecAnimationBridge(ControllerContext ctx, Object lock, int displayId) {
+            mControllerCtx = ctx;
             mLock = lock;
-            mWindowManager = wm;
-            final long animationDuration = context.getResources().getInteger(
-                    R.integer.config_longAnimTime);
-            mValueAnimator = animator;
+            mDisplayId = displayId;
+            final long animationDuration = mControllerCtx.getAnimationDuration();
+            mValueAnimator = mControllerCtx.newValueAnimator();
             mValueAnimator.setDuration(animationDuration);
             mValueAnimator.setInterpolator(new DecelerateInterpolator(2.5f));
             mValueAnimator.setFloatValues(0.0f, 1.0f);
@@ -999,7 +1204,8 @@
                     mEnabled = enabled;
                     if (!mEnabled) {
                         mSentMagnificationSpec.clear();
-                        mWindowManager.setMagnificationSpec(mSentMagnificationSpec);
+                        mControllerCtx.getWindowManager().setMagnificationSpec(
+                                mDisplayId, mSentMagnificationSpec);
                     }
                 }
             }
@@ -1031,7 +1237,8 @@
                 }
 
                 mSentMagnificationSpec.setTo(spec);
-                mWindowManager.setMagnificationSpec(spec);
+                mControllerCtx.getWindowManager().setMagnificationSpec(
+                        mDisplayId, mSentMagnificationSpec);
             }
         }
 
@@ -1054,9 +1261,7 @@
                     mTmpMagnificationSpec.offsetY = mStartMagnificationSpec.offsetY +
                             (mEndMagnificationSpec.offsetY - mStartMagnificationSpec.offsetY)
                                     * fract;
-                    synchronized (mLock) {
-                        setMagnificationSpecLocked(mTmpMagnificationSpec);
-                    }
+                    setMagnificationSpecLocked(mTmpMagnificationSpec);
                 }
             }
         }
@@ -1072,7 +1277,7 @@
             mController = controller;
         }
 
-        public void register() {
+        public void registerIfNecessary() {
             if (!mRegistered) {
                 mContext.registerReceiver(this, new IntentFilter(Intent.ACTION_SCREEN_OFF));
                 mRegistered = true;
@@ -1092,26 +1297,97 @@
         }
     }
 
-    // Extra class to get settings so tests can mock it
-    public static class SettingsBridge {
-        private final ContentResolver mContentResolver;
+    /**
+     * This class holds resources used between the classes in MagnificationController, and
+     * functions for tests to mock it.
+     */
+    @VisibleForTesting
+    public static class ControllerContext {
+        private final Context mContext;
+        private final AccessibilityManagerService mAms;
+        private final WindowManagerInternal mWindowManager;
+        private final Handler mHandler;
+        private final Long mAnimationDuration;
 
-        public SettingsBridge(ContentResolver contentResolver) {
-            mContentResolver = contentResolver;
+        /**
+         * Constructor for ControllerContext.
+         */
+        public ControllerContext(@NonNull Context context,
+                @NonNull AccessibilityManagerService ams,
+                @NonNull WindowManagerInternal windowManager,
+                @NonNull Handler handler,
+                long animationDuration) {
+            mContext = context;
+            mAms = ams;
+            mWindowManager = windowManager;
+            mHandler = handler;
+            mAnimationDuration = animationDuration;
         }
 
-        public void putMagnificationScale(float value, int displayId, int userId) {
-            Settings.Secure.putFloatForUser(mContentResolver,
-                    Settings.Secure.ACCESSIBILITY_DISPLAY_MAGNIFICATION_SCALE + (
-                            Display.DEFAULT_DISPLAY == displayId ? "" : displayId),
-                    value, userId);
+        /**
+         * @return A context.
+         */
+        @NonNull
+        public Context getContext() {
+            return mContext;
         }
 
-        public float getMagnificationScale(int displayId, int userId) {
-            return Settings.Secure.getFloatForUser(mContentResolver,
-                    Settings.Secure.ACCESSIBILITY_DISPLAY_MAGNIFICATION_SCALE
-                            + (Display.DEFAULT_DISPLAY == displayId ? "" : displayId),
+        /**
+         * @return AccessibilityManagerService
+         */
+        @NonNull
+        public AccessibilityManagerService getAms() {
+            return mAms;
+        }
+
+        /**
+         * @return WindowManagerInternal
+         */
+        @NonNull
+        public WindowManagerInternal getWindowManager() {
+            return mWindowManager;
+        }
+
+        /**
+         * @return Handler for main looper
+         */
+        @NonNull
+        public Handler getHandler() {
+            return mHandler;
+        }
+
+        /**
+         * Create a new ValueAnimator.
+         *
+         * @return ValueAnimator
+         */
+        @NonNull
+        public ValueAnimator newValueAnimator() {
+            return new ValueAnimator();
+        }
+
+        /**
+         * Write Settings of magnification scale.
+         */
+        public void putMagnificationScale(float value, int userId) {
+            Settings.Secure.putFloatForUser(mContext.getContentResolver(),
+                    Settings.Secure.ACCESSIBILITY_DISPLAY_MAGNIFICATION_SCALE, value, userId);
+        }
+
+        /**
+         * Get Settings of magnification scale.
+         */
+        public float getMagnificationScale(int userId) {
+            return Settings.Secure.getFloatForUser(mContext.getContentResolver(),
+                    Settings.Secure.ACCESSIBILITY_DISPLAY_MAGNIFICATION_SCALE,
                     DEFAULT_MAGNIFICATION_SCALE, userId);
         }
+
+        /**
+         * @return Configuration of animation duration.
+         */
+        public long getAnimationDuration() {
+            return mAnimationDuration;
+        }
     }
 }
diff --git a/services/accessibility/java/com/android/server/accessibility/MagnificationGestureHandler.java b/services/accessibility/java/com/android/server/accessibility/MagnificationGestureHandler.java
index 80049e8..2fbaee6 100644
--- a/services/accessibility/java/com/android/server/accessibility/MagnificationGestureHandler.java
+++ b/services/accessibility/java/com/android/server/accessibility/MagnificationGestureHandler.java
@@ -148,6 +148,8 @@
     private PointerCoords[] mTempPointerCoords;
     private PointerProperties[] mTempPointerProperties;
 
+    private final int mDisplayId;
+
     private final Queue<MotionEvent> mDebugInputEventHistory;
     private final Queue<MotionEvent> mDebugOutputEventHistory;
 
@@ -161,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
@@ -173,6 +177,7 @@
         }
 
         mMagnificationController = magnificationController;
+        mDisplayId = displayId;
 
         mDelegatingState = new DelegatingState();
         mDetectingState = new DetectingState(context);
@@ -251,14 +256,15 @@
             mScreenStateReceiver.unregister();
         }
         // Check if need to reset when MagnificationGestureHandler is the last magnifying service.
-        mMagnificationController.resetIfNeeded(
+        mMagnificationController.resetAllIfNeeded(
                 AccessibilityManagerService.MAGNIFICATION_GESTURE_HANDLER_ID);
         clearAndTransitionToStateDetecting();
     }
 
     void notifyShortcutTriggered() {
         if (mDetectShortcutTrigger) {
-            boolean wasMagnifying = mMagnificationController.resetIfNeeded(/* animate */ true);
+            boolean wasMagnifying = mMagnificationController.resetIfNeeded(mDisplayId,
+                    /* animate */ true);
             if (wasMagnifying) {
                 clearAndTransitionToStateDetecting();
             } else {
@@ -419,8 +425,8 @@
                 Slog.i(LOG_TAG, "Panned content by scrollX: " + distanceX
                         + " scrollY: " + distanceY);
             }
-            mMagnificationController.offsetMagnifiedRegion(distanceX, distanceY,
-                    AccessibilityManagerService.MAGNIFICATION_GESTURE_HANDLER_ID);
+            mMagnificationController.offsetMagnifiedRegion(mDisplayId, distanceX,
+                    distanceY, AccessibilityManagerService.MAGNIFICATION_GESTURE_HANDLER_ID);
             return /* event consumed: */ true;
         }
 
@@ -436,7 +442,7 @@
                 return mScaling;
             }
 
-            final float initialScale = mMagnificationController.getScale();
+            final float initialScale = mMagnificationController.getScale(mDisplayId);
             final float targetScale = initialScale * detector.getScaleFactor();
 
             // Don't allow a gesture to move the user further outside the
@@ -458,7 +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");
-            mMagnificationController.setScale(scale, pivotX, pivotY, false,
+            mMagnificationController.setScale(mDisplayId, scale, pivotX, pivotY, false,
                     AccessibilityManagerService.MAGNIFICATION_GESTURE_HANDLER_ID);
             return /* handled: */ true;
         }
@@ -518,8 +524,9 @@
                     }
                     final float eventX = event.getX();
                     final float eventY = event.getY();
-                    if (mMagnificationController.magnificationRegionContains(eventX, eventY)) {
-                        mMagnificationController.setCenter(eventX, eventY,
+                    if (mMagnificationController.magnificationRegionContains(
+                            mDisplayId, eventX, eventY)) {
+                        mMagnificationController.setCenter(mDisplayId, eventX, eventY,
                                 /* animate */ mLastMoveOutsideMagnifiedRegion,
                                 AccessibilityManagerService.MAGNIFICATION_GESTURE_HANDLER_ID);
                         mLastMoveOutsideMagnifiedRegion = false;
@@ -658,7 +665,7 @@
                     mHandler.removeMessages(MESSAGE_TRANSITION_TO_DELEGATING_STATE);
 
                     if (!mMagnificationController.magnificationRegionContains(
-                            event.getX(), event.getY())) {
+                            mDisplayId, event.getX(), event.getY())) {
 
                         transitionToDelegatingStateAndClear();
 
@@ -667,11 +674,15 @@
                         // 3tap and hold
                         afterLongTapTimeoutTransitionToDraggingState(event);
 
+                    } else if (isTapOutOfDistanceSlop()) {
+
+                        transitionToDelegatingStateAndClear();
+
                     } else if (mDetectTripleTap
                             // If magnified, delay an ACTION_DOWN for mMultiTapMaxDelay
                             // to ensure reachability of
                             // STATE_PANNING_SCALING(triggerable with ACTION_POINTER_DOWN)
-                            || mMagnificationController.isMagnifying()) {
+                            || mMagnificationController.isMagnifying(mDisplayId)) {
 
                         afterMultiTapTimeoutTransitionToDelegatingState();
 
@@ -683,7 +694,7 @@
                 }
                 break;
                 case ACTION_POINTER_DOWN: {
-                    if (mMagnificationController.isMagnifying()) {
+                    if (mMagnificationController.isMagnifying(mDisplayId)) {
                         transitionTo(mPanningScalingState);
                         clear();
                     } else {
@@ -713,7 +724,7 @@
                     mHandler.removeMessages(MESSAGE_ON_TRIPLE_TAP_AND_HOLD);
 
                     if (!mMagnificationController.magnificationRegionContains(
-                            event.getX(), event.getY())) {
+                            mDisplayId, event.getX(), event.getY())) {
 
                         transitionToDelegatingStateAndClear();
 
@@ -864,7 +875,7 @@
             clear();
 
             // Toggle zoom
-            if (mMagnificationController.isMagnifying()) {
+            if (mMagnificationController.isMagnifying(mDisplayId)) {
                 zoomOff();
             } else {
                 zoomOn(up.getX(), up.getY());
@@ -877,7 +888,7 @@
             clear();
 
             mViewportDraggingState.mZoomedInBeforeDrag =
-                    mMagnificationController.isMagnifying();
+                    mMagnificationController.isMagnifying(mDisplayId);
 
             zoomOn(down.getX(), down.getY());
 
@@ -904,7 +915,32 @@
             if (DEBUG_DETECTING) Slog.i(LOG_TAG, "setShortcutTriggered(" + state + ")");
 
             mShortcutTriggered = state;
-            mMagnificationController.setForceShowMagnifiableBounds(state);
+            mMagnificationController.setForceShowMagnifiableBounds(mDisplayId, state);
+        }
+
+        /**
+         * Detects if last action down is out of distance slop between with previous
+         * one, when triple tap is enabled.
+         *
+         * @return true if tap is out of distance slop
+         */
+        boolean isTapOutOfDistanceSlop() {
+            if (!mDetectTripleTap) return false;
+            if (mPreLastDown == null || mLastDown == null) {
+                return false;
+            }
+            final boolean outOfDistanceSlop =
+                    GestureUtils.distance(mPreLastDown, mLastDown) > mMultiTapMaxDistance;
+            if (tapCount() > 0) {
+                return outOfDistanceSlop;
+            }
+            // There's no tap in the queue here. We still need to check if this is the case that
+            // user tap screen quickly and out of distance slop.
+            if (outOfDistanceSlop
+                    && !GestureUtils.isTimedOut(mPreLastDown, mLastDown, mMultiTapMaxDelay)) {
+                return true;
+            }
+            return false;
         }
     }
 
@@ -914,7 +950,7 @@
         final float scale = MathUtils.constrain(
                 mMagnificationController.getPersistedScale(),
                 MIN_SCALE, MAX_SCALE);
-        mMagnificationController.setScaleAndCenter(
+        mMagnificationController.setScaleAndCenter(mDisplayId,
                 scale, centerX, centerY,
                 /* animate */ true,
                 AccessibilityManagerService.MAGNIFICATION_GESTURE_HANDLER_ID);
@@ -922,8 +958,7 @@
 
     private void zoomOff() {
         if (DEBUG_DETECTING) Slog.i(LOG_TAG, "zoomOff()");
-
-        mMagnificationController.reset(/* animate */ true);
+        mMagnificationController.reset(mDisplayId, /* animate */ true);
     }
 
     private static MotionEvent recycleAndNullify(@Nullable MotionEvent event) {
@@ -945,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/autofill/java/com/android/server/autofill/AutofillManagerService.java b/services/autofill/java/com/android/server/autofill/AutofillManagerService.java
index c992da4..2e45fa7 100644
--- a/services/autofill/java/com/android/server/autofill/AutofillManagerService.java
+++ b/services/autofill/java/com/android/server/autofill/AutofillManagerService.java
@@ -18,14 +18,13 @@
 
 import static android.Manifest.permission.MANAGE_AUTO_FILL;
 import static android.content.Context.AUTOFILL_MANAGER_SERVICE;
-import static android.util.DebugUtils.flagsToString;
 import static android.view.autofill.AutofillManager.MAX_TEMP_AUGMENTED_SERVICE_DURATION_MS;
+import static android.view.autofill.AutofillManager.getSmartSuggestionModeToString;
 
 import static com.android.server.autofill.Helper.sDebug;
 import static com.android.server.autofill.Helper.sFullScreenMode;
 import static com.android.server.autofill.Helper.sVerbose;
 
-import android.annotation.IntDef;
 import android.annotation.NonNull;
 import android.annotation.Nullable;
 import android.annotation.UserIdInt;
@@ -61,6 +60,7 @@
 import android.util.SparseArray;
 import android.view.autofill.AutofillId;
 import android.view.autofill.AutofillManager;
+import android.view.autofill.AutofillManager.SmartSuggestionMode;
 import android.view.autofill.AutofillManagerInternal;
 import android.view.autofill.AutofillValue;
 import android.view.autofill.IAutoFillManager;
@@ -80,8 +80,6 @@
 
 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.Arrays;
 import java.util.List;
@@ -102,26 +100,6 @@
 
     private static final Object sLock = AutofillManagerService.class;
 
-    /**
-     * IME supports Smart Suggestions.
-     */
-    // NOTE: must be public because of flagsToString()
-    public static final int FLAG_SMART_SUGGESTION_IME = 0x1;
-
-    /**
-     * System supports Smarts Suggestions (as a popup-window similar to standard Autofill).
-     */
-    // NOTE: must be public because of flagsToString()
-    public static final int FLAG_SMART_SUGGESTION_SYSTEM = 0x2;
-
-    /** @hide */
-    @IntDef(flag = true, prefix = { "FLAG_SMART_SUGGESTION_" }, value = {
-            FLAG_SMART_SUGGESTION_IME,
-            FLAG_SMART_SUGGESTION_SYSTEM
-    })
-    @Retention(RetentionPolicy.SOURCE)
-    @interface SmartSuggestionMode {}
-
     static final String RECEIVER_BUNDLE_EXTRA_SESSIONS = "sessions";
 
     private static final char COMPAT_PACKAGE_DELIMITER = ':';
@@ -484,7 +462,7 @@
                 Settings.Global.AUTOFILL_SMART_SUGGESTION_EMULATION_FLAGS, 0);
         if (sDebug) {
             Slog.d(TAG, "setSmartSuggestionEmulationFromSettings(): "
-                    + smartSuggestionFlagsToString(flags));
+                    + getSmartSuggestionModeToString(flags));
         }
 
         synchronized (mLock) {
@@ -698,10 +676,6 @@
         }
     }
 
-    static String smartSuggestionFlagsToString(int flags) {
-        return flagsToString(AutofillManagerService.class, "FLAG_SMART_SUGGESTION_", flags);
-    }
-
     private final class LocalService extends AutofillManagerInternal {
         @Override
         public void onBackKeyPressed() {
@@ -1251,7 +1225,7 @@
                     pw.println(getWhitelistedCompatModePackagesFromSettings());
                     if (mSupportedSmartSuggestionModes != 0) {
                         pw.print("Smart Suggestion modes: ");
-                        pw.println(smartSuggestionFlagsToString(mSupportedSmartSuggestionModes));
+                        pw.println(getSmartSuggestionModeToString(mSupportedSmartSuggestionModes));
                     }
                     if (showHistory) {
                         pw.println(); pw.println("Requests history:"); pw.println();
diff --git a/services/autofill/java/com/android/server/autofill/AutofillManagerServiceImpl.java b/services/autofill/java/com/android/server/autofill/AutofillManagerServiceImpl.java
index 954b67e..8886ee2 100644
--- a/services/autofill/java/com/android/server/autofill/AutofillManagerServiceImpl.java
+++ b/services/autofill/java/com/android/server/autofill/AutofillManagerServiceImpl.java
@@ -63,6 +63,7 @@
 import android.util.TimeUtils;
 import android.view.autofill.AutofillId;
 import android.view.autofill.AutofillManager;
+import android.view.autofill.AutofillManager.SmartSuggestionMode;
 import android.view.autofill.AutofillValue;
 import android.view.autofill.IAutoFillManagerClient;
 
@@ -72,7 +73,6 @@
 import com.android.internal.logging.nano.MetricsProto.MetricsEvent;
 import com.android.server.LocalServices;
 import com.android.server.autofill.AutofillManagerService.AutofillCompatState;
-import com.android.server.autofill.AutofillManagerService.SmartSuggestionMode;
 import com.android.server.autofill.RemoteAugmentedAutofillService.RemoteAugmentedAutofillServiceCallbacks;
 import com.android.server.autofill.ui.AutoFillUI;
 import com.android.server.infra.AbstractPerUserSystemService;
@@ -855,7 +855,6 @@
 
     @GuardedBy("mLock")
     @SmartSuggestionMode int getSupportedSmartSuggestionModesLocked() {
-        // TODO(b/111330312): once we support IME, we need to set it per-user (OR'ed with master)
         return mMaster.getSupportedSmartSuggestionModesLocked();
     }
 
@@ -1049,7 +1048,7 @@
                     componentName, mUserId, new RemoteAugmentedAutofillServiceCallbacks() {
                         @Override
                         public void onServiceDied(@NonNull RemoteAugmentedAutofillService service) {
-                            // TODO(b/111330312): properly implement
+                            // TODO(b/123100811): properly implement
                             Slog.w(TAG, "remote augmented autofill service died");
                         }
                     }, mMaster.isInstantServiceAllowed(), mMaster.verbose);
diff --git a/services/autofill/java/com/android/server/autofill/RemoteAugmentedAutofillService.java b/services/autofill/java/com/android/server/autofill/RemoteAugmentedAutofillService.java
index 5d8d8fa..9b863a9 100644
--- a/services/autofill/java/com/android/server/autofill/RemoteAugmentedAutofillService.java
+++ b/services/autofill/java/com/android/server/autofill/RemoteAugmentedAutofillService.java
@@ -113,7 +113,7 @@
         scheduleAsyncRequest((s) -> s.onDestroyAllFillWindowsRequest());
     }
 
-    // TODO(b/111330312): inline into PendingAutofillRequest if it doesn't have any other subclass
+    // TODO(b/123100811): inline into PendingAutofillRequest if it doesn't have any other subclass
     private abstract static class MyPendingRequest
             extends PendingRequest<RemoteAugmentedAutofillService, IAugmentedAutofillService> {
         protected final int mSessionId;
diff --git a/services/autofill/java/com/android/server/autofill/Session.java b/services/autofill/java/com/android/server/autofill/Session.java
index 7dfd8fe..194332a 100644
--- a/services/autofill/java/com/android/server/autofill/Session.java
+++ b/services/autofill/java/com/android/server/autofill/Session.java
@@ -23,11 +23,10 @@
 import static android.view.autofill.AutofillManager.ACTION_VALUE_CHANGED;
 import static android.view.autofill.AutofillManager.ACTION_VIEW_ENTERED;
 import static android.view.autofill.AutofillManager.ACTION_VIEW_EXITED;
+import static android.view.autofill.AutofillManager.FLAG_SMART_SUGGESTION_SYSTEM;
+import static android.view.autofill.AutofillManager.getSmartSuggestionModeToString;
 
 import static com.android.internal.util.function.pooled.PooledLambda.obtainMessage;
-import static com.android.server.autofill.AutofillManagerService.FLAG_SMART_SUGGESTION_IME;
-import static com.android.server.autofill.AutofillManagerService.FLAG_SMART_SUGGESTION_SYSTEM;
-import static com.android.server.autofill.AutofillManagerService.smartSuggestionFlagsToString;
 import static com.android.server.autofill.Helper.getNumericValue;
 import static com.android.server.autofill.Helper.sDebug;
 import static com.android.server.autofill.Helper.sVerbose;
@@ -88,6 +87,7 @@
 import android.view.KeyEvent;
 import android.view.autofill.AutofillId;
 import android.view.autofill.AutofillManager;
+import android.view.autofill.AutofillManager.SmartSuggestionMode;
 import android.view.autofill.AutofillValue;
 import android.view.autofill.IAutoFillManagerClient;
 import android.view.autofill.IAutofillWindowPresenter;
@@ -97,7 +97,6 @@
 import com.android.internal.logging.MetricsLogger;
 import com.android.internal.logging.nano.MetricsProto.MetricsEvent;
 import com.android.internal.util.ArrayUtils;
-import com.android.server.autofill.AutofillManagerService.SmartSuggestionMode;
 import com.android.server.autofill.ui.AutoFillUI;
 import com.android.server.autofill.ui.PendingUi;
 
@@ -250,7 +249,7 @@
     /**
      * Destroys the augmented Autofill UI.
      */
-    // TODO(b/111330312): this runnable is called when the Autofill session is destroyed, the
+    // TODO(b/123099468): this runnable is called when the Autofill session is destroyed, the
     // main reason being the cases where user tap HOME.
     // Right now it's completely destroying the UI, but we need to decide whether / how to
     // properly recover it later (for example, if the user switches back to the activity,
@@ -261,6 +260,12 @@
     private Runnable mAugmentedAutofillDestroyer;
 
     /**
+     * List of {@link MetricsEvent#AUTOFILL_AUGMENTED_REQUEST} metrics.
+     */
+    @GuardedBy("mLock")
+    private ArrayList<LogMaker> mAugmentedRequestsLogs;
+
+    /**
      * Receiver of assist data from the app's {@link Activity}.
      */
     private final IAssistDataReceiver mAssistReceiver = new IAssistDataReceiver.Stub() {
@@ -2541,8 +2546,8 @@
         }
         mService.resetLastResponse();
 
-        // The default autofill service cannot fullfill the request, let's check if the intelligence
-        // service can.
+        // The default autofill service cannot fullfill the request, let's check if the augmented
+        // autofill service can.
         mAugmentedAutofillDestroyer = triggerAugmentedAutofillLocked();
         if (mAugmentedAutofillDestroyer == null) {
             if (sVerbose) {
@@ -2553,7 +2558,7 @@
             notifyUnavailableToClient(AutofillManager.STATE_FINISHED);
             removeSelf();
         } else {
-            // TODO(b/111330312, b/119638958): must set internal state so when user focus other
+            // TODO(b/123099468, b/119638958): must set internal state so when user focus other
             // fields it does not generate a new call to the standard autofill service (right now
             // it does). Must also add CTS tests to exercise this scenario.
             if (sVerbose) {
@@ -2568,7 +2573,7 @@
      *
      * @return callback to destroy the autofill UI, or {@code null} if not supported.
      */
-    // TODO(b/111330312): might need to call it in other places, like when the service returns a
+    // TODO(b/123099468): might need to call it in other places, like when the service returns a
     // non-null response but without datasets (for example, just SaveInfo)
     @GuardedBy("mLock")
     private Runnable triggerAugmentedAutofillLocked() {
@@ -2588,14 +2593,12 @@
 
         // Define which mode will be used
         final int mode;
-        if ((supportedModes & FLAG_SMART_SUGGESTION_IME) != 0) {
-            // TODO(b/111330312): support it :-)
-            Slog.w(TAG, "Smart Suggestions on IME not supported yet");
-            return null;
-        } else if ((supportedModes & FLAG_SMART_SUGGESTION_SYSTEM) != 0) {
+        if ((supportedModes & FLAG_SMART_SUGGESTION_SYSTEM) != 0) {
             mode = FLAG_SMART_SUGGESTION_SYSTEM;
+        } else if ((supportedModes & AutofillManager.FLAG_SMART_SUGGESTION_LEGACY) != 0) {
+            mode = AutofillManager.FLAG_SMART_SUGGESTION_LEGACY;
         } else {
-            Slog.w(TAG, "Unsupported Smart Suggestion Mode: " + supportedModes);
+            Slog.w(TAG, "Unsupported Smart Suggestion mode: " + supportedModes);
             return null;
         }
 
@@ -2605,8 +2608,10 @@
         }
 
         if (sVerbose) {
-            Slog.v(TAG, "calling IntelligenseService on view " + mCurrentViewId
-                    + " using suggestion mode " + smartSuggestionFlagsToString(mode)
+            Slog.v(TAG, "calling Augmented Autofill Service ("
+                    + remoteService.getComponentName().toShortString() + ") on view "
+                    + mCurrentViewId + " using suggestion mode "
+                    + getSmartSuggestionModeToString(mode)
                     + " when server returned null for session " + this.id);
         }
 
@@ -2620,8 +2625,15 @@
         final AutofillValue currentValue = mViewStates.get(mCurrentViewId).getCurrentValue();
 
         // TODO(b/111330312): we might need to add a new state in the AutofillManager to optimize
-        // furgher AFM -> AFMS calls.
-        // TODO(b/119638958): add CTS tests
+        // further AFM -> AFMS calls.
+
+        if (mAugmentedRequestsLogs == null) {
+            mAugmentedRequestsLogs = new ArrayList<>();
+        }
+        final LogMaker log = newLogMaker(MetricsEvent.AUTOFILL_AUGMENTED_REQUEST,
+                remoteService.getComponentName().getPackageName());
+        mAugmentedRequestsLogs.add(log);
+
         remoteService.onRequestAutofillLocked(id, mClient, taskId, mComponentName, mCurrentViewId,
                 currentValue);
 
@@ -2912,6 +2924,11 @@
         if (mAugmentedAutofillDestroyer != null) {
             pw.print(prefix); pw.println("has mAugmentedAutofillDestroyer");
         }
+        if (mAugmentedRequestsLogs != null) {
+            pw.print(prefix); pw.print("number augmented requests: ");
+            pw.println(mAugmentedRequestsLogs.size());
+        }
+
         mRemoteFillService.dump(prefix, pw);
     }
 
@@ -3053,8 +3070,26 @@
                 mMetricsLogger.write(log);
             }
         }
-        mMetricsLogger.write(newLogMaker(MetricsEvent.AUTOFILL_SESSION_FINISHED)
-                .addTaggedData(MetricsEvent.FIELD_AUTOFILL_NUMBER_REQUESTS, totalRequests));
+
+        final int totalAugmentedRequests = mAugmentedRequestsLogs == null ? 0
+                : mAugmentedRequestsLogs.size();
+        if (totalAugmentedRequests > 0) {
+            if (sVerbose) {
+                Slog.v(TAG, "destroyLocked(): logging " + totalRequests + " augmented requests");
+            }
+            for (int i = 0; i < totalAugmentedRequests; i++) {
+                final LogMaker log = mAugmentedRequestsLogs.get(i);
+                mMetricsLogger.write(log);
+            }
+        }
+
+        final LogMaker log = newLogMaker(MetricsEvent.AUTOFILL_SESSION_FINISHED)
+                .addTaggedData(MetricsEvent.FIELD_AUTOFILL_NUMBER_REQUESTS, totalRequests);
+        if (totalAugmentedRequests > 0) {
+            log.addTaggedData(MetricsEvent.FIELD_AUTOFILL_NUMBER_AUGMENTED_REQUESTS,
+                    totalAugmentedRequests);
+        }
+        mMetricsLogger.write(log);
 
         return mRemoteFillService;
     }
diff --git a/services/backup/java/com/android/server/backup/PackageManagerBackupAgent.java b/services/backup/java/com/android/server/backup/PackageManagerBackupAgent.java
index 4a1e5b9..2241569 100644
--- a/services/backup/java/com/android/server/backup/PackageManagerBackupAgent.java
+++ b/services/backup/java/com/android/server/backup/PackageManagerBackupAgent.java
@@ -164,7 +164,7 @@
         int N = pkgs.size();
         for (int a = N-1; a >= 0; a--) {
             PackageInfo pkg = pkgs.get(a);
-            if (!AppBackupUtils.appIsEligibleForBackup(pkg.applicationInfo, pm)) {
+            if (!AppBackupUtils.appIsEligibleForBackup(pkg.applicationInfo, userId)) {
                 pkgs.remove(a);
             }
         }
diff --git a/services/backup/java/com/android/server/backup/Trampoline.java b/services/backup/java/com/android/server/backup/Trampoline.java
index b9a6f3c..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");
                 }
@@ -636,14 +695,18 @@
     }
 
     @Override
-    public void opComplete(int token, long result) throws RemoteException {
-        int userId = binderGetCallingUserId();
+    public void opCompleteForUser(int userId, int token, long result) throws RemoteException {
         if (isUserReadyForBackup(userId)) {
-            mService.opComplete(binderGetCallingUserId(), token, result);
+            mService.opComplete(userId, token, result);
         }
     }
 
     @Override
+    public void opComplete(int token, long result) throws RemoteException {
+        opCompleteForUser(binderGetCallingUserId(), token, result);
+    }
+
+    @Override
     public long getAvailableRestoreTokenForUser(int userId, String packageName) {
         return isUserReadyForBackup(userId) ? mService.getAvailableRestoreToken(userId,
                 packageName) : 0;
diff --git a/services/backup/java/com/android/server/backup/UserBackupManagerService.java b/services/backup/java/com/android/server/backup/UserBackupManagerService.java
index 79f8a7e..115e924 100644
--- a/services/backup/java/com/android/server/backup/UserBackupManagerService.java
+++ b/services/backup/java/com/android/server/backup/UserBackupManagerService.java
@@ -804,7 +804,7 @@
     public BackupAgent makeMetadataAgent() {
         PackageManagerBackupAgent pmAgent = new PackageManagerBackupAgent(mPackageManager, mUserId);
         pmAgent.attach(mContext);
-        pmAgent.onCreate();
+        pmAgent.onCreate(UserHandle.of(mUserId));
         return pmAgent;
     }
 
@@ -815,7 +815,7 @@
         PackageManagerBackupAgent pmAgent =
                 new PackageManagerBackupAgent(mPackageManager, packages, mUserId);
         pmAgent.attach(mContext);
-        pmAgent.onCreate();
+        pmAgent.onCreate(UserHandle.of(mUserId));
         return pmAgent;
     }
 
@@ -910,10 +910,10 @@
                     long lastBackup = in.readLong();
                     foundApps.add(pkgName); // all apps that we've addressed already
                     try {
-                        PackageInfo pkg = mPackageManager.getPackageInfo(pkgName, 0);
+                        PackageInfo pkg = mPackageManager.getPackageInfoAsUser(pkgName, 0, mUserId);
                         if (AppBackupUtils.appGetsFullBackup(pkg)
-                                && AppBackupUtils.appIsEligibleForBackup(
-                                pkg.applicationInfo, mPackageManager)) {
+                                && AppBackupUtils.appIsEligibleForBackup(pkg.applicationInfo,
+                                mUserId)) {
                             schedule.add(new FullBackupEntry(pkgName, lastBackup));
                         } else {
                             if (DEBUG) {
@@ -933,8 +933,8 @@
                 // scan to make sure that we're tracking all full-backup candidates properly
                 for (PackageInfo app : apps) {
                     if (AppBackupUtils.appGetsFullBackup(app)
-                            && AppBackupUtils.appIsEligibleForBackup(
-                            app.applicationInfo, mPackageManager)) {
+                            && AppBackupUtils.appIsEligibleForBackup(app.applicationInfo,
+                            mUserId)) {
                         if (!foundApps.contains(app.packageName)) {
                             if (MORE_DEBUG) {
                                 Slog.i(TAG, "New full backup app " + app.packageName + " found");
@@ -960,7 +960,7 @@
             schedule = new ArrayList<>(apps.size());
             for (PackageInfo info : apps) {
                 if (AppBackupUtils.appGetsFullBackup(info) && AppBackupUtils.appIsEligibleForBackup(
-                        info.applicationInfo, mPackageManager)) {
+                        info.applicationInfo, mUserId)) {
                     schedule.add(new FullBackupEntry(info.packageName, 0));
                 }
             }
@@ -1222,8 +1222,8 @@
                                 mPackageManager.getPackageInfoAsUser(
                                         packageName, /* flags */ 0, mUserId);
                         if (AppBackupUtils.appGetsFullBackup(app)
-                                && AppBackupUtils.appIsEligibleForBackup(
-                                app.applicationInfo, mPackageManager)) {
+                                && AppBackupUtils.appIsEligibleForBackup(app.applicationInfo,
+                                mUserId)) {
                             enqueueFullBackup(packageName, now);
                             scheduleNextFullBackupJob(0);
                         } else {
@@ -1618,8 +1618,7 @@
             try {
                 PackageInfo packageInfo = mPackageManager.getPackageInfoAsUser(packageName,
                         PackageManager.GET_SIGNING_CERTIFICATES, mUserId);
-                if (!AppBackupUtils.appIsEligibleForBackup(packageInfo.applicationInfo,
-                        mPackageManager)) {
+                if (!AppBackupUtils.appIsEligibleForBackup(packageInfo.applicationInfo, mUserId)) {
                     BackupObserverUtils.sendBackupOnPackageResult(observer, packageName,
                             BackupManager.ERROR_BACKUP_NOT_ALLOWED);
                     continue;
@@ -2095,7 +2094,8 @@
                     }
 
                     try {
-                        PackageInfo appInfo = mPackageManager.getPackageInfo(entry.packageName, 0);
+                        PackageInfo appInfo = mPackageManager.getPackageInfoAsUser(
+                                entry.packageName, 0, mUserId);
                         if (!AppBackupUtils.appGetsFullBackup(appInfo)) {
                             // The head app isn't supposed to get full-data backups [any more];
                             // so we cull it and force a loop around to consider the new head
diff --git a/services/backup/java/com/android/server/backup/fullbackup/PerformAdbBackupTask.java b/services/backup/java/com/android/server/backup/fullbackup/PerformAdbBackupTask.java
index 31786d7..0a7159b 100644
--- a/services/backup/java/com/android/server/backup/fullbackup/PerformAdbBackupTask.java
+++ b/services/backup/java/com/android/server/backup/fullbackup/PerformAdbBackupTask.java
@@ -286,7 +286,8 @@
         Iterator<Entry<String, PackageInfo>> iter = packagesToBackup.entrySet().iterator();
         while (iter.hasNext()) {
             PackageInfo pkg = iter.next().getValue();
-            if (!AppBackupUtils.appIsEligibleForBackup(pkg.applicationInfo, pm)
+            if (!AppBackupUtils.appIsEligibleForBackup(pkg.applicationInfo,
+                    mUserBackupManagerService.getUserId())
                     || AppBackupUtils.appIsStopped(pkg.applicationInfo)) {
                 iter.remove();
                 if (DEBUG) {
diff --git a/services/backup/java/com/android/server/backup/fullbackup/PerformFullTransportBackupTask.java b/services/backup/java/com/android/server/backup/fullbackup/PerformFullTransportBackupTask.java
index 0fb4f93..86e679f 100644
--- a/services/backup/java/com/android/server/backup/fullbackup/PerformFullTransportBackupTask.java
+++ b/services/backup/java/com/android/server/backup/fullbackup/PerformFullTransportBackupTask.java
@@ -143,6 +143,7 @@
     private final int mBackupRunnerOpToken;
     private final OnTaskFinishedListener mListener;
     private final TransportClient mTransportClient;
+    private final int mUserId;
 
     // This is true when a backup operation for some package is in progress.
     private volatile boolean mIsDoingBackup;
@@ -173,6 +174,7 @@
         mAgentTimeoutParameters = Preconditions.checkNotNull(
                 backupManagerService.getAgentTimeoutParameters(),
                 "Timeout parameters cannot be null");
+        mUserId = backupManagerService.getUserId();
 
         if (backupManagerService.isBackupOperationInProgress()) {
             if (DEBUG) {
@@ -187,9 +189,10 @@
         for (String pkg : whichPackages) {
             try {
                 PackageManager pm = backupManagerService.getPackageManager();
-                PackageInfo info = pm.getPackageInfo(pkg, PackageManager.GET_SIGNING_CERTIFICATES);
+                PackageInfo info = pm.getPackageInfoAsUser(pkg,
+                        PackageManager.GET_SIGNING_CERTIFICATES, mUserId);
                 mCurrentPackage = info;
-                if (!AppBackupUtils.appIsEligibleForBackup(info.applicationInfo, pm)) {
+                if (!AppBackupUtils.appIsEligibleForBackup(info.applicationInfo, mUserId)) {
                     // Cull any packages that have indicated that backups are not permitted,
                     // that run as system-domain uids but do not define their own backup agents,
                     // as well as any explicit mention of the 'special' shared-storage agent
@@ -633,7 +636,7 @@
             unregisterTask();
 
             if (mJob != null) {
-                mJob.finishBackupPass(backupManagerService.getUserId());
+                mJob.finishBackupPass(mUserId);
             }
 
             synchronized (backupManagerService.getQueueLock()) {
diff --git a/services/backup/java/com/android/server/backup/keyvalue/KeyValueBackupTask.java b/services/backup/java/com/android/server/backup/keyvalue/KeyValueBackupTask.java
index cfc129e..294eb01 100644
--- a/services/backup/java/com/android/server/backup/keyvalue/KeyValueBackupTask.java
+++ b/services/backup/java/com/android/server/backup/keyvalue/KeyValueBackupTask.java
@@ -489,7 +489,7 @@
             throw AgentException.permanent(e);
         }
         ApplicationInfo applicationInfo = packageInfo.applicationInfo;
-        if (!AppBackupUtils.appIsEligibleForBackup(applicationInfo, mPackageManager)) {
+        if (!AppBackupUtils.appIsEligibleForBackup(applicationInfo, mUserId)) {
             mReporter.onPackageNotEligibleForBackup(packageName);
             throw AgentException.permanent();
         }
diff --git a/services/backup/java/com/android/server/backup/restore/FullRestoreEngine.java b/services/backup/java/com/android/server/backup/restore/FullRestoreEngine.java
index b3d9fbc..c5389fa 100644
--- a/services/backup/java/com/android/server/backup/restore/FullRestoreEngine.java
+++ b/services/backup/java/com/android/server/backup/restore/FullRestoreEngine.java
@@ -230,7 +230,7 @@
                             PackageManagerInternal.class);
                     RestorePolicy restorePolicy = tarBackupReader.chooseRestorePolicy(
                             mBackupManagerService.getPackageManager(), allowApks, info, signatures,
-                            pmi);
+                            pmi, mUserId);
                     mManifestSignatures.put(info.packageName, signatures);
                     mPackagePolicies.put(pkg, restorePolicy);
                     mPackageInstallers.put(pkg, info.installerPackageName);
@@ -332,8 +332,9 @@
                         }
 
                         try {
-                            mTargetApp = mBackupManagerService.getPackageManager()
-                                    .getApplicationInfoAsUser(pkg, 0, mUserId);
+                            mTargetApp =
+                                    mBackupManagerService.getPackageManager()
+                                            .getApplicationInfoAsUser(pkg, 0, mUserId);
 
                             // If we haven't sent any data to this app yet, we probably
                             // need to clear it first. Check that.
diff --git a/services/backup/java/com/android/server/backup/restore/PerformUnifiedRestoreTask.java b/services/backup/java/com/android/server/backup/restore/PerformUnifiedRestoreTask.java
index d01f77b..7763d7b 100644
--- a/services/backup/java/com/android/server/backup/restore/PerformUnifiedRestoreTask.java
+++ b/services/backup/java/com/android/server/backup/restore/PerformUnifiedRestoreTask.java
@@ -232,7 +232,7 @@
                         continue;
                     }
 
-                    if (AppBackupUtils.appIsEligibleForBackup(info.applicationInfo, pm)) {
+                    if (AppBackupUtils.appIsEligibleForBackup(info.applicationInfo, mUserId)) {
                         mAcceptSet.add(info);
                     }
                 } catch (NameNotFoundException e) {
diff --git a/services/backup/java/com/android/server/backup/utils/AppBackupUtils.java b/services/backup/java/com/android/server/backup/utils/AppBackupUtils.java
index 054879b..2db8928 100644
--- a/services/backup/java/com/android/server/backup/utils/AppBackupUtils.java
+++ b/services/backup/java/com/android/server/backup/utils/AppBackupUtils.java
@@ -18,19 +18,25 @@
 
 import static com.android.server.backup.BackupManagerService.MORE_DEBUG;
 import static com.android.server.backup.BackupManagerService.TAG;
+import static com.android.server.backup.UserBackupManagerService.PACKAGE_MANAGER_SENTINEL;
 import static com.android.server.backup.UserBackupManagerService.SHARED_BACKUP_AGENT_PACKAGE;
 
 import android.annotation.Nullable;
+import android.app.AppGlobals;
 import android.app.backup.BackupTransport;
 import android.content.pm.ApplicationInfo;
+import android.content.pm.IPackageManager;
 import android.content.pm.PackageInfo;
 import android.content.pm.PackageManager;
 import android.content.pm.PackageManagerInternal;
 import android.content.pm.Signature;
 import android.content.pm.SigningInfo;
 import android.os.Process;
+import android.os.RemoteException;
+import android.os.UserHandle;
 import android.util.Slog;
 
+import com.android.internal.annotations.VisibleForTesting;
 import com.android.internal.backup.IBackupTransport;
 import com.android.internal.util.ArrayUtils;
 import com.android.server.backup.transport.TransportClient;
@@ -39,7 +45,6 @@
  * Utility methods wrapping operations on ApplicationInfo and PackageInfo.
  */
 public class AppBackupUtils {
-
     private static final boolean DEBUG = false;
 
     /**
@@ -54,15 +59,30 @@
      *     <li>it is the special shared-storage backup package used for 'adb backup'
      * </ol>
      */
-    public static boolean appIsEligibleForBackup(ApplicationInfo app, PackageManager pm) {
+    public static boolean appIsEligibleForBackup(ApplicationInfo app, int userId) {
+        return appIsEligibleForBackup(app, AppGlobals.getPackageManager(), userId);
+    }
+
+    @VisibleForTesting
+    static boolean appIsEligibleForBackup(ApplicationInfo app,
+        IPackageManager packageManager, int userId) {
         // 1. their manifest states android:allowBackup="false"
         if ((app.flags & ApplicationInfo.FLAG_ALLOW_BACKUP) == 0) {
             return false;
         }
 
-        // 2. they run as a system-level uid but do not supply their own backup agent
-        if ((app.uid < Process.FIRST_APPLICATION_UID) && (app.backupAgentName == null)) {
-            return false;
+        // 2. they run as a system-level uid
+        if ((app.uid < Process.FIRST_APPLICATION_UID)) {
+            // and the backup is happening for non-system user
+            if (userId != UserHandle.USER_SYSTEM && !app.packageName.equals(
+                    PACKAGE_MANAGER_SENTINEL)) {
+                return false;
+            }
+
+            // or do not supply their own backup agent
+            if (app.backupAgentName == null) {
+                return false;
+            }
         }
 
         // 3. it is the special shared-storage backup package used for 'adb backup'
@@ -75,9 +95,7 @@
             return false;
         }
 
-        // Everything else checks out; the only remaining roadblock would be if the
-        // package were disabled
-        return !appIsDisabled(app, pm);
+        return !appIsDisabled(app, packageManager, userId);
     }
 
     /**
@@ -99,9 +117,9 @@
             PackageInfo packageInfo = pm.getPackageInfoAsUser(packageName,
                     PackageManager.GET_SIGNING_CERTIFICATES, userId);
             ApplicationInfo applicationInfo = packageInfo.applicationInfo;
-            if (!appIsEligibleForBackup(applicationInfo, pm)
+            if (!appIsEligibleForBackup(applicationInfo, userId)
                     || appIsStopped(applicationInfo)
-                    || appIsDisabled(applicationInfo, pm)) {
+                    || appIsDisabled(applicationInfo, userId)) {
                 return false;
             }
             if (transportClient != null) {
@@ -123,8 +141,22 @@
     }
 
     /** Avoid backups of 'disabled' apps. */
-    public static boolean appIsDisabled(ApplicationInfo app, PackageManager pm) {
-        switch (pm.getApplicationEnabledSetting(app.packageName)) {
+    static boolean appIsDisabled(ApplicationInfo app, int userId) {
+        return appIsDisabled(app, AppGlobals.getPackageManager(), userId);
+    }
+
+    @VisibleForTesting
+    static boolean appIsDisabled(ApplicationInfo app,
+        IPackageManager packageManager, int userId) {
+        int enabledSetting;
+        try {
+            enabledSetting = packageManager.getApplicationEnabledSetting(app.packageName, userId);
+        } catch (RemoteException e) {
+            Slog.e(TAG, "Failed to get application enabled setting: " + e);
+            return false;
+        }
+
+        switch (enabledSetting) {
             case PackageManager.COMPONENT_ENABLED_STATE_DISABLED:
             case PackageManager.COMPONENT_ENABLED_STATE_DISABLED_USER:
             case PackageManager.COMPONENT_ENABLED_STATE_DISABLED_UNTIL_USED:
diff --git a/services/backup/java/com/android/server/backup/utils/TarBackupReader.java b/services/backup/java/com/android/server/backup/utils/TarBackupReader.java
index 0f4b681..f4b235a 100644
--- a/services/backup/java/com/android/server/backup/utils/TarBackupReader.java
+++ b/services/backup/java/com/android/server/backup/utils/TarBackupReader.java
@@ -383,11 +383,12 @@
      * @param allowApks - allow restore set to include apks.
      * @param info - file metadata.
      * @param signatures - array of signatures parsed from backup file.
+     * @param userId - ID of the user for which restore is performed.
      * @return a restore policy constant.
      */
     public RestorePolicy chooseRestorePolicy(PackageManager packageManager,
             boolean allowApks, FileMetadata info, Signature[] signatures,
-            PackageManagerInternal pmi) {
+            PackageManagerInternal pmi, int userId) {
         if (signatures == null) {
             return RestorePolicy.IGNORE;
         }
@@ -396,8 +397,8 @@
 
         // Okay, got the manifest info we need...
         try {
-            PackageInfo pkgInfo = packageManager.getPackageInfo(
-                    info.packageName, PackageManager.GET_SIGNING_CERTIFICATES);
+            PackageInfo pkgInfo = packageManager.getPackageInfoAsUser(
+                    info.packageName, PackageManager.GET_SIGNING_CERTIFICATES, userId);
             // Fall through to IGNORE if the app explicitly disallows backup
             final int flags = pkgInfo.applicationInfo.flags;
             if ((flags & ApplicationInfo.FLAG_ALLOW_BACKUP) != 0) {
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 0fa996e..e3dcb7d 100644
--- a/services/core/java/com/android/server/AlarmManagerService.java
+++ b/services/core/java/com/android/server/AlarmManagerService.java
@@ -64,6 +64,7 @@
 import android.os.ShellCommand;
 import android.os.SystemClock;
 import android.os.SystemProperties;
+import android.os.ThreadLocalWorkSource;
 import android.os.Trace;
 import android.os.UserHandle;
 import android.os.WorkSource;
@@ -76,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;
@@ -90,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;
@@ -144,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;
@@ -194,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;
@@ -276,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
@@ -301,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 = {
@@ -318,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
@@ -347,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(',');
@@ -408,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) {
@@ -924,7 +1081,7 @@
                 if (targetPackages != null && !targetPackages.contains(packageUser)) {
                     continue;
                 }
-                if (adjustDeliveryTimeBasedOnStandbyBucketLocked(alarm)) {
+                if (adjustDeliveryTimeBasedOnBucketLocked(alarm)) {
                     batch.remove(alarm);
                     rescheduledAlarms.add(alarm);
                 }
@@ -1131,23 +1288,23 @@
         final IBinder mListener;
         final WorkSource mWorkSource;
         final int mUid;
+        final int mCreatorUid;
         final String mTag;
         final BroadcastStats mBroadcastStats;
         final FilterStats mFilterStats;
         final int mAlarmType;
 
-        InFlight(AlarmManagerService service, PendingIntent pendingIntent, IAlarmListener listener,
-                WorkSource workSource, int uid, String alarmPkg, int alarmType, String tag,
-                long nowELAPSED) {
-            mPendingIntent = pendingIntent;
+        InFlight(AlarmManagerService service, Alarm alarm, long nowELAPSED) {
+            mPendingIntent = alarm.operation;
             mWhenElapsed = nowELAPSED;
-            mListener = listener != null ? listener.asBinder() : null;
-            mWorkSource = workSource;
-            mUid = uid;
-            mTag = tag;
-            mBroadcastStats = (pendingIntent != null)
-                    ? service.getStatsLocked(pendingIntent)
-                    : service.getStatsLocked(uid, alarmPkg);
+            mListener = alarm.listener != null ? alarm.listener.asBinder() : null;
+            mWorkSource = alarm.workSource;
+            mUid = alarm.uid;
+            mCreatorUid = alarm.creatorUid;
+            mTag = alarm.statsTag;
+            mBroadcastStats = (alarm.operation != null)
+                    ? service.getStatsLocked(alarm.operation)
+                    : service.getStatsLocked(alarm.uid, alarm.packageName);
             FilterStats fs = mBroadcastStats.filterStats.get(mTag);
             if (fs == null) {
                 fs = new FilterStats(mBroadcastStats, mTag);
@@ -1155,7 +1312,7 @@
             }
             fs.lastTime = nowELAPSED;
             mFilterStats = fs;
-            mAlarmType = alarmType;
+            mAlarmType = alarm.type;
         }
 
         @Override
@@ -1165,6 +1322,7 @@
                     + ", when=" + mWhenElapsed
                     + ", workSource=" + mWorkSource
                     + ", uid=" + mUid
+                    + ", creatorUid=" + mCreatorUid
                     + ", tag=" + mTag
                     + ", broadcastStats=" + mBroadcastStats
                     + ", filterStats=" + mFilterStats
@@ -1298,6 +1456,7 @@
         synchronized (mLock) {
             mHandler = new AlarmHandler();
             mConstants = new Constants(mHandler);
+            mAppWakeupHistory = new AppWakeupHistory(Constants.DEFAULT_APP_STANDBY_WINDOW);
 
             mNextWakeup = mNextNonWakeup = 0;
 
@@ -1581,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
      */
@@ -1606,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;
         }
@@ -1627,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);
     }
@@ -1694,7 +1905,7 @@
                 mAllowWhileIdleDispatches.add(ent);
             }
         }
-        adjustDeliveryTimeBasedOnStandbyBucketLocked(a);
+        adjustDeliveryTimeBasedOnBucketLocked(a);
         insertAndBatchAlarmLocked(a);
 
         if (a.alarmClock != null) {
@@ -1913,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) {
@@ -2063,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();
@@ -3811,12 +4015,10 @@
 
     /**
      * Attribute blame for a WakeLock.
-     * @param pi PendingIntent to attribute blame to if ws is null.
      * @param ws WorkSource to attribute blame.
-     * @param knownUid attribution uid; < 0 if we need to derive it from the PendingIntent sender
+     * @param knownUid attribution uid; < 0 values are ignored.
      */
-    void setWakelockWorkSource(PendingIntent pi, WorkSource ws, int type, String tag,
-            int knownUid, boolean first) {
+    void setWakelockWorkSource(WorkSource ws, int knownUid, String tag, boolean first) {
         try {
             mWakeLock.setHistoryTag(first ? tag : null);
 
@@ -3825,11 +4027,8 @@
                 return;
             }
 
-            final int uid = (knownUid >= 0)
-                    ? knownUid
-                    : ActivityManager.getService().getUidForIntentSender(pi.getTarget());
-            if (uid >= 0) {
-                mWakeLock.setWorkSource(new WorkSource(uid));
+            if (knownUid >= 0) {
+                mWakeLock.setWorkSource(new WorkSource(knownUid));
                 return;
             }
         } catch (Exception e) {
@@ -3839,6 +4038,14 @@
         mWakeLock.setWorkSource(null);
     }
 
+    private static int getAlarmAttributionUid(Alarm alarm) {
+        if (alarm.workSource != null && !alarm.workSource.isEmpty()) {
+            return alarm.workSource.getAttributionUid();
+        }
+
+        return alarm.creatorUid;
+    }
+
     @VisibleForTesting
     class AlarmHandler extends Handler {
         public static final int ALARM_EVENT = 1;
@@ -3857,6 +4064,7 @@
             obtainMessage(REMOVE_FOR_STOPPED, uid, 0).sendToTarget();
         }
 
+        @Override
         public void handleMessage(Message msg) {
             switch (msg.what) {
                 case ALARM_EVENT: {
@@ -4025,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);
@@ -4126,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) {
@@ -4285,8 +4487,8 @@
                 // the next of our alarms is now in flight.  reattribute the wakelock.
                 if (mInFlight.size() > 0) {
                     InFlight inFlight = mInFlight.get(0);
-                    setWakelockWorkSource(inFlight.mPendingIntent, inFlight.mWorkSource,
-                            inFlight.mAlarmType, inFlight.mTag, -1, false);
+                    setWakelockWorkSource(inFlight.mWorkSource, inFlight.mCreatorUid, inFlight.mTag,
+                            false);
                 } else {
                     // should never happen
                     mLog.w("Alarm wakelock still held but sent queue empty");
@@ -4369,64 +4571,70 @@
          */
         @GuardedBy("mLock")
         public void deliverLocked(Alarm alarm, long nowELAPSED, boolean allowWhileIdle) {
-            if (alarm.operation != null) {
-                // PendingIntent alarm
-                mSendCount++;
+            final long workSourceToken = ThreadLocalWorkSource.setUid(
+                    getAlarmAttributionUid(alarm));
+            try {
+                if (alarm.operation != null) {
+                    // PendingIntent alarm
+                    mSendCount++;
 
-                try {
-                    alarm.operation.send(getContext(), 0,
-                            mBackgroundIntent.putExtra(
-                                    Intent.EXTRA_ALARM_COUNT, alarm.count),
-                                    mDeliveryTracker, mHandler, null,
-                                    allowWhileIdle ? mIdleOptions : null);
-                } catch (PendingIntent.CanceledException e) {
-                    if (alarm.repeatInterval > 0) {
-                        // This IntentSender is no longer valid, but this
-                        // is a repeating alarm, so toss it
-                        removeImpl(alarm.operation, null);
+                    try {
+                        alarm.operation.send(getContext(), 0,
+                                mBackgroundIntent.putExtra(
+                                        Intent.EXTRA_ALARM_COUNT, alarm.count),
+                                mDeliveryTracker, mHandler, null,
+                                allowWhileIdle ? mIdleOptions : null);
+                    } catch (PendingIntent.CanceledException e) {
+                        if (alarm.repeatInterval > 0) {
+                            // This IntentSender is no longer valid, but this
+                            // is a repeating alarm, so toss it
+                            removeImpl(alarm.operation, null);
+                        }
+                        // No actual delivery was possible, so the delivery tracker's
+                        // 'finished' callback won't be invoked.  We also don't need
+                        // to do any wakelock or stats tracking, so we have nothing
+                        // left to do here but go on to the next thing.
+                        mSendFinishCount++;
+                        return;
                     }
-                    // No actual delivery was possible, so the delivery tracker's
-                    // 'finished' callback won't be invoked.  We also don't need
-                    // to do any wakelock or stats tracking, so we have nothing
-                    // left to do here but go on to the next thing.
-                    mSendFinishCount++;
-                    return;
-                }
-            } else {
-                // Direct listener callback alarm
-                mListenerCount++;
+                } else {
+                    // Direct listener callback alarm
+                    mListenerCount++;
 
-                if (RECORD_ALARMS_IN_HISTORY) {
-                    if (alarm.listener == mTimeTickTrigger) {
-                        mTickHistory[mNextTickHistory++] = nowELAPSED;
-                        if (mNextTickHistory >= TICK_HISTORY_DEPTH) {
-                            mNextTickHistory = 0;
+                    if (RECORD_ALARMS_IN_HISTORY) {
+                        if (alarm.listener == mTimeTickTrigger) {
+                            mTickHistory[mNextTickHistory++] = nowELAPSED;
+                            if (mNextTickHistory >= TICK_HISTORY_DEPTH) {
+                                mNextTickHistory = 0;
+                            }
                         }
                     }
-                }
 
-                try {
-                    if (DEBUG_LISTENER_CALLBACK) {
-                        Slog.v(TAG, "Alarm to uid=" + alarm.uid
-                                + " listener=" + alarm.listener.asBinder());
+                    try {
+                        if (DEBUG_LISTENER_CALLBACK) {
+                            Slog.v(TAG, "Alarm to uid=" + alarm.uid
+                                    + " listener=" + alarm.listener.asBinder());
+                        }
+                        alarm.listener.doAlarm(this);
+                        mHandler.sendMessageDelayed(
+                                mHandler.obtainMessage(AlarmHandler.LISTENER_TIMEOUT,
+                                        alarm.listener.asBinder()),
+                                mConstants.LISTENER_TIMEOUT);
+                    } catch (Exception e) {
+                        if (DEBUG_LISTENER_CALLBACK) {
+                            Slog.i(TAG, "Alarm undeliverable to listener "
+                                    + alarm.listener.asBinder(), e);
+                        }
+                        // As in the PendingIntent.CanceledException case, delivery of the
+                        // alarm was not possible, so we have no wakelock or timeout or
+                        // stats management to do.  It threw before we posted the delayed
+                        // timeout message, so we're done here.
+                        mListenerFinishCount++;
+                        return;
                     }
-                    alarm.listener.doAlarm(this);
-                    mHandler.sendMessageDelayed(
-                            mHandler.obtainMessage(AlarmHandler.LISTENER_TIMEOUT,
-                                    alarm.listener.asBinder()),
-                            mConstants.LISTENER_TIMEOUT);
-                } catch (Exception e) {
-                    if (DEBUG_LISTENER_CALLBACK) {
-                        Slog.i(TAG, "Alarm undeliverable to listener "
-                                + alarm.listener.asBinder(), e);
-                    }
-                    // As in the PendingIntent.CanceledException case, delivery of the
-                    // alarm was not possible, so we have no wakelock or timeout or
-                    // stats management to do.  It threw before we posted the delayed
-                    // timeout message, so we're done here.
-                    mListenerFinishCount++;
-                    return;
                 }
+            } finally {
+                ThreadLocalWorkSource.restore(workSourceToken);
             }
 
             // The alarm is now in flight; now arrange wakelock and stats tracking
@@ -4434,15 +4642,11 @@
                 Slog.d(TAG, "mBroadcastRefCount -> " + (mBroadcastRefCount + 1));
             }
             if (mBroadcastRefCount == 0) {
-                setWakelockWorkSource(alarm.operation, alarm.workSource,
-                        alarm.type, alarm.statsTag, (alarm.operation == null) ? alarm.uid : -1,
-                        true);
+                setWakelockWorkSource(alarm.workSource, alarm.creatorUid, alarm.statsTag, true);
                 mWakeLock.acquire();
                 mHandler.obtainMessage(AlarmHandler.REPORT_ALARMS_ACTIVE, 1).sendToTarget();
             }
-            final InFlight inflight = new InFlight(AlarmManagerService.this,
-                    alarm.operation, alarm.listener, alarm.workSource, alarm.uid,
-                    alarm.packageName, alarm.type, alarm.statsTag, nowELAPSED);
+            final InFlight inflight = new InFlight(AlarmManagerService.this, alarm, nowELAPSED);
             mInFlight.add(inflight);
             mBroadcastRefCount++;
             if (allowWhileIdle) {
@@ -4467,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 00550d9..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.util.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");
@@ -2640,8 +2674,7 @@
     }
 
     private boolean networkRequiresValidation(NetworkAgentInfo nai) {
-        return isValidationRequired(
-                mDefaultRequest.networkCapabilities, nai.networkCapabilities);
+        return isValidationRequired(nai.networkCapabilities);
     }
 
     private void handleFreshlyValidatedNetwork(NetworkAgentInfo nai) {
@@ -3183,6 +3216,15 @@
         return mMultinetworkPolicyTracker.getAvoidBadWifi();
     }
 
+    @Override
+    public boolean getAvoidBadWifi() {
+        if (!checkNetworkStackPermission()) {
+            throw new SecurityException("avoidBadWifi requires NETWORK_STACK permission");
+        }
+        return avoidBadWifi();
+    }
+
+
     private void rematchForAvoidBadWifiUpdate() {
         rematchAllNetworksAndRequests(null, 0);
         for (NetworkAgentInfo nai: mNetworkAgentInfos.values()) {
@@ -3229,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;
@@ -3244,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);
@@ -3265,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) {
@@ -3274,7 +3324,7 @@
 
         if (nc.hasTransport(NetworkCapabilities.TRANSPORT_WIFI) &&
             mMultinetworkPolicyTracker.shouldNotifyWifiUnvalidated()) {
-            showValidationNotification(nai, NotificationType.LOST_INTERNET);
+            showNetworkNotification(nai, NotificationType.LOST_INTERNET);
         }
     }
 
@@ -3420,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;
             }
         }
     }
@@ -3677,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);
         }
     }
 
@@ -3714,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();
@@ -5885,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
@@ -5909,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
@@ -6177,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));
@@ -6346,6 +6435,20 @@
         }
     }
 
+    @GuardedBy("mVpns")
+    private Vpn getVpnIfOwner() {
+        final int uid = Binder.getCallingUid();
+        final int user = UserHandle.getUserId(uid);
+
+        final Vpn vpn = mVpns.get(user);
+        if (vpn == null) {
+            return null;
+        } else {
+            final VpnInfo info = vpn.getVpnInfo();
+            return (info == null || info.ownerUid != uid) ? null : vpn;
+        }
+    }
+
     /**
      * Caller either needs to be an active VPN, or hold the NETWORK_STACK permission
      * for testing.
@@ -6354,14 +6457,10 @@
         if (checkNetworkStackPermission()) {
             return null;
         }
-        final int uid = Binder.getCallingUid();
-        final int user = UserHandle.getUserId(uid);
         synchronized (mVpns) {
-            Vpn vpn = mVpns.get(user);
-            try {
-                if (vpn.getVpnInfo().ownerUid == uid) return vpn;
-            } catch (NullPointerException e) {
-                /* vpn is null, or VPN is not connected and getVpnInfo() is null. */
+            Vpn vpn = getVpnIfOwner();
+            if (vpn != null) {
+                return vpn;
             }
         }
         throw new SecurityException("App must either be an active VPN or have the NETWORK_STACK "
@@ -6390,4 +6489,20 @@
 
         return uid;
     }
+
+    @Override
+    public boolean isCallerCurrentAlwaysOnVpnApp() {
+        synchronized (mVpns) {
+            Vpn vpn = getVpnIfOwner();
+            return vpn != null && vpn.getAlwaysOn();
+        }
+    }
+
+    @Override
+    public boolean isCallerCurrentAlwaysOnVpnLockdownApp() {
+        synchronized (mVpns) {
+            Vpn vpn = getVpnIfOwner();
+            return vpn != null && vpn.getLockdown();
+        }
+    }
 }
diff --git a/services/core/java/com/android/server/DeviceIdleController.java b/services/core/java/com/android/server/DeviceIdleController.java
index 121a830..39030aa 100644
--- a/services/core/java/com/android/server/DeviceIdleController.java
+++ b/services/core/java/com/android/server/DeviceIdleController.java
@@ -341,6 +341,29 @@
     @VisibleForTesting
     static final int STATE_QUICK_DOZE_DELAY = 7;
 
+    private static final int ACTIVE_REASON_UNKNOWN = 0;
+    private static final int ACTIVE_REASON_MOTION = 1;
+    private static final int ACTIVE_REASON_SCREEN = 2;
+    private static final int ACTIVE_REASON_CHARGING = 3;
+    private static final int ACTIVE_REASON_UNLOCKED = 4;
+    private static final int ACTIVE_REASON_FROM_BINDER_CALL = 5;
+    private static final int ACTIVE_REASON_FORCED = 6;
+    private static final int ACTIVE_REASON_ALARM = 7;
+    @VisibleForTesting
+    static final int SET_IDLE_FACTOR_RESULT_UNINIT = -1;
+    @VisibleForTesting
+    static final int SET_IDLE_FACTOR_RESULT_IGNORED = 0;
+    @VisibleForTesting
+    static final int SET_IDLE_FACTOR_RESULT_OK = 1;
+    @VisibleForTesting
+    static final int SET_IDLE_FACTOR_RESULT_NOT_SUPPORT = 2;
+    @VisibleForTesting
+    static final int SET_IDLE_FACTOR_RESULT_INVALID = 3;
+    @VisibleForTesting
+    static final long MIN_STATE_STEP_ALARM_CHANGE = 60 * 1000;
+    @VisibleForTesting
+    static final float MIN_PRE_IDLE_FACTOR_CHANGE = 0.05f;
+
     @VisibleForTesting
     static String stateToString(int state) {
         switch (state) {
@@ -405,6 +428,7 @@
     private long mNextSensingTimeoutAlarmTime;
     private long mCurIdleBudget;
     private long mMaintenanceStartTime;
+    private long mIdleStartTime;
 
     private int mActiveIdleOpCount;
     private PowerManager.WakeLock mActiveIdleWakeLock; // held when there are operations in progress
@@ -415,6 +439,17 @@
     private boolean mAlarmsActive;
     private boolean mReportedMaintenanceActivity;
 
+    /* Factor to apply to INACTIVE_TIMEOUT and IDLE_AFTER_INACTIVE_TIMEOUT in order to enter
+     * STATE_IDLE faster or slower. Don't apply this to SENSING_TIMEOUT or LOCATING_TIMEOUT because:
+     *   - Both of them are shorter
+     *   - Device sensor might take time be to become be stabilized
+     * Also don't apply the factor if the device is in motion because device motion provides a
+     * stronger signal than a prediction algorithm.
+     */
+    private float mPreIdleFactor;
+    private float mLastPreIdleFactor;
+    private int mActiveReason;
+
     public final AtomicFile mConfigFile;
 
     private final RemoteCallbackList<IMaintenanceActivityListener> mMaintenanceActivityListeners =
@@ -760,6 +795,10 @@
          * exit doze. Default = true
          */
         private static final String KEY_WAIT_FOR_UNLOCK = "wait_for_unlock";
+        private static final String KEY_PRE_IDLE_FACTOR_LONG =
+                "pre_idle_factor_long";
+        private static final String KEY_PRE_IDLE_FACTOR_SHORT =
+                "pre_idle_factor_short";
 
         /**
          * This is the time, after becoming inactive, that we go in to the first
@@ -987,6 +1026,16 @@
          */
         public long NOTIFICATION_WHITELIST_DURATION;
 
+        /**
+         * Pre idle time factor use to make idle delay longer
+         */
+        public float PRE_IDLE_FACTOR_LONG;
+
+        /**
+         * Pre idle time factor use to make idle delay shorter
+         */
+        public float PRE_IDLE_FACTOR_SHORT;
+
         public boolean WAIT_FOR_UNLOCK;
 
         private final ContentResolver mResolver;
@@ -1082,6 +1131,8 @@
                 NOTIFICATION_WHITELIST_DURATION = mParser.getDurationMillis(
                         KEY_NOTIFICATION_WHITELIST_DURATION, 30 * 1000L);
                 WAIT_FOR_UNLOCK = mParser.getBoolean(KEY_WAIT_FOR_UNLOCK, false);
+                PRE_IDLE_FACTOR_LONG = mParser.getFloat(KEY_PRE_IDLE_FACTOR_LONG, 1.67f);
+                PRE_IDLE_FACTOR_SHORT = mParser.getFloat(KEY_PRE_IDLE_FACTOR_SHORT, 0.33f);
             }
         }
 
@@ -1196,6 +1247,12 @@
 
             pw.print("    "); pw.print(KEY_WAIT_FOR_UNLOCK); pw.print("=");
             pw.println(WAIT_FOR_UNLOCK);
+
+            pw.print("    "); pw.print(KEY_PRE_IDLE_FACTOR_LONG); pw.print("=");
+            pw.println(PRE_IDLE_FACTOR_LONG);
+
+            pw.print("    "); pw.print(KEY_PRE_IDLE_FACTOR_SHORT); pw.print("=");
+            pw.println(PRE_IDLE_FACTOR_SHORT);
         }
     }
 
@@ -1244,6 +1301,8 @@
     private static final int MSG_FINISH_IDLE_OP = 8;
     private static final int MSG_REPORT_TEMP_APP_WHITELIST_CHANGED = 9;
     private static final int MSG_SEND_CONSTRAINT_MONITORING = 10;
+    private static final int MSG_UPDATE_PRE_IDLE_TIMEOUT_FACTOR = 11;
+    private static final int MSG_RESET_PRE_IDLE_TIMEOUT_FACTOR = 12;
 
     final class MyHandler extends Handler {
         MyHandler(Looper looper) {
@@ -1373,6 +1432,13 @@
                         constraint.stopMonitoring();
                     }
                 } break;
+                case MSG_UPDATE_PRE_IDLE_TIMEOUT_FACTOR: {
+                    updatePreIdleFactor();
+                } break;
+                case MSG_RESET_PRE_IDLE_TIMEOUT_FACTOR: {
+                    updatePreIdleFactor();
+                    maybeDoImmediateMaintenance();
+                } break;
             }
         }
     }
@@ -1526,6 +1592,28 @@
             DeviceIdleController.this.unregisterMaintenanceActivityListener(listener);
         }
 
+        @Override public int setPreIdleTimeoutMode(int mode) {
+            getContext().enforceCallingOrSelfPermission(Manifest.permission.DEVICE_POWER,
+                    null);
+            long ident = Binder.clearCallingIdentity();
+            try {
+                return DeviceIdleController.this.setPreIdleTimeoutMode(mode);
+            } finally {
+                Binder.restoreCallingIdentity(ident);
+            }
+        }
+
+        @Override public void resetPreIdleTimeoutMode() {
+            getContext().enforceCallingOrSelfPermission(Manifest.permission.DEVICE_POWER,
+                    null);
+            long ident = Binder.clearCallingIdentity();
+            try {
+                DeviceIdleController.this.resetPreIdleTimeoutMode();
+            } finally {
+                Binder.restoreCallingIdentity(ident);
+            }
+        }
+
         @Override protected void dump(FileDescriptor fd, PrintWriter pw, String[] args) {
             DeviceIdleController.this.dump(fd, pw, args);
         }
@@ -1768,9 +1856,12 @@
             // Start out assuming we are charging.  If we aren't, we will at least get
             // a battery update the next time the level drops.
             mCharging = true;
+            mActiveReason = ACTIVE_REASON_UNKNOWN;
             mState = STATE_ACTIVE;
             mLightState = LIGHT_STATE_ACTIVE;
             mInactiveTimeout = mConstants.INACTIVE_TIMEOUT;
+            mPreIdleFactor = 1.0f;
+            mLastPreIdleFactor = 1.0f;
         }
 
         mBinderService = new BinderService();
@@ -2394,6 +2485,7 @@
 
     public void exitIdleInternal(String reason) {
         synchronized (this) {
+            mActiveReason = ACTIVE_REASON_FROM_BINDER_CALL;
             becomeActiveLocked(reason, Binder.getCallingUid());
         }
     }
@@ -2463,6 +2555,7 @@
         } else if (screenOn) {
             mScreenOn = true;
             if (!mForceIdle && (!mScreenLocked || !mConstants.WAIT_FOR_UNLOCK)) {
+                mActiveReason = ACTIVE_REASON_SCREEN;
                 becomeActiveLocked("screen", Process.myUid());
             }
         }
@@ -2485,6 +2578,7 @@
         } else if (charging) {
             mCharging = charging;
             if (!mForceIdle) {
+                mActiveReason = ACTIVE_REASON_CHARGING;
                 becomeActiveLocked("charging", Process.myUid());
             }
         }
@@ -2516,6 +2610,7 @@
         if (mScreenLocked != showing) {
             mScreenLocked = showing;
             if (mScreenOn && !mForceIdle && !mScreenLocked) {
+                mActiveReason = ACTIVE_REASON_UNLOCKED;
                 becomeActiveLocked("unlocked", Process.myUid());
             }
         }
@@ -2587,7 +2682,11 @@
                     mState = STATE_INACTIVE;
                     if (DEBUG) Slog.d(TAG, "Moved from STATE_ACTIVE to STATE_INACTIVE");
                     resetIdleManagementLocked();
-                    scheduleAlarmLocked(mInactiveTimeout, false);
+                    long delay = mInactiveTimeout;
+                    if (shouldUseIdleTimeoutFactorLocked()) {
+                        delay = (long) (mPreIdleFactor * delay);
+                    }
+                    scheduleAlarmLocked(delay, false);
                     EventLogTags.writeDeviceIdle(mState, "no activity");
                 }
             }
@@ -2605,6 +2704,7 @@
         mNextIdlePendingDelay = 0;
         mNextIdleDelay = 0;
         mNextLightIdleDelay = 0;
+        mIdleStartTime = 0;
         cancelAlarmLocked();
         cancelSensingTimeoutAlarmLocked();
         cancelLocatingLocked();
@@ -2621,6 +2721,7 @@
         if (mForceIdle) {
             mForceIdle = false;
             if (mScreenOn || mCharging) {
+                mActiveReason = ACTIVE_REASON_FORCED;
                 becomeActiveLocked("exit-force", Process.myUid());
             }
         }
@@ -2740,6 +2841,7 @@
         if ((now+mConstants.MIN_TIME_TO_ALARM) > mAlarmManager.getNextWakeFromIdleTime()) {
             // Whoops, there is an upcoming alarm.  We don't actually want to go idle.
             if (mState != STATE_ACTIVE) {
+                mActiveReason = ACTIVE_REASON_ALARM;
                 becomeActiveLocked("alarm", Process.myUid());
                 becomeInactiveIfAppropriateLocked();
             }
@@ -2763,7 +2865,11 @@
                 // We have now been inactive long enough, it is time to start looking
                 // for motion and sleep some more while doing so.
                 startMonitoringMotionLocked();
-                scheduleAlarmLocked(mConstants.IDLE_AFTER_INACTIVE_TIMEOUT, false);
+                long delay = mConstants.IDLE_AFTER_INACTIVE_TIMEOUT;
+                if (shouldUseIdleTimeoutFactorLocked()) {
+                    delay = (long) (mPreIdleFactor * delay);
+                }
+                scheduleAlarmLocked(delay, false);
                 moveToStateLocked(STATE_IDLE_PENDING, reason);
                 break;
             case STATE_IDLE_PENDING:
@@ -2834,6 +2940,7 @@
                         " ms.");
                 mNextIdleDelay = (long)(mNextIdleDelay * mConstants.IDLE_FACTOR);
                 if (DEBUG) Slog.d(TAG, "Setting mNextIdleDelay = " + mNextIdleDelay);
+                mIdleStartTime = SystemClock.elapsedRealtime();
                 mNextIdleDelay = Math.min(mNextIdleDelay, mConstants.MAX_IDLE_TIMEOUT);
                 if (mNextIdleDelay < mConstants.IDLE_TIMEOUT) {
                     mNextIdleDelay = mConstants.IDLE_TIMEOUT;
@@ -2934,6 +3041,127 @@
         }
     }
 
+    @VisibleForTesting
+    int setPreIdleTimeoutMode(int mode) {
+        return setPreIdleTimeoutFactor(getPreIdleTimeoutByMode(mode));
+    }
+
+    @VisibleForTesting
+    float getPreIdleTimeoutByMode(int mode) {
+        switch (mode) {
+            case PowerManager.PRE_IDLE_TIMEOUT_MODE_LONG: {
+                return mConstants.PRE_IDLE_FACTOR_LONG;
+            }
+            case PowerManager.PRE_IDLE_TIMEOUT_MODE_SHORT: {
+                return mConstants.PRE_IDLE_FACTOR_SHORT;
+            }
+            case PowerManager.PRE_IDLE_TIMEOUT_MODE_NORMAL: {
+                return 1.0f;
+            }
+            default: {
+                Slog.w(TAG, "Invalid time out factor mode: " + mode);
+                return 1.0f;
+            }
+        }
+    }
+
+    @VisibleForTesting
+    float getPreIdleTimeoutFactor() {
+        return mPreIdleFactor;
+    }
+
+    @VisibleForTesting
+    int setPreIdleTimeoutFactor(float ratio) {
+        if (!mDeepEnabled) {
+            if (DEBUG) Slog.d(TAG, "setPreIdleTimeoutFactor: Deep Idle disable");
+            return SET_IDLE_FACTOR_RESULT_NOT_SUPPORT;
+        } else if (ratio <= MIN_PRE_IDLE_FACTOR_CHANGE) {
+            if (DEBUG) Slog.d(TAG, "setPreIdleTimeoutFactor: Invalid input");
+            return SET_IDLE_FACTOR_RESULT_INVALID;
+        } else if (Math.abs(ratio - mPreIdleFactor) < MIN_PRE_IDLE_FACTOR_CHANGE) {
+            if (DEBUG) Slog.d(TAG, "setPreIdleTimeoutFactor: New factor same as previous factor");
+            return SET_IDLE_FACTOR_RESULT_IGNORED;
+        }
+        synchronized (this) {
+            mLastPreIdleFactor = mPreIdleFactor;
+            mPreIdleFactor = ratio;
+        }
+        if (DEBUG) Slog.d(TAG, "setPreIdleTimeoutFactor: " + ratio);
+        postUpdatePreIdleFactor();
+        return SET_IDLE_FACTOR_RESULT_OK;
+    }
+
+    @VisibleForTesting
+    void resetPreIdleTimeoutMode() {
+        synchronized (this) {
+            mLastPreIdleFactor = mPreIdleFactor;
+            mPreIdleFactor = 1.0f;
+        }
+        if (DEBUG) Slog.d(TAG, "resetPreIdleTimeoutMode to 1.0");
+        postResetPreIdleTimeoutFactor();
+    }
+
+    private void postUpdatePreIdleFactor() {
+        mHandler.sendEmptyMessage(MSG_UPDATE_PRE_IDLE_TIMEOUT_FACTOR);
+    }
+
+    private void postResetPreIdleTimeoutFactor() {
+        mHandler.sendEmptyMessage(MSG_RESET_PRE_IDLE_TIMEOUT_FACTOR);
+    }
+
+    @VisibleForTesting
+    void updatePreIdleFactor() {
+        synchronized (this) {
+            if (!shouldUseIdleTimeoutFactorLocked()) {
+                return;
+            }
+            if (mState == STATE_INACTIVE || mState == STATE_IDLE_PENDING) {
+                if (mNextAlarmTime == 0) {
+                    return;
+                }
+                long delay = mNextAlarmTime - SystemClock.elapsedRealtime();
+                if (delay < MIN_STATE_STEP_ALARM_CHANGE) {
+                    return;
+                }
+                long newDelay = (long) (delay / mLastPreIdleFactor * mPreIdleFactor);
+                if (Math.abs(delay - newDelay) < MIN_STATE_STEP_ALARM_CHANGE) {
+                    return;
+                }
+                scheduleAlarmLocked(newDelay, false);
+            }
+        }
+    }
+
+    @VisibleForTesting
+    void maybeDoImmediateMaintenance() {
+        synchronized (this) {
+            if (mState == STATE_IDLE) {
+                long duration = SystemClock.elapsedRealtime() - mIdleStartTime;
+                /* Let's trgger a immediate maintenance,
+                 * if it has been idle for a long time */
+                if (duration > mConstants.IDLE_TIMEOUT) {
+                    scheduleAlarmLocked(0, false);
+                }
+            }
+        }
+    }
+
+    private boolean shouldUseIdleTimeoutFactorLocked() {
+        // exclude ACTIVE_REASON_MOTION, for exclude device in pocket case
+        if (mActiveReason == ACTIVE_REASON_MOTION) {
+            return false;
+        }
+        return true;
+    }
+
+    /** Must only be used in tests. */
+    @VisibleForTesting
+    void setIdleStartTimeForTest(long idleStartTime) {
+        synchronized (this) {
+            mIdleStartTime = idleStartTime;
+        }
+    }
+
     void reportMaintenanceActivityIfNeededLocked() {
         boolean active = mJobsActive;
         if (active == mReportedMaintenanceActivity) {
@@ -2945,6 +3173,11 @@
         mHandler.sendMessage(msg);
     }
 
+    @VisibleForTesting
+    long getNextAlarmTime() {
+        return mNextAlarmTime;
+    }
+
     boolean isOpsInactiveLocked() {
         return mActiveIdleOpCount <= 0 && !mJobsActive && !mAlarmsActive;
     }
@@ -2994,6 +3227,7 @@
                 scheduleReportActiveLocked(type, Process.myUid());
                 addEvent(EVENT_NORMAL, type);
             }
+            mActiveReason = ACTIVE_REASON_MOTION;
             mState = STATE_ACTIVE;
             mInactiveTimeout = timeout;
             mCurIdleBudget = 0;
@@ -3401,6 +3635,11 @@
                 + "and any [-d] is ignored");
         pw.println("  motion");
         pw.println("    Simulate a motion event to bring the device out of deep doze");
+        pw.println("  pre-idle-factor [0|1|2]");
+        pw.println("    Set a new factor to idle time before step to idle"
+                + "(inactive_to and idle_after_inactive_to)");
+        pw.println("  reset-pre-idle-factor");
+        pw.println("    Reset factor to idle time to default");
     }
 
     class Shell extends ShellCommand {
@@ -3571,6 +3810,7 @@
                         }
                     }
                     if (becomeActive) {
+                        mActiveReason = ACTIVE_REASON_FORCED;
                         becomeActiveLocked((arg == null ? "all" : arg) + "-disabled",
                                 Process.myUid());
                     }
@@ -3820,6 +4060,52 @@
                     Binder.restoreCallingIdentity(token);
                 }
             }
+        } else if ("pre-idle-factor".equals(cmd)) {
+            getContext().enforceCallingOrSelfPermission(android.Manifest.permission.DEVICE_POWER,
+                    null);
+            synchronized (this) {
+                long token = Binder.clearCallingIdentity();
+                int ret  = SET_IDLE_FACTOR_RESULT_UNINIT;
+                try {
+                    String arg = shell.getNextArg();
+                    boolean valid = false;
+                    int mode = 0;
+                    if (arg != null) {
+                        mode = Integer.parseInt(arg);
+                        ret = setPreIdleTimeoutMode(mode);
+                        if (ret == SET_IDLE_FACTOR_RESULT_OK) {
+                            pw.println("pre-idle-factor: " + mode);
+                            valid = true;
+                        } else if (ret == SET_IDLE_FACTOR_RESULT_NOT_SUPPORT) {
+                            valid = true;
+                            pw.println("Deep idle not supported");
+                        } else if (ret == SET_IDLE_FACTOR_RESULT_IGNORED) {
+                            valid = true;
+                            pw.println("Idle timeout factor not changed");
+                        }
+                    }
+                    if (!valid) {
+                        pw.println("Unknown idle timeout factor: " + arg
+                                + ",(error code: " + ret + ")");
+                    }
+                } catch (NumberFormatException e) {
+                    pw.println("Unknown idle timeout factor"
+                            + ",(error code: " + ret + ")");
+                } finally {
+                    Binder.restoreCallingIdentity(token);
+                }
+            }
+        } else if ("reset-pre-idle-factor".equals(cmd)) {
+            getContext().enforceCallingOrSelfPermission(android.Manifest.permission.DEVICE_POWER,
+                    null);
+            synchronized (this) {
+                long token = Binder.clearCallingIdentity();
+                try {
+                    resetPreIdleTimeoutMode();
+                } finally {
+                    Binder.restoreCallingIdentity(token);
+                }
+            }
         } else {
             return shell.handleDefaultCommands(cmd);
         }
@@ -4053,6 +4339,9 @@
             if (mAlarmsActive) {
                 pw.print("  mAlarmsActive="); pw.println(mAlarmsActive);
             }
+            if (Math.abs(mPreIdleFactor - 1.0f) > MIN_PRE_IDLE_FACTOR_CHANGE) {
+                pw.print("  mPreIdleFactor="); pw.println(mPreIdleFactor);
+            }
         }
     }
 
diff --git a/services/core/java/com/android/server/IpSecService.java b/services/core/java/com/android/server/IpSecService.java
index 126bf65..371276f 100644
--- a/services/core/java/com/android/server/IpSecService.java
+++ b/services/core/java/com/android/server/IpSecService.java
@@ -44,7 +44,7 @@
 import android.net.Network;
 import android.net.NetworkUtils;
 import android.net.TrafficStats;
-import android.net.util.NetdService;
+import android.net.shared.NetdService;
 import android.os.Binder;
 import android.os.IBinder;
 import android.os.ParcelFileDescriptor;
diff --git a/services/core/java/com/android/server/LocationManagerService.java b/services/core/java/com/android/server/LocationManagerService.java
index add5e5f..d2c6354 100644
--- a/services/core/java/com/android/server/LocationManagerService.java
+++ b/services/core/java/com/android/server/LocationManagerService.java
@@ -46,6 +46,7 @@
 import android.content.pm.Signature;
 import android.content.res.Resources;
 import android.database.ContentObserver;
+import android.hardware.location.ActivityRecognitionHardware;
 import android.location.Address;
 import android.location.Criteria;
 import android.location.GeocoderParams;
@@ -66,7 +67,6 @@
 import android.os.Bundle;
 import android.os.Handler;
 import android.os.IBinder;
-import android.os.Looper;
 import android.os.PowerManager;
 import android.os.Process;
 import android.os.RemoteException;
@@ -87,11 +87,11 @@
 import com.android.internal.content.PackageMonitor;
 import com.android.internal.location.ProviderProperties;
 import com.android.internal.location.ProviderRequest;
-import com.android.internal.os.BackgroundThread;
 import com.android.internal.util.ArrayUtils;
 import com.android.internal.util.DumpUtils;
 import com.android.internal.util.Preconditions;
 import com.android.server.location.AbstractLocationProvider;
+import com.android.server.location.ActivityRecognitionProxy;
 import com.android.server.location.GeocoderProxy;
 import com.android.server.location.GeofenceManager;
 import com.android.server.location.GeofenceProxy;
@@ -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<>();
 
@@ -246,7 +248,7 @@
     public LocationManagerService(Context context) {
         super();
         mContext = context;
-        mHandler = BackgroundThread.getHandler();
+        mHandler = FgThread.getHandler();
 
         // Let the package manager query which are the default location
         // providers as they get certain permissions granted by default.
@@ -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);
@@ -736,6 +769,25 @@
             Slog.d(TAG, "Unable to bind FLP Geofence proxy.");
         }
 
+        // bind to hardware activity recognition
+        boolean activityRecognitionHardwareIsSupported = ActivityRecognitionHardware.isSupported();
+        ActivityRecognitionHardware activityRecognitionHardware = null;
+        if (activityRecognitionHardwareIsSupported) {
+            activityRecognitionHardware = ActivityRecognitionHardware.getInstance(mContext);
+        } else {
+            Slog.d(TAG, "Hardware Activity-Recognition not supported.");
+        }
+        ActivityRecognitionProxy proxy = ActivityRecognitionProxy.createAndBind(
+                mContext,
+                activityRecognitionHardwareIsSupported,
+                activityRecognitionHardware,
+                com.android.internal.R.bool.config_enableActivityRecognitionHardwareOverlay,
+                com.android.internal.R.string.config_activityRecognitionHardwarePackageName,
+                com.android.internal.R.array.config_locationProviderPackageNames);
+        if (proxy == null) {
+            Slog.d(TAG, "Unable to bind ActivityRecognitionProxy.");
+        }
+
         String[] testProviderStrings = resources.getStringArray(
                 com.android.internal.R.array.config_testLocationProviders);
         for (String testProviderString : testProviderStrings) {
@@ -830,6 +882,15 @@
             mAllowed = !mIsManagedBySettings;
             mEnabled = false;
             mProperties = null;
+
+            if (mIsManagedBySettings) {
+                // since we assume providers are disabled by default
+                Settings.Secure.putStringForUser(
+                        mContext.getContentResolver(),
+                        Settings.Secure.LOCATION_PROVIDERS_ALLOWED,
+                        "-" + mName,
+                        mCurrentUserId);
+            }
         }
 
         @GuardedBy("mLock")
@@ -954,7 +1015,7 @@
         public void onReportLocation(Location location) {
             // no security check necessary because this is coming from an internal-only interface
             // move calls coming from below LMS onto a different thread to avoid deadlock
-            runInternal(() -> {
+            mHandler.post(() -> {
                 synchronized (mLock) {
                     handleLocationChangedLocked(location, this);
                 }
@@ -965,7 +1026,7 @@
         @Override
         public void onReportLocation(List<Location> locations) {
             // move calls coming from below LMS onto a different thread to avoid deadlock
-            runInternal(() -> {
+            mHandler.post(() -> {
                 synchronized (mLock) {
                     LocationProvider gpsProvider = getLocationProviderLocked(GPS_PROVIDER);
                     if (gpsProvider == null || !gpsProvider.isUseableLocked()) {
@@ -991,7 +1052,7 @@
         @Override
         public void onSetEnabled(boolean enabled) {
             // move calls coming from below LMS onto a different thread to avoid deadlock
-            runInternal(() -> {
+            mHandler.post(() -> {
                 synchronized (mLock) {
                     if (enabled == mEnabled) {
                         return;
@@ -1113,16 +1174,6 @@
             mUseable = false;
             updateProviderUseableLocked(this);
         }
-
-        // binder transactions coming from below LMS (ie location providers) need to be moved onto
-        // a different thread to avoid potential deadlock as code reenters the location providers
-        private void runInternal(Runnable runnable) {
-            if (Looper.myLooper() == mHandler.getLooper()) {
-                runnable.run();
-            } else {
-                mHandler.post(runnable);
-            }
-        }
     }
 
     private class MockLocationProvider extends LocationProvider {
@@ -1278,7 +1329,10 @@
                 // are high power (has a high power provider with an interval under a threshold).
                 for (UpdateRecord updateRecord : mUpdateRecords.values()) {
                     LocationProvider provider = getLocationProviderLocked(updateRecord.mProvider);
-                    if (provider == null || !provider.isUseableLocked()) {
+                    if (provider == null) {
+                        continue;
+                    }
+                    if (!provider.isUseableLocked() && !isSettingsExemptLocked(updateRecord)) {
                         continue;
                     }
 
@@ -1960,14 +2014,22 @@
         ArrayList<UpdateRecord> records = mRecordsByProvider.get(provider.getName());
         if (records != null) {
             for (UpdateRecord record : records) {
-                if (isCurrentProfileLocked(UserHandle.getUserId(record.mReceiver.mIdentity.mUid))) {
-                    // Sends a notification message to the receiver
-                    if (!record.mReceiver.callProviderEnabledLocked(provider.getName(), useable)) {
-                        if (deadReceivers == null) {
-                            deadReceivers = new ArrayList<>();
-                        }
-                        deadReceivers.add(record.mReceiver);
+                if (!isCurrentProfileLocked(
+                        UserHandle.getUserId(record.mReceiver.mIdentity.mUid))) {
+                    continue;
+                }
+
+                // requests that ignore location settings will never provider notifications
+                if (isSettingsExemptLocked(record)) {
+                    continue;
+                }
+
+                // Sends a notification message to the receiver
+                if (!record.mReceiver.callProviderEnabledLocked(provider.getName(), useable)) {
+                    if (deadReceivers == null) {
+                        deadReceivers = new ArrayList<>();
                     }
+                    deadReceivers.add(record.mReceiver);
                 }
             }
         }
@@ -2007,39 +2069,46 @@
             Binder.restoreCallingIdentity(identity);
         }
 
-        if (provider.isUseableLocked() && records != null && !records.isEmpty()) {
+        if (records != null && !records.isEmpty()) {
             // initialize the low power mode to true and set to false if any of the records requires
             providerRequest.lowPowerMode = true;
             for (UpdateRecord record : records) {
-                if (isCurrentProfileLocked(UserHandle.getUserId(record.mReceiver.mIdentity.mUid))) {
-                    if (checkLocationAccess(
-                            record.mReceiver.mIdentity.mPid,
-                            record.mReceiver.mIdentity.mUid,
-                            record.mReceiver.mIdentity.mPackageName,
-                            record.mReceiver.mAllowedResolutionLevel)) {
-                        LocationRequest locationRequest = record.mRealRequest;
-                        long interval = locationRequest.getInterval();
+                if (!isCurrentProfileLocked(
+                        UserHandle.getUserId(record.mReceiver.mIdentity.mUid))) {
+                    continue;
+                }
+                if (!checkLocationAccess(
+                        record.mReceiver.mIdentity.mPid,
+                        record.mReceiver.mIdentity.mUid,
+                        record.mReceiver.mIdentity.mPackageName,
+                        record.mReceiver.mAllowedResolutionLevel)) {
+                    continue;
+                }
+                if (!provider.isUseableLocked() && !isSettingsExemptLocked(record)) {
+                    continue;
+                }
 
-                        if (!isThrottlingExemptLocked(record.mReceiver.mIdentity)) {
-                            if (!record.mIsForegroundUid) {
-                                interval = Math.max(interval, backgroundThrottleInterval);
-                            }
-                            if (interval != locationRequest.getInterval()) {
-                                locationRequest = new LocationRequest(locationRequest);
-                                locationRequest.setInterval(interval);
-                            }
-                        }
+                LocationRequest locationRequest = record.mRealRequest;
+                long interval = locationRequest.getInterval();
 
-                        record.mRequest = locationRequest;
-                        providerRequest.locationRequests.add(locationRequest);
-                        if (!locationRequest.isLowPowerMode()) {
-                            providerRequest.lowPowerMode = false;
-                        }
-                        if (interval < providerRequest.interval) {
-                            providerRequest.reportLocation = true;
-                            providerRequest.interval = interval;
-                        }
+                if (!isThrottlingExemptLocked(record.mReceiver.mIdentity)) {
+                    if (!record.mIsForegroundUid) {
+                        interval = Math.max(interval, backgroundThrottleInterval);
                     }
+                    if (interval != locationRequest.getInterval()) {
+                        locationRequest = new LocationRequest(locationRequest);
+                        locationRequest.setInterval(interval);
+                    }
+                }
+
+                record.mRequest = locationRequest;
+                providerRequest.locationRequests.add(locationRequest);
+                if (!locationRequest.isLowPowerMode()) {
+                    providerRequest.lowPowerMode = false;
+                }
+                if (interval < providerRequest.interval) {
+                    providerRequest.reportLocation = true;
+                    providerRequest.interval = interval;
                 }
             }
 
@@ -2125,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
@@ -2268,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;
     }
@@ -2307,6 +2395,10 @@
                 mContext.enforceCallingOrSelfPermission(
                         Manifest.permission.UPDATE_APP_OPS_STATS, null);
             }
+            if (request.isLocationSettingsIgnored()) {
+                mContext.enforceCallingOrSelfPermission(
+                        Manifest.permission.WRITE_SECURE_SETTINGS, null);
+            }
             boolean callerHasLocationHardwarePermission =
                     mContext.checkCallingPermission(android.Manifest.permission.LOCATION_HARDWARE)
                             == PERMISSION_GRANTED;
@@ -2376,12 +2468,14 @@
             oldRecord.disposeLocked(false);
         }
 
-        if (provider.isUseableLocked()) {
-            applyRequirementsLocked(name);
-        } else {
-            // Notify the listener that updates are currently disabled
+        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);
         }
+
+        applyRequirementsLocked(name);
+
         // Update the monitoring here just in case multiple location requests were added to the
         // same receiver (this request may be high power and the initial might not have been).
         receiver.updateMonitoring(true);
@@ -2865,33 +2959,11 @@
 
         long identity = Binder.clearCallingIdentity();
         try {
-            boolean enabled;
-            try {
-                enabled = Settings.Secure.getIntForUser(
+            return Settings.Secure.getIntForUser(
                         mContext.getContentResolver(),
                         Settings.Secure.LOCATION_MODE,
+                        Settings.Secure.LOCATION_MODE_OFF,
                         userId) != Settings.Secure.LOCATION_MODE_OFF;
-            } catch (Settings.SettingNotFoundException e) {
-                // OS upgrade case where mode isn't set yet
-                enabled = !TextUtils.isEmpty(Settings.Secure.getStringForUser(
-                        mContext.getContentResolver(),
-                        Settings.Secure.LOCATION_PROVIDERS_ALLOWED,
-                        userId));
-
-                try {
-                    Settings.Secure.putIntForUser(
-                            mContext.getContentResolver(),
-                            Settings.Secure.LOCATION_MODE,
-                            enabled
-                                    ? Settings.Secure.LOCATION_MODE_HIGH_ACCURACY
-                                    : Settings.Secure.LOCATION_MODE_OFF,
-                            userId);
-                } catch (RuntimeException ex) {
-                    // any problem with writing should not be propagated
-                    Slog.e(TAG, "error updating location mode", ex);
-                }
-            }
-            return enabled;
         } finally {
             Binder.restoreCallingIdentity(identity);
         }
@@ -2981,26 +3053,29 @@
             return;
         }
 
-        if (!provider.isPassiveLocked()) {
-            // notify passive provider of the new location
-            mPassiveProvider.updateLocation(location);
+        // only notify passive provider and update last location for locations that come from
+        // useable providers
+        if (provider.isUseableLocked()) {
+            if (!provider.isPassiveLocked()) {
+                mPassiveProvider.updateLocation(location);
+            }
         }
 
         if (D) Log.d(TAG, "incoming location: " + location);
         long now = SystemClock.elapsedRealtime();
-        updateLastLocationLocked(location, provider.getName());
-        // mLastLocation should have been updated from the updateLastLocationLocked call above.
-        Location lastLocation = mLastLocation.get(provider.getName());
-        if (lastLocation == null) {
-            Log.e(TAG, "handleLocationChangedLocked() updateLastLocation failed");
-            return;
+        if (provider.isUseableLocked()) {
+            updateLastLocationLocked(location, provider.getName());
         }
 
         // Update last known coarse interval location if enough time has passed.
-        Location lastLocationCoarseInterval = mLastLocationCoarseInterval.get(provider.getName());
+        Location lastLocationCoarseInterval = mLastLocationCoarseInterval.get(
+                provider.getName());
         if (lastLocationCoarseInterval == null) {
             lastLocationCoarseInterval = new Location(location);
-            mLastLocationCoarseInterval.put(provider.getName(), lastLocationCoarseInterval);
+
+            if (provider.isUseableLocked()) {
+                mLastLocationCoarseInterval.put(provider.getName(), lastLocationCoarseInterval);
+            }
         }
         long timeDiffNanos = location.getElapsedRealtimeNanos()
                 - lastLocationCoarseInterval.getElapsedRealtimeNanos();
@@ -3031,6 +3106,10 @@
             Receiver receiver = r.mReceiver;
             boolean receiverDead = false;
 
+            if (!provider.isUseableLocked() && !isSettingsExemptLocked(r)) {
+                continue;
+            }
+
             int receiverUserId = UserHandle.getUserId(receiver.mIdentity.mUid);
             if (!isCurrentProfileLocked(receiverUserId)
                     && !isLocationProviderLocked(receiver.mIdentity.mUid)) {
@@ -3066,7 +3145,7 @@
             if (receiver.mAllowedResolutionLevel < RESOLUTION_LEVEL_FINE) {
                 notifyLocation = coarseLocation;  // use coarse location
             } else {
-                notifyLocation = lastLocation;  // use fine location
+                notifyLocation = location;  // use fine location
             }
             if (notifyLocation != null) {
                 Location lastLoc = r.mLastFixBroadcast;
diff --git a/services/core/java/com/android/server/LooperStatsService.java b/services/core/java/com/android/server/LooperStatsService.java
index cee98c1..4a8706e 100644
--- a/services/core/java/com/android/server/LooperStatsService.java
+++ b/services/core/java/com/android/server/LooperStatsService.java
@@ -122,6 +122,10 @@
                 "exception_count"));
         pw.println(header);
         for (LooperStats.ExportedEntry entry : entries) {
+            if (entry.messageName.startsWith(LooperStats.DEBUG_ENTRY_PREFIX)) {
+                // Do not dump debug entries.
+                continue;
+            }
             pw.printf("%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s\n",
                     packageMap.mapUid(entry.workSourceUid),
                     entry.threadName,
diff --git a/services/core/java/com/android/server/NetworkManagementService.java b/services/core/java/com/android/server/NetworkManagementService.java
index c84389b..c064453 100644
--- a/services/core/java/com/android/server/NetworkManagementService.java
+++ b/services/core/java/com/android/server/NetworkManagementService.java
@@ -60,7 +60,8 @@
 import android.net.RouteInfo;
 import android.net.TetherStatsParcel;
 import android.net.UidRange;
-import android.net.util.NetdService;
+import android.net.shared.NetdService;
+import android.net.shared.NetworkObserverRegistry;
 import android.os.BatteryStats;
 import android.os.Binder;
 import android.os.Handler;
@@ -205,14 +206,13 @@
 
     private INetd mNetdService;
 
+    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")
@@ -340,6 +340,7 @@
         mFgHandler = null;
         mThread = null;
         mServices = null;
+        mNetworkObserverRegistry = null;
     }
 
     static NetworkManagementService create(Context context, String socket, SystemServices services)
@@ -387,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
@@ -402,128 +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));
         }
     }
 
@@ -552,7 +524,8 @@
                 return;
             }
             // No current code examines the interface parameter in a global alert. Just pass null.
-            notifyLimitReached(LIMIT_GLOBAL_ALERT, null);
+            mDaemonHandler.post(() -> mNetworkObserverRegistry.notifyLimitReached(
+                    LIMIT_GLOBAL_ALERT, null));
         }
     }
 
@@ -583,6 +556,13 @@
 
     private void connectNativeNetdService() {
         mNetdService = mServices.getNetd();
+        try {
+            mNetworkObserverRegistry = new NMSNetworkObserverRegistry(
+                    mContext, mDaemonHandler, mNetdService);
+            if (DBG) Slog.d(TAG, "Registered NetworkObserverRegistry");
+        } catch (RemoteException | ServiceSpecificException e) {
+            Slog.wtf(TAG, "Failed to register NetworkObserverRegistry: " + e);
+        }
     }
 
     /**
@@ -685,38 +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(String action, RouteInfo route) {
-        if (action.equals("updated")) {
-            invokeForAllObservers(o -> o.routeUpdated(route));
-        } else {
-            invokeForAllObservers(o -> o.routeRemoved(route));
-        }
-    }
-
     //
     // Netd Callback handling
     //
@@ -765,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);
@@ -788,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);
@@ -814,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;
@@ -843,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;
@@ -865,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;
@@ -904,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], route);
+                            mNetworkObserverRegistry.notifyRouteChange(
+                                    cooked[2].equals("updated"), route);
                             return true;
                         } catch (IllegalArgumentException e) {}
                     }
@@ -1367,13 +1318,8 @@
             if (ConnectivityManager.isNetworkTypeMobile(type)) {
                 mNetworkActive = false;
             }
-            mDaemonHandler.post(new Runnable() {
-                @Override public void run() {
-                    notifyInterfaceClassActivity(type,
-                            DataConnectionRealTimeInfo.DC_POWER_STATE_HIGH,
-                            SystemClock.elapsedRealtimeNanos(), -1, false);
-                }
-            });
+            mDaemonHandler.post(() -> mNetworkObserverRegistry.notifyInterfaceClassActivity(
+                    type, true /* isActive */, SystemClock.elapsedRealtimeNanos(), -1, false));
         }
     }
 
@@ -1396,13 +1342,9 @@
                 throw new IllegalStateException(e);
             }
             mActiveIdleTimers.remove(iface);
-            mDaemonHandler.post(new Runnable() {
-                @Override public void run() {
-                    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/PersistentDataBlockManagerInternal.java b/services/core/java/com/android/server/PersistentDataBlockManagerInternal.java
index 1e9a007..190fff1 100644
--- a/services/core/java/com/android/server/PersistentDataBlockManagerInternal.java
+++ b/services/core/java/com/android/server/PersistentDataBlockManagerInternal.java
@@ -31,6 +31,19 @@
      */
     byte[] getFrpCredentialHandle();
 
+    /** Stores the data used to enable the Test Harness Mode after factory-resetting. */
+    void setTestHarnessModeData(byte[] data);
+
+    /**
+     * Retrieves the data used to place the device into Test Harness Mode.
+     *
+     * @throws IllegalStateException if the underlying storage is corrupt or inaccessible.
+     */
+    byte[] getTestHarnessModeData();
+
+    /** Clear out the Test Harness Mode data. */
+    void clearTestHarnessModeData();
+
     /** Update the OEM unlock enabled bit, bypassing user restriction checks. */
     void forceOemUnlockEnabled(boolean enabled);
 }
diff --git a/services/core/java/com/android/server/PersistentDataBlockService.java b/services/core/java/com/android/server/PersistentDataBlockService.java
index 21093b9..bd5ad96 100644
--- a/services/core/java/com/android/server/PersistentDataBlockService.java
+++ b/services/core/java/com/android/server/PersistentDataBlockService.java
@@ -16,6 +16,8 @@
 
 package com.android.server;
 
+import static com.android.internal.util.Preconditions.checkArgument;
+
 import android.Manifest;
 import android.app.ActivityManager;
 import android.content.Context;
@@ -28,12 +30,10 @@
 import android.os.UserManager;
 import android.service.persistentdata.IPersistentDataBlockService;
 import android.service.persistentdata.PersistentDataBlockManager;
-import android.util.Log;
 import android.util.Slog;
 
 import com.android.internal.R;
 import com.android.internal.annotations.GuardedBy;
-import com.android.internal.util.Preconditions;
 
 import libcore.io.IoUtils;
 
@@ -65,6 +65,40 @@
  *
  * Clients can read any number of bytes from the currently written block up to its total size by
  * invoking {@link IPersistentDataBlockService#read}
+ *
+ * The persistent data block is currently laid out as follows:
+ * | ---------BEGINNING OF PARTITION-------------|
+ * | Partition digest (32 bytes)                 |
+ * | --------------------------------------------|
+ * | PARTITION_TYPE_MARKER (4 bytes)             |
+ * | --------------------------------------------|
+ * | FRP data block length (4 bytes)             |
+ * | --------------------------------------------|
+ * | FRP data (variable length)                  |
+ * | --------------------------------------------|
+ * | ...                                         |
+ * | --------------------------------------------|
+ * | Test mode data block (10000 bytes)          |
+ * | --------------------------------------------|
+ * |     | Test mode data length (4 bytes)       |
+ * | --------------------------------------------|
+ * |     | Test mode data (variable length)      |
+ * |     | ...                                   |
+ * | --------------------------------------------|
+ * | FRP credential handle block (1000 bytes)    |
+ * | --------------------------------------------|
+ * |     | FRP credential handle length (4 bytes)|
+ * | --------------------------------------------|
+ * |     | FRP credential handle (variable len)  |
+ * |     | ...                                   |
+ * | --------------------------------------------|
+ * | OEM Unlock bit (1 byte)                     |
+ * | ---------END OF PARTITION-------------------|
+ *
+ * TODO: now that the persistent partition contains several blocks, next time someone wants a new
+ * block, we should look at adding more generic block definitions and get rid of the various raw
+ * XXX_RESERVED_SIZE and XXX_DATA_SIZE constants. That will ensure the code is easier to maintain
+ * and less likely to introduce out-of-bounds read/write.
  */
 public class PersistentDataBlockService extends SystemService {
     private static final String TAG = PersistentDataBlockService.class.getSimpleName();
@@ -73,10 +107,16 @@
     private static final int HEADER_SIZE = 8;
     // Magic number to mark block device as adhering to the format consumed by this service
     private static final int PARTITION_TYPE_MARKER = 0x19901873;
-    /** Size of the block reserved for FPR credential, including 4 bytes for the size header. */
+    /** Size of the block reserved for FRP credential, including 4 bytes for the size header. */
     private static final int FRP_CREDENTIAL_RESERVED_SIZE = 1000;
     /** Maximum size of the FRP credential handle that can be stored. */
     private static final int MAX_FRP_CREDENTIAL_HANDLE_SIZE = FRP_CREDENTIAL_RESERVED_SIZE - 4;
+    /**
+     * Size of the block reserved for Test Harness Mode data, including 4 bytes for the size header.
+     */
+    private static final int TEST_MODE_RESERVED_SIZE = 10000;
+    /** Maximum size of the Test Harness Mode data that can be stored. */
+    private static final int MAX_TEST_MODE_DATA_SIZE = TEST_MODE_RESERVED_SIZE - 4;
     // Limit to 100k as blocks larger than this might cause strain on Binder.
     private static final int MAX_DATA_BLOCK_SIZE = 1024 * 100;
 
@@ -221,6 +261,14 @@
         return mBlockDeviceSize;
     }
 
+    private long getFrpCredentialDataOffset() {
+        return getBlockDeviceSize() - 1 - FRP_CREDENTIAL_RESERVED_SIZE;
+    }
+
+    private long getTestHarnessModeDataOffset() {
+        return getFrpCredentialDataOffset() - TEST_MODE_RESERVED_SIZE;
+    }
+
     private boolean enforceChecksumValidity() {
         byte[] storedDigest = new byte[DIGEST_SIZE_BYTES];
 
@@ -383,7 +431,7 @@
 
     private long doGetMaximumDataBlockSize() {
         long actualSize = getBlockDeviceSize() - HEADER_SIZE - DIGEST_SIZE_BYTES
-                - FRP_CREDENTIAL_RESERVED_SIZE - 1;
+                - TEST_MODE_RESERVED_SIZE - FRP_CREDENTIAL_RESERVED_SIZE - 1;
         return actualSize <= MAX_DATA_BLOCK_SIZE ? actualSize : MAX_DATA_BLOCK_SIZE;
     }
 
@@ -391,6 +439,13 @@
     private native int nativeWipe(String path);
 
     private final IBinder mService = new IPersistentDataBlockService.Stub() {
+
+        /**
+         * Write the data to the persistent data block.
+         *
+         * @return a positive integer of the number of bytes that were written if successful,
+         * otherwise a negative integer indicating there was a problem
+         */
         @Override
         public int write(byte[] data) throws RemoteException {
             enforceUid(Binder.getCallingUid());
@@ -597,12 +652,51 @@
 
         @Override
         public void setFrpCredentialHandle(byte[] handle) {
-            Preconditions.checkArgument(handle == null || handle.length > 0,
-                    "handle must be null or non-empty");
-            Preconditions.checkArgument(handle == null
-                            || handle.length <= MAX_FRP_CREDENTIAL_HANDLE_SIZE,
-                    "handle must not be longer than " + MAX_FRP_CREDENTIAL_HANDLE_SIZE);
+            writeInternal(handle, getFrpCredentialDataOffset(), MAX_FRP_CREDENTIAL_HANDLE_SIZE);
+        }
 
+        @Override
+        public byte[] getFrpCredentialHandle() {
+            return readInternal(getFrpCredentialDataOffset(), MAX_FRP_CREDENTIAL_HANDLE_SIZE);
+        }
+
+        @Override
+        public void setTestHarnessModeData(byte[] data) {
+            writeInternal(data, getTestHarnessModeDataOffset(), MAX_TEST_MODE_DATA_SIZE);
+        }
+
+        @Override
+        public byte[] getTestHarnessModeData() {
+            byte[] data = readInternal(getTestHarnessModeDataOffset(), MAX_TEST_MODE_DATA_SIZE);
+            if (data == null) {
+                return new byte[0];
+            }
+            return data;
+        }
+
+        @Override
+        public void clearTestHarnessModeData() {
+            int size = Math.min(MAX_TEST_MODE_DATA_SIZE, getTestHarnessModeData().length) + 4;
+            writeDataBuffer(getTestHarnessModeDataOffset(), ByteBuffer.allocate(size));
+        }
+
+        private void writeInternal(byte[] data, long offset, int dataLength) {
+            checkArgument(data == null || data.length > 0, "data must be null or non-empty");
+            checkArgument(
+                    data == null || data.length <= dataLength,
+                    "data must not be longer than " + dataLength);
+
+            ByteBuffer dataBuffer = ByteBuffer.allocate(dataLength + 4);
+            dataBuffer.putInt(data == null ? 0 : data.length);
+            if (data != null) {
+                dataBuffer.put(data);
+            }
+            dataBuffer.flip();
+
+            writeDataBuffer(offset, dataBuffer);
+        }
+
+        private void writeDataBuffer(long offset, ByteBuffer dataBuffer) {
             FileOutputStream outputStream;
             try {
                 outputStream = new FileOutputStream(new File(mDataBlockFile));
@@ -610,25 +704,15 @@
                 Slog.e(TAG, "partition not available", e);
                 return;
             }
-
-            ByteBuffer data = ByteBuffer.allocate(FRP_CREDENTIAL_RESERVED_SIZE);
-            data.putInt(handle == null ? 0 : handle.length);
-            if (handle != null) {
-                data.put(handle);
-            }
-            data.flip();
-
             synchronized (mLock) {
                 if (!mIsWritable) {
                     IoUtils.closeQuietly(outputStream);
                     return;
                 }
-
                 try {
                     FileChannel channel = outputStream.getChannel();
-
-                    channel.position(getBlockDeviceSize() - 1 - FRP_CREDENTIAL_RESERVED_SIZE);
-                    channel.write(data);
+                    channel.position(offset);
+                    channel.write(dataBuffer);
                     outputStream.flush();
                 } catch (IOException e) {
                     Slog.e(TAG, "unable to access persistent partition", e);
@@ -641,8 +725,7 @@
             }
         }
 
-        @Override
-        public byte[] getFrpCredentialHandle() {
+        private byte[] readInternal(long offset, int maxLength) {
             if (!enforceChecksumValidity()) {
                 throw new IllegalStateException("invalid checksum");
             }
@@ -652,14 +735,14 @@
                 inputStream = new DataInputStream(
                         new FileInputStream(new File(mDataBlockFile)));
             } catch (FileNotFoundException e) {
-                throw new IllegalStateException("frp partition not available");
+                throw new IllegalStateException("persistent partition not available");
             }
 
             try {
                 synchronized (mLock) {
-                    inputStream.skip(getBlockDeviceSize() - 1 - FRP_CREDENTIAL_RESERVED_SIZE);
+                    inputStream.skip(offset);
                     int length = inputStream.readInt();
-                    if (length <= 0 || length > MAX_FRP_CREDENTIAL_HANDLE_SIZE) {
+                    if (length <= 0 || length > maxLength) {
                         return null;
                     }
                     byte[] bytes = new byte[length];
@@ -667,7 +750,7 @@
                     return bytes;
                 }
             } catch (IOException e) {
-                throw new IllegalStateException("frp handle not readable", e);
+                throw new IllegalStateException("persistent partition not readable", e);
             } finally {
                 IoUtils.closeQuietly(inputStream);
             }
diff --git a/services/core/java/com/android/server/StorageManagerService.java b/services/core/java/com/android/server/StorageManagerService.java
index 7731c04..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);
 
@@ -1706,8 +1718,8 @@
             int uid, String packageName, int[] ops) {
         long maxTime = 0;
         final List<AppOpsManager.PackageOps> pkgs = manager.getOpsForPackage(uid, packageName, ops);
-        for (AppOpsManager.PackageOps pkg : CollectionUtils.defeatNullable(pkgs)) {
-            for (AppOpsManager.OpEntry op : CollectionUtils.defeatNullable(pkg.getOps())) {
+        for (AppOpsManager.PackageOps pkg : CollectionUtils.emptyIfNull(pkgs)) {
+            for (AppOpsManager.OpEntry op : CollectionUtils.emptyIfNull(pkg.getOps())) {
                 maxTime = Math.max(maxTime, op.getLastAccessTime());
             }
         }
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/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/adb/AdbDebuggingManager.java b/services/core/java/com/android/server/adb/AdbDebuggingManager.java
index ccead6c..c7b9a3c 100644
--- a/services/core/java/com/android/server/adb/AdbDebuggingManager.java
+++ b/services/core/java/com/android/server/adb/AdbDebuggingManager.java
@@ -18,6 +18,7 @@
 
 import static com.android.internal.util.dump.DumpUtils.writeStringIfNotNull;
 
+import android.annotation.TestApi;
 import android.app.ActivityManager;
 import android.content.ActivityNotFoundException;
 import android.content.ComponentName;
@@ -26,6 +27,7 @@
 import android.content.pm.PackageManager;
 import android.content.pm.UserInfo;
 import android.content.res.Resources;
+import android.debug.AdbProtoEnums;
 import android.net.LocalSocket;
 import android.net.LocalSocketAddress;
 import android.os.Environment;
@@ -37,21 +39,36 @@
 import android.os.SystemProperties;
 import android.os.UserHandle;
 import android.os.UserManager;
+import android.provider.Settings;
 import android.service.adb.AdbDebuggingManagerProto;
+import android.util.AtomicFile;
 import android.util.Base64;
 import android.util.Slog;
+import android.util.StatsLog;
+import android.util.Xml;
 
 import com.android.internal.R;
+import com.android.internal.util.FastXmlSerializer;
+import com.android.internal.util.XmlUtils;
 import com.android.internal.util.dump.DualDumpOutputStream;
 import com.android.server.FgThread;
 
+import org.xmlpull.v1.XmlPullParser;
+import org.xmlpull.v1.XmlPullParserException;
+import org.xmlpull.v1.XmlSerializer;
+
 import java.io.File;
+import java.io.FileInputStream;
 import java.io.FileOutputStream;
 import java.io.IOException;
 import java.io.InputStream;
 import java.io.OutputStream;
+import java.nio.charset.StandardCharsets;
 import java.security.MessageDigest;
 import java.util.Arrays;
+import java.util.HashMap;
+import java.util.Iterator;
+import java.util.Map;
 
 /**
  * Provides communication to the Android Debug Bridge daemon to allow, deny, or clear public keysi
@@ -63,6 +80,7 @@
 
     private static final String ADBD_SOCKET = "adbd";
     private static final String ADB_DIRECTORY = "misc/adb";
+    // This file contains keys that will always be allowed to connect to the device via adb.
     private static final String ADB_KEYS_FILE = "adb_keys";
     private static final int BUFFER_SIZE = 4096;
 
@@ -71,12 +89,25 @@
     private AdbDebuggingThread mThread;
     private boolean mAdbEnabled = false;
     private String mFingerprints;
+    private String mConnectedKey;
+    private String mConfirmComponent;
 
     public AdbDebuggingManager(Context context) {
         mHandler = new AdbDebuggingHandler(FgThread.get().getLooper());
         mContext = context;
     }
 
+    /**
+     * Constructor that accepts the component to be invoked to confirm if the user wants to allow
+     * an adb connection from the key.
+     */
+    @TestApi
+    protected AdbDebuggingManager(Context context, String confirmComponent) {
+        mHandler = new AdbDebuggingHandler(FgThread.get().getLooper());
+        mContext = context;
+        mConfirmComponent = confirmComponent;
+    }
+
     class AdbDebuggingThread extends Thread {
         private boolean mStopped;
         private LocalSocket mSocket;
@@ -135,7 +166,9 @@
                 byte[] buffer = new byte[BUFFER_SIZE];
                 while (true) {
                     int count = mInputStream.read(buffer);
-                    if (count < 0) {
+                    // if less than 2 bytes are read the if statements below will throw an
+                    // IndexOutOfBoundsException.
+                    if (count < 2) {
                         break;
                     }
 
@@ -146,6 +179,11 @@
                                 AdbDebuggingHandler.MESSAGE_ADB_CONFIRM);
                         msg.obj = key;
                         mHandler.sendMessage(msg);
+                    } else if (buffer[0] == 'D' && buffer[1] == 'C') {
+                        Slog.d(TAG, "Received disconnected message");
+                        Message msg = mHandler.obtainMessage(
+                                AdbDebuggingHandler.MESSAGE_ADB_DISCONNECT);
+                        mHandler.sendMessage(msg);
                     } else {
                         Slog.e(TAG, "Wrong message: "
                                 + (new String(Arrays.copyOfRange(buffer, 0, 2))));
@@ -202,15 +240,41 @@
     }
 
     class AdbDebuggingHandler extends Handler {
-        private static final int MESSAGE_ADB_ENABLED = 1;
-        private static final int MESSAGE_ADB_DISABLED = 2;
-        private static final int MESSAGE_ADB_ALLOW = 3;
-        private static final int MESSAGE_ADB_DENY = 4;
-        private static final int MESSAGE_ADB_CONFIRM = 5;
-        private static final int MESSAGE_ADB_CLEAR = 6;
+        // The time to schedule the job to keep the key store updated with a currently connected
+        // key. This job is required because a deveoper could keep a device connected to their
+        // system beyond the time within which a subsequent connection is allowed. But since the
+        // last connection time is only written when a device is connected and disconnected then
+        // if the device is rebooted while connected to the development system it would appear as
+        // though the adb grant for the system is no longer authorized and the developer would need
+        // to manually allow the connection again.
+        private static final long UPDATE_KEY_STORE_JOB_INTERVAL = 86400000;
+
+        static final int MESSAGE_ADB_ENABLED = 1;
+        static final int MESSAGE_ADB_DISABLED = 2;
+        static final int MESSAGE_ADB_ALLOW = 3;
+        static final int MESSAGE_ADB_DENY = 4;
+        static final int MESSAGE_ADB_CONFIRM = 5;
+        static final int MESSAGE_ADB_CLEAR = 6;
+        static final int MESSAGE_ADB_DISCONNECT = 7;
+        static final int MESSAGE_ADB_PERSIST_KEY_STORE = 8;
+        static final int MESSAGE_ADB_UPDATE_KEY_CONNECTION_TIME = 9;
+
+        private AdbKeyStore mAdbKeyStore;
 
         AdbDebuggingHandler(Looper looper) {
             super(looper);
+            mAdbKeyStore = new AdbKeyStore();
+        }
+
+        /**
+         * Constructor that accepts the AdbDebuggingThread to which responses should be sent
+         * and the AdbKeyStore to be used to store the temporary grants.
+         */
+        @TestApi
+        AdbDebuggingHandler(Looper looper, AdbDebuggingThread thread, AdbKeyStore adbKeyStore) {
+            super(looper);
+            mThread = thread;
+            mAdbKeyStore = adbKeyStore;
         }
 
         public void handleMessage(Message msg) {
@@ -251,12 +315,15 @@
                         break;
                     }
 
-                    if (msg.arg1 == 1) {
-                        writeKey(key);
-                    }
-
+                    boolean alwaysAllow = msg.arg1 == 1;
                     if (mThread != null) {
                         mThread.sendResponse("OK");
+                        if (alwaysAllow) {
+                            mConnectedKey = key;
+                            mAdbKeyStore.setLastConnectionTime(key, System.currentTimeMillis());
+                            scheduleJobToUpdateAdbKeyStore();
+                        }
+                        logAdbConnectionChanged(key, AdbProtoEnums.USER_ALLOWED, alwaysAllow);
                     }
                     break;
                 }
@@ -264,36 +331,88 @@
                 case MESSAGE_ADB_DENY:
                     if (mThread != null) {
                         mThread.sendResponse("NO");
+                        logAdbConnectionChanged(null, AdbProtoEnums.USER_DENIED, false);
                     }
                     break;
 
                 case MESSAGE_ADB_CONFIRM: {
+                    String key = (String) msg.obj;
                     if ("trigger_restart_min_framework".equals(
                             SystemProperties.get("vold.decrypt"))) {
                         Slog.d(TAG, "Deferring adb confirmation until after vold decrypt");
                         if (mThread != null) {
                             mThread.sendResponse("NO");
+                            logAdbConnectionChanged(key, AdbProtoEnums.DENIED_VOLD_DECRYPT, false);
                         }
                         break;
                     }
-                    String key = (String) msg.obj;
                     String fingerprints = getFingerprints(key);
                     if ("".equals(fingerprints)) {
                         if (mThread != null) {
                             mThread.sendResponse("NO");
+                            logAdbConnectionChanged(key, AdbProtoEnums.DENIED_INVALID_KEY, false);
                         }
                         break;
                     }
-                    mFingerprints = fingerprints;
-                    startConfirmation(key, mFingerprints);
+                    // Check if the key should be allowed without user interaction.
+                    if (mAdbKeyStore.isKeyAuthorized(key)) {
+                        if (mThread != null) {
+                            mThread.sendResponse("OK");
+                            mAdbKeyStore.setLastConnectionTime(key, System.currentTimeMillis());
+                            logAdbConnectionChanged(key, AdbProtoEnums.AUTOMATICALLY_ALLOWED, true);
+                            mConnectedKey = key;
+                            scheduleJobToUpdateAdbKeyStore();
+                        }
+                    } else {
+                        logAdbConnectionChanged(key, AdbProtoEnums.AWAITING_USER_APPROVAL, false);
+                        mFingerprints = fingerprints;
+                        startConfirmation(key, mFingerprints);
+                    }
                     break;
                 }
 
-                case MESSAGE_ADB_CLEAR:
+                case MESSAGE_ADB_CLEAR: {
                     deleteKeyFile();
+                    mConnectedKey = null;
+                    mAdbKeyStore.deleteKeyStore();
+                    cancelJobToUpdateAdbKeyStore();
                     break;
+                }
+
+                case MESSAGE_ADB_DISCONNECT: {
+                    if (mConnectedKey != null) {
+                        mAdbKeyStore.setLastConnectionTime(mConnectedKey,
+                                System.currentTimeMillis());
+                        cancelJobToUpdateAdbKeyStore();
+                    }
+                    logAdbConnectionChanged(mConnectedKey, AdbProtoEnums.DISCONNECTED,
+                            (mConnectedKey != null));
+                    mConnectedKey = null;
+                    break;
+                }
+
+                case MESSAGE_ADB_PERSIST_KEY_STORE: {
+                    mAdbKeyStore.persistKeyStore();
+                    break;
+                }
+
+                case MESSAGE_ADB_UPDATE_KEY_CONNECTION_TIME: {
+                    if (mConnectedKey != null) {
+                        mAdbKeyStore.setLastConnectionTime(mConnectedKey,
+                                System.currentTimeMillis());
+                        scheduleJobToUpdateAdbKeyStore();
+                    }
+                    break;
+                }
             }
         }
+
+        private void logAdbConnectionChanged(String key, int state, boolean alwaysAllow) {
+            long lastConnectionTime = mAdbKeyStore.getLastConnectionTime(key);
+            long authWindow = mAdbKeyStore.getAllowedConnectionTime();
+            StatsLog.write(StatsLog.ADB_CONNECTION_CHANGED, lastConnectionTime, authWindow, state,
+                    alwaysAllow);
+        }
     }
 
     private String getFingerprints(String key) {
@@ -335,7 +454,8 @@
         UserInfo userInfo = UserManager.get(mContext).getUserInfo(currentUserId);
         String componentString;
         if (userInfo.isAdmin()) {
-            componentString = Resources.getSystem().getString(
+            componentString = mConfirmComponent != null
+                    ? mConfirmComponent : Resources.getSystem().getString(
                     com.android.internal.R.string.config_customAdbPublicKeyConfirmationComponent);
         } else {
             // If the current foreground user is not the admin user we send a different
@@ -397,7 +517,10 @@
         return intent;
     }
 
-    private File getUserKeyFile() {
+    /**
+     * Returns a new File with the specified name in the adb directory.
+     */
+    private File getAdbFile(String fileName) {
         File dataDir = Environment.getDataDirectory();
         File adbDir = new File(dataDir, ADB_DIRECTORY);
 
@@ -406,7 +529,11 @@
             return null;
         }
 
-        return new File(adbDir, ADB_KEYS_FILE);
+        return new File(adbDir, fileName);
+    }
+
+    private File getUserKeyFile() {
+        return getAdbFile(ADB_KEYS_FILE);
     }
 
     private void writeKey(String key) {
@@ -476,6 +603,36 @@
     }
 
     /**
+     * Sends a message to the handler to persist the key store.
+     */
+    private void sendPersistKeyStoreMessage() {
+        Message msg = mHandler.obtainMessage(AdbDebuggingHandler.MESSAGE_ADB_PERSIST_KEY_STORE);
+        mHandler.sendMessage(msg);
+    }
+
+    /**
+     * Schedules a job to update the connection time of the currently connected key. This is
+     * intended for cases such as development devices that are left connected to a user's
+     * system beyond the window within which a connection is allowed without user interaction.
+     * A job should be rescheduled daily so that if the device is rebooted while connected to
+     * the user's system the last time in the key store will show within 24 hours which should
+     * be within the allowed window.
+     */
+    private void scheduleJobToUpdateAdbKeyStore() {
+        Message message = mHandler.obtainMessage(
+                AdbDebuggingHandler.MESSAGE_ADB_UPDATE_KEY_CONNECTION_TIME);
+        mHandler.sendMessageDelayed(message, AdbDebuggingHandler.UPDATE_KEY_STORE_JOB_INTERVAL);
+    }
+
+    /**
+     * Cancels the scheduled job to update the connection time of the currently connected key.
+     * This should be invoked once the adb session is disconnected.
+     */
+    private void cancelJobToUpdateAdbKeyStore() {
+        mHandler.removeMessages(AdbDebuggingHandler.MESSAGE_ADB_UPDATE_KEY_CONNECTION_TIME);
+    }
+
+    /**
      * Dump the USB debugging state.
      */
     public void dump(DualDumpOutputStream dump, String idName, long id) {
@@ -501,4 +658,231 @@
 
         dump.end(token);
     }
+
+    /**
+     * Handles adb keys for which the user has granted the 'always allow' option. This class ensures
+     * these grants are revoked after a period of inactivity as specified in the
+     * ADB_ALLOWED_CONNECTION_TIME setting.
+     */
+    class AdbKeyStore {
+        private Map<String, Long> mKeyMap;
+        private File mKeyFile;
+        private AtomicFile mAtomicKeyFile;
+        // This file contains keys that will be allowed to connect without user interaction as long
+        // as a subsequent connection occurs within the allowed duration.
+        private static final String ADB_TEMP_KEYS_FILE = "adb_temp_keys.xml";
+        private static final String XML_TAG_ADB_KEY = "adbKey";
+        private static final String XML_ATTRIBUTE_KEY = "key";
+        private static final String XML_ATTRIBUTE_LAST_CONNECTION = "lastConnection";
+
+        /**
+         * Value returned by {@code getLastConnectionTime} when there is no previously saved
+         * connection time for the specified key.
+         */
+        public static final long NO_PREVIOUS_CONNECTION = 0;
+
+        /**
+         * Constructor that uses the default location for the persistent adb key store.
+         */
+        AdbKeyStore() {
+            initKeyFile();
+            mKeyMap = getKeyMapFromFile();
+        }
+
+        /**
+         * Constructor that uses the specified file as the location for the persistent adb key
+         * store.
+         */
+        AdbKeyStore(File keyFile) {
+            mKeyFile = keyFile;
+            initKeyFile();
+            mKeyMap = getKeyMapFromFile();
+        }
+
+        /**
+         * Initializes the key file that will be used to persist the adb grants.
+         */
+        private void initKeyFile() {
+            if (mKeyFile == null) {
+                mKeyFile = getAdbFile(ADB_TEMP_KEYS_FILE);
+            }
+            // getAdbFile can return null if the adb file cannot be obtained
+            if (mKeyFile != null) {
+                mAtomicKeyFile = new AtomicFile(mKeyFile);
+            }
+        }
+
+        /**
+         * Returns the key map with the keys and last connection times from the key file.
+         */
+        private Map<String, Long> getKeyMapFromFile() {
+            Map<String, Long> keyMap = new HashMap<String, Long>();
+            // if the AtomicFile could not be instantiated before attempt again; if it still fails
+            // return an empty key map.
+            if (mAtomicKeyFile == null) {
+                initKeyFile();
+                if (mAtomicKeyFile == null) {
+                    Slog.e(TAG, "Unable to obtain the key file, " + mKeyFile + ", for reading");
+                    return keyMap;
+                }
+            }
+            if (!mAtomicKeyFile.exists()) {
+                return keyMap;
+            }
+            try (FileInputStream keyStream = mAtomicKeyFile.openRead()) {
+                XmlPullParser parser = Xml.newPullParser();
+                parser.setInput(keyStream, StandardCharsets.UTF_8.name());
+                XmlUtils.beginDocument(parser, XML_TAG_ADB_KEY);
+                while (parser.next() != XmlPullParser.END_DOCUMENT) {
+                    String tagName = parser.getName();
+                    if (tagName == null) {
+                        break;
+                    } else if (!tagName.equals(XML_TAG_ADB_KEY)) {
+                        XmlUtils.skipCurrentTag(parser);
+                        continue;
+                    }
+                    String key = parser.getAttributeValue(null, XML_ATTRIBUTE_KEY);
+                    long connectionTime;
+                    try {
+                        connectionTime = Long.valueOf(
+                                parser.getAttributeValue(null, XML_ATTRIBUTE_LAST_CONNECTION));
+                    } catch (NumberFormatException e) {
+                        Slog.e(TAG,
+                                "Caught a NumberFormatException parsing the last connection time: "
+                                        + e);
+                        XmlUtils.skipCurrentTag(parser);
+                        continue;
+                    }
+                    keyMap.put(key, connectionTime);
+                }
+            } catch (IOException | XmlPullParserException e) {
+                Slog.e(TAG, "Caught an exception parsing the XML key file: ", e);
+            }
+            return keyMap;
+        }
+
+        /**
+         * Writes the key map to the key file.
+         */
+        public void persistKeyStore() {
+            // if there is nothing in the key map then ensure any keys left in the key store files
+            // are deleted as well.
+            if (mKeyMap.size() == 0) {
+                deleteKeyStore();
+                return;
+            }
+            if (mAtomicKeyFile == null) {
+                initKeyFile();
+                if (mAtomicKeyFile == null) {
+                    Slog.e(TAG, "Unable to obtain the key file, " + mKeyFile + ", for writing");
+                    return;
+                }
+            }
+            FileOutputStream keyStream = null;
+            try {
+                XmlSerializer serializer = new FastXmlSerializer();
+                keyStream = mAtomicKeyFile.startWrite();
+                serializer.setOutput(keyStream, StandardCharsets.UTF_8.name());
+                serializer.startDocument(null, true);
+                long allowedTime = getAllowedConnectionTime();
+                long systemTime = System.currentTimeMillis();
+                Iterator keyMapIterator = mKeyMap.entrySet().iterator();
+                while (keyMapIterator.hasNext()) {
+                    Map.Entry<String, Long> keyEntry = (Map.Entry) keyMapIterator.next();
+                    long connectionTime = keyEntry.getValue();
+                    if (systemTime < (connectionTime + allowedTime)) {
+                        serializer.startTag(null, XML_TAG_ADB_KEY);
+                        serializer.attribute(null, XML_ATTRIBUTE_KEY, keyEntry.getKey());
+                        serializer.attribute(null, XML_ATTRIBUTE_LAST_CONNECTION,
+                                String.valueOf(keyEntry.getValue()));
+                        serializer.endTag(null, XML_TAG_ADB_KEY);
+                    } else {
+                        keyMapIterator.remove();
+                    }
+                }
+                serializer.endDocument();
+                mAtomicKeyFile.finishWrite(keyStream);
+            } catch (IOException e) {
+                Slog.e(TAG, "Caught an exception writing the key map: ", e);
+                mAtomicKeyFile.failWrite(keyStream);
+            }
+        }
+
+        /**
+         * Removes all of the entries in the key map and deletes the key file.
+         */
+        public void deleteKeyStore() {
+            mKeyMap.clear();
+            if (mAtomicKeyFile == null) {
+                return;
+            }
+            mAtomicKeyFile.delete();
+        }
+
+        /**
+         * Returns the time of the last connection from the specified key, or {@code
+         * NO_PREVIOUS_CONNECTION} if the specified key does not have an active adb grant.
+         */
+        public long getLastConnectionTime(String key) {
+            return mKeyMap.getOrDefault(key, NO_PREVIOUS_CONNECTION);
+        }
+
+        /**
+         * Sets the time of the last connection for the specified key to the provided time.
+         */
+        public void setLastConnectionTime(String key, long connectionTime) {
+            // Do not set the connection time to a value that is earlier than what was previously
+            // stored as the last connection time.
+            if (mKeyMap.containsKey(key) && mKeyMap.get(key) >= connectionTime) {
+                return;
+            }
+            mKeyMap.put(key, connectionTime);
+            sendPersistKeyStoreMessage();
+        }
+
+        /**
+         * Returns whether the specified key should be authroized to connect without user
+         * interaction. This requires that the user previously connected this device and selected
+         * the option to 'Always allow', and the time since the last connection is within the
+         * allowed window.
+         */
+        public boolean isKeyAuthorized(String key) {
+            long lastConnectionTime = getLastConnectionTime(key);
+            if (lastConnectionTime == NO_PREVIOUS_CONNECTION) {
+                return false;
+            }
+            long allowedConnectionTime = getAllowedConnectionTime();
+            // if the allowed connection time is 0 then revert to the previous behavior of always
+            // allowing previously granted adb grants.
+            if (allowedConnectionTime == 0 || (System.currentTimeMillis() < (lastConnectionTime
+                    + allowedConnectionTime))) {
+                return true;
+            } else {
+                // since this key is no longer auhorized remove it from the Map
+                removeKey(key);
+                return false;
+            }
+        }
+
+        /**
+         * Returns the connection time within which a connection from an allowed key is
+         * automatically allowed without user interaction.
+         */
+        public long getAllowedConnectionTime() {
+            return Settings.Global.getLong(mContext.getContentResolver(),
+                    Settings.Global.ADB_ALLOWED_CONNECTION_TIME,
+                    Settings.Global.DEFAULT_ADB_ALLOWED_CONNECTION_TIME);
+        }
+
+        /**
+         * Removes the specified key from the key store.
+         */
+        public void removeKey(String key) {
+            if (!mKeyMap.containsKey(key)) {
+                return;
+            }
+            mKeyMap.remove(key);
+            sendPersistKeyStoreMessage();
+        }
+    }
 }
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 089847d..eb643b6 100644
--- a/services/core/java/com/android/server/am/ActivityManagerService.java
+++ b/services/core/java/com/android/server/am/ActivityManagerService.java
@@ -64,6 +64,7 @@
 import static android.os.Process.SIGNAL_USR1;
 import static android.os.Process.SYSTEM_UID;
 import static android.os.Process.THREAD_PRIORITY_FOREGROUND;
+import static android.os.Process.ZYGOTE_PROCESS;
 import static android.os.Process.getTotalMemory;
 import static android.os.Process.isThreadInProcess;
 import static android.os.Process.killProcess;
@@ -75,7 +76,6 @@
 import static android.os.Process.sendSignal;
 import static android.os.Process.setThreadPriority;
 import static android.os.Process.setThreadScheduler;
-import static android.os.Process.zygoteProcess;
 import static android.provider.Settings.Global.ALWAYS_FINISH_ACTIVITIES;
 import static android.provider.Settings.Global.DEBUG_APP;
 import static android.provider.Settings.Global.NETWORK_ACCESS_TIMEOUT_MS;
@@ -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: {
@@ -2162,7 +2151,7 @@
                             ? Collections.emptyList()
                             : Arrays.asList(exemptions.split(","));
                 }
-                if (!zygoteProcess.setApiBlacklistExemptions(mExemptions)) {
+                if (!ZYGOTE_PROCESS.setApiBlacklistExemptions(mExemptions)) {
                   Slog.e(TAG, "Failed to set API blacklist exemptions!");
                   // leave mExemptionsStr as is, so we don't try to send the same list again.
                   mExemptions = Collections.emptyList();
@@ -2175,7 +2164,7 @@
             }
             if (logSampleRate != -1 && logSampleRate != mLogSampleRate) {
                 mLogSampleRate = logSampleRate;
-                zygoteProcess.setHiddenApiAccessLogSampleRate(mLogSampleRate);
+                ZYGOTE_PROCESS.setHiddenApiAccessLogSampleRate(mLogSampleRate);
             }
             mPolicy = getValidEnforcementPolicy(Settings.Global.HIDDEN_API_POLICY);
         }
@@ -2864,16 +2853,18 @@
      * @param userId
      * @param event
      * @param appToken ActivityRecord's appToken.
+     * @param taskRoot TaskRecord's root
      */
     public void updateActivityUsageStats(ComponentName activity, int userId, int event,
-            IBinder appToken) {
+            IBinder appToken, ComponentName taskRoot) {
         if (DEBUG_SWITCH) {
             Slog.d(TAG_SWITCH, "updateActivityUsageStats: comp="
                     + activity + " hash=" + appToken.hashCode() + " event=" + event);
         }
         synchronized (this) {
             if (mUsageStatsService != null) {
-                mUsageStatsService.reportEvent(activity, userId, event, appToken.hashCode());
+                mUsageStatsService.reportEvent(activity, userId, event, appToken.hashCode(),
+                        taskRoot);
             }
         }
     }
@@ -2911,7 +2902,7 @@
             if (mUsageStatsService != null) {
                 mUsageStatsService.reportEvent(service, userId,
                         started ? UsageEvents.Event.FOREGROUND_SERVICE_START
-                                : UsageEvents.Event.FOREGROUND_SERVICE_STOP, 0);
+                                : UsageEvents.Event.FOREGROUND_SERVICE_STOP, 0, null);
             }
         }
     }
@@ -4880,7 +4871,7 @@
 
         ArraySet<String> completedIsas = new ArraySet<String>();
         for (String abi : Build.SUPPORTED_ABIS) {
-            zygoteProcess.establishZygoteConnectionForAbi(abi);
+            ZYGOTE_PROCESS.establishZygoteConnectionForAbi(abi);
             final String instructionSet = VMRuntime.getInstructionSet(abi);
             if (!completedIsas.contains(instructionSet)) {
                 try {
@@ -7226,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.
@@ -9338,6 +9330,7 @@
 
         synchronized(this) {
             mConstants.dump(pw);
+            mOomAdjuster.dumpAppCompactorSettings(pw);
             pw.println();
             if (dumpAll) {
                 pw.println("-------------------------------------------------------------------------------");
@@ -9737,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) {
@@ -14654,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:
@@ -17521,10 +17514,10 @@
 
         @Override
         public void updateActivityUsageStats(ComponentName activity, int userId, int event,
-                IBinder appToken) {
+                IBinder appToken, ComponentName taskRoot) {
             synchronized (ActivityManagerService.this) {
                 ActivityManagerService.this.updateActivityUsageStats(activity, userId, event,
-                        appToken);
+                        appToken, taskRoot);
             }
         }
 
diff --git a/services/core/java/com/android/server/am/AppCompactor.java b/services/core/java/com/android/server/am/AppCompactor.java
index fe27c49..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 = "full";
+    // 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 65aacdc..cdf6e0e 100644
--- a/services/core/java/com/android/server/am/BroadcastQueue.java
+++ b/services/core/java/com/android/server/am/BroadcastQueue.java
@@ -45,6 +45,7 @@
 import android.os.UserHandle;
 import android.util.EventLog;
 import android.util.Slog;
+import android.util.StatsLog;
 import android.util.TimeUtils;
 import android.util.proto.ProtoOutputStream;
 
@@ -157,6 +158,9 @@
     static final int BROADCAST_INTENT_MSG = ActivityManagerService.FIRST_BROADCAST_QUEUE_MSG;
     static final int BROADCAST_TIMEOUT_MSG = ActivityManagerService.FIRST_BROADCAST_QUEUE_MSG + 1;
 
+    // log latency metrics for ordered broadcasts during BOOT_COMPLETED processing
+    boolean mLogLatencyMetrics = true;
+
     final BroadcastHandler mHandler;
 
     private final class BroadcastHandler extends Handler {
@@ -772,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));
@@ -941,6 +946,12 @@
                     // adjustments.
                     mService.updateOomAdjLocked();
                 }
+
+                // when we have no more ordered broadcast on this queue, stop logging
+                if (mService.mUserController.mBootCompleted && mLogLatencyMetrics) {
+                    mLogLatencyMetrics = false;
+                }
+
                 return;
             }
             r = mOrderedBroadcasts.get(0);
@@ -1036,6 +1047,13 @@
         if (recIdx == 0) {
             r.dispatchTime = r.receiverTime;
             r.dispatchClockTime = System.currentTimeMillis();
+
+            if (mLogLatencyMetrics) {
+                StatsLog.write(
+                        StatsLog.BROADCAST_DISPATCH_LATENCY_REPORTED,
+                        r.dispatchClockTime - r.enqueueClockTime);
+            }
+
             if (Trace.isTagEnabled(Trace.TRACE_TAG_ACTIVITY_MANAGER)) {
                 Trace.asyncTraceEnd(Trace.TRACE_TAG_ACTIVITY_MANAGER,
                     createBroadcastTraceTitle(r, BroadcastRecord.DELIVERY_PENDING),
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/am/UserController.java b/services/core/java/com/android/server/am/UserController.java
index bcce052..c981e68 100644
--- a/services/core/java/com/android/server/am/UserController.java
+++ b/services/core/java/com/android/server/am/UserController.java
@@ -240,6 +240,8 @@
 
     private final LockPatternUtils mLockPatternUtils;
 
+    volatile boolean mBootCompleted;
+
     UserController(ActivityManagerService service) {
         this(new Injector(service));
     }
@@ -567,6 +569,7 @@
                             Bundle extras, boolean ordered, boolean sticky, int sendingUser)
                             throws RemoteException {
                         Slog.i(UserController.TAG, "Finished processing BOOT_COMPLETED for u" + userId);
+                        mBootCompleted = true;
                     }
                 }, 0, null, null,
                 new String[]{android.Manifest.permission.RECEIVE_BOOT_COMPLETED},
diff --git a/services/core/java/com/android/server/attention/AttentionManagerService.java b/services/core/java/com/android/server/attention/AttentionManagerService.java
index f60d6b0..b71a751 100644
--- a/services/core/java/com/android/server/attention/AttentionManagerService.java
+++ b/services/core/java/com/android/server/attention/AttentionManagerService.java
@@ -47,6 +47,7 @@
 import android.text.TextUtils;
 import android.util.Slog;
 import android.util.SparseArray;
+import android.util.StatsLog;
 
 import com.android.internal.R;
 import com.android.internal.annotations.GuardedBy;
@@ -73,7 +74,6 @@
 
     private final Context mContext;
     private final PowerManager mPowerManager;
-    private final ActivityManager mActivityManager;
     private final Object mLock;
     @GuardedBy("mLock")
     private final SparseArray<UserState> mUserStates = new SparseArray<>();
@@ -85,7 +85,6 @@
         super(context);
         mContext = Preconditions.checkNotNull(context);
         mPowerManager = (PowerManager) mContext.getSystemService(Context.POWER_SERVICE);
-        mActivityManager = (ActivityManager) context.getSystemService(Context.ACTIVITY_SERVICE);
         mLock = new Object();
         mAttentionHandler = new AttentionHandler();
     }
@@ -96,7 +95,7 @@
     }
 
     @Override
-    public void onStopUser(int userId) {
+    public void onSwitchUser(int userId) {
         cancelAndUnbindLocked(peekUserStateLocked(userId),
                 AttentionService.ATTENTION_FAILURE_UNKNOWN);
     }
@@ -175,14 +174,20 @@
                         @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);
                         }
 
                         @Override
                         public void onFailure(int requestCode, int error) {
                             callback.onFailure(requestCode, error);
+                            StatsLog.write(StatsLog.ATTENTION_MANAGER_SERVICE_RESULT_REPORTED,
+                                    error);
                         }
 
                         @Override
@@ -201,11 +206,20 @@
 
     /** Cancels the specified attention check. */
     public void cancelAttentionCheck(int requestCode) {
-        final UserState userState = getOrCreateCurrentUserStateLocked();
-        try {
-            userState.mService.cancelAttentionCheck(requestCode);
-        } catch (RemoteException e) {
-            Slog.e(LOG_TAG, "Cannot call into the AttentionService");
+        synchronized (mLock) {
+            final UserState userState = getOrCreateCurrentUserStateLocked();
+            if (userState.mService == null) {
+                if (userState.mPendingAttentionCheck != null
+                        && userState.mPendingAttentionCheck.mRequestCode == requestCode) {
+                    userState.mPendingAttentionCheck = null;
+                }
+                return;
+            }
+            try {
+                userState.mService.cancelAttentionCheck(requestCode);
+            } catch (RemoteException e) {
+                Slog.e(LOG_TAG, "Cannot call into the AttentionService");
+            }
         }
     }
 
@@ -224,7 +238,7 @@
 
     @GuardedBy("mLock")
     private UserState getOrCreateCurrentUserStateLocked() {
-        return getOrCreateUserStateLocked(mActivityManager.getCurrentUser());
+        return getOrCreateUserStateLocked(ActivityManager.getCurrentUser());
     }
 
     @GuardedBy("mLock")
@@ -239,7 +253,7 @@
 
     @GuardedBy("mLock")
     UserState peekCurrentUserStateLocked() {
-        return peekUserStateLocked(mActivityManager.getCurrentUser());
+        return peekUserStateLocked(ActivityManager.getCurrentUser());
     }
 
     @GuardedBy("mLock")
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 11299b6..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();
@@ -1223,11 +1046,13 @@
 
     private void checkMuteAffectedStreams() {
         // any stream with a min level > 0 is not muteable by definition
-        // STREAM_VOICE_CALL can be muted by applications that has the the MODIFY_PHONE_STATE permission.
+        // STREAM_VOICE_CALL and STREAM_BLUETOOTH_SCO can be muted by applications
+        // that has the the MODIFY_PHONE_STATE permission.
         for (int i = 0; i < mStreamStates.length; i++) {
             final VolumeStreamState vss = mStreamStates[i];
             if (vss.mIndexMin > 0 &&
-                vss.mStreamType != AudioSystem.STREAM_VOICE_CALL) {
+                (vss.mStreamType != AudioSystem.STREAM_VOICE_CALL &&
+                vss.mStreamType != AudioSystem.STREAM_BLUETOOTH_SCO)) {
                 mMuteAffectedStreams &= ~(1 << vss.mStreamType);
             }
         }
@@ -1371,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);
@@ -1400,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);
         }
     }
 
@@ -1630,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;
@@ -1688,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());
@@ -1711,10 +1531,11 @@
             return;
         }
 
-        // If adjust is mute and the stream is STREAM_VOICE_CALL, make sure
+        // If adjust is mute and the stream is STREAM_VOICE_CALL or STREAM_BLUETOOTH_SCO, make sure
         // that the calling app have the MODIFY_PHONE_STATE permission.
         if (isMuteAdjust &&
-            streamType == AudioSystem.STREAM_VOICE_CALL &&
+            (streamType == AudioSystem.STREAM_VOICE_CALL ||
+                streamType == AudioSystem.STREAM_BLUETOOTH_SCO) &&
             mContext.checkCallingOrSelfPermission(
                 android.Manifest.permission.MODIFY_PHONE_STATE)
                     != PackageManager.PERMISSION_GRANTED) {
@@ -1868,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.
@@ -2038,16 +1861,18 @@
                     + " CHANGE_ACCESSIBILITY_VOLUME  callingPackage=" + callingPackage);
             return;
         }
-        if ((streamType == AudioManager.STREAM_VOICE_CALL) &&
+        if ((streamType == AudioManager.STREAM_VOICE_CALL ||
+                streamType == AudioManager.STREAM_BLUETOOTH_SCO) &&
                 (index == 0) &&
                 (mContext.checkCallingOrSelfPermission(
                 android.Manifest.permission.MODIFY_PHONE_STATE)
                     != PackageManager.PERMISSION_GRANTED)) {
-            Log.w(TAG, "Trying to call setStreamVolume() for STREAM_VOICE_CALL and index 0 without"
+            Log.w(TAG, "Trying to call setStreamVolume() for STREAM_VOICE_CALL or"
+                    + " STREAM_BLUETOOTH_SCO and index 0 without"
                     + " 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());
@@ -2122,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) {
@@ -2876,6 +2703,10 @@
         }
     }
 
+    /*package*/ void setUpdateRingerModeServiceInt() {
+        setRingerModeInt(getRingerModeInternal(), false);
+    }
+
     /** @see AudioManager#shouldVibrate(int) */
     public boolean shouldVibrate(int vibrateType) {
         if (!mHasVibrator) return false;
@@ -2916,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
@@ -2929,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();
@@ -2944,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);
             }
         }
 
@@ -2989,7 +2818,7 @@
 
         int oldModeOwnerPid = 0;
         int newModeOwnerPid = 0;
-        synchronized(mSetModeDeathHandlers) {
+        synchronized (mDeviceBroker.mSetModeLock) {
             if (!mSetModeDeathHandlers.isEmpty()) {
                 oldModeOwnerPid = mSetModeDeathHandlers.get(0).getPid();
             }
@@ -3001,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) {
@@ -3375,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) */
@@ -3405,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;
         }
 
@@ -3413,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() */
@@ -3510,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) {
@@ -4168,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) {
@@ -4187,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];
@@ -4196,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);
@@ -4513,7 +3656,7 @@
                 || adjust == AudioManager.ADJUST_TOGGLE_MUTE;
     }
 
-    private boolean isInCommunication() {
+    /*package*/ boolean isInCommunication() {
         boolean IsInCall = false;
 
         TelecomManager telecomManager =
@@ -4666,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) {
@@ -4699,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:
@@ -4744,154 +3871,94 @@
         }
     }
 
-    private int getA2dpCodec(BluetoothDevice device) {
-        synchronized (mA2dpAvrcpLock) {
-            if (mA2dp != null) {
-                BluetoothCodecStatus btCodecStatus = mA2dp.getCodecStatus(device);
-                BluetoothCodecConfig btCodecConfig = btCodecStatus.getCodecConfig();
-                return mapBluetoothCodecToAudioFormat(btCodecConfig.getCodecType());
-            }
-            return AudioSystem.AUDIO_FORMAT_DEFAULT;
-        }
+
+    /*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);
     }
 
     /**
@@ -4901,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 =
@@ -4956,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);
         }
     }
 
@@ -4983,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 {
@@ -5127,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;
@@ -5140,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;
@@ -5153,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;
@@ -5162,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
@@ -5172,7 +4209,7 @@
                 } else {
                     index = (getIndex(AudioSystem.DEVICE_OUT_DEFAULT) + 5)/10;
                 }
-                AudioSystem.setStreamVolumeIndex(
+                AudioSystem.setStreamVolumeIndexAS(
                         mStreamType, index, AudioSystem.DEVICE_OUT_DEFAULT);
             }
         }
@@ -5362,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) {
@@ -5372,7 +4410,7 @@
                                 || (((device & mFixedVolumeDevices) != 0) && index != 0)) {
                             mIndexMap.put(device, mIndexMax);
                         }
-                        applyDeviceVolume_syncVSS(device);
+                        applyDeviceVolume_syncVSS(device, isAvrcpAbsVolSupported);
                     }
                 }
             }
@@ -5454,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();
@@ -5468,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);
                 }
             }
         }
@@ -5751,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,
@@ -5823,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:
@@ -5881,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),
@@ -5919,10 +4894,6 @@
                     onPersistSafeVolumeState(msg.arg1);
                     break;
 
-                case MSG_BROADCAST_BT_CONNECTION_STATE:
-                    onBroadcastScoConnectionState(msg.arg1);
-                    break;
-
                 case MSG_SYSTEM_READY:
                     onSystemReady();
                     break;
@@ -6022,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 {
@@ -6044,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;
@@ -6567,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;
@@ -6801,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();
@@ -6887,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
@@ -6927,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)) {
@@ -7167,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());
@@ -7191,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);
@@ -7226,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);
     }
 
 
@@ -7272,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.
@@ -7427,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);
             }
@@ -7542,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,
@@ -7602,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);
@@ -7619,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);
@@ -7632,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) {
@@ -8259,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/connectivity/IpConnectivityMetrics.java b/services/core/java/com/android/server/connectivity/IpConnectivityMetrics.java
index ac74598..79b56c6 100644
--- a/services/core/java/com/android/server/connectivity/IpConnectivityMetrics.java
+++ b/services/core/java/com/android/server/connectivity/IpConnectivityMetrics.java
@@ -20,7 +20,6 @@
 import android.net.ConnectivityMetricsEvent;
 import android.net.IIpConnectivityMetrics;
 import android.net.INetdEventCallback;
-import android.net.ip.IpClient;
 import android.net.metrics.ApfProgramEvent;
 import android.net.metrics.IpConnectivityLog;
 import android.os.Binder;
@@ -270,8 +269,6 @@
         // Dump the rolling buffer of metrics event and pretty print events using a human readable
         // format. Also print network dns/connect statistics and default network event time series.
         static final String CMD_LIST = "list";
-        // Dump all IpClient logs ("ipclient").
-        static final String CMD_IPCLIENT = IpClient.DUMP_ARG;
         // By default any other argument will fall into the default case which is the equivalent
         // of calling both the "list" and "ipclient" commands. This includes most notably bug
         // reports collected by dumpsys.cpp with the "-a" argument.
@@ -295,20 +292,9 @@
                 case CMD_PROTO:
                     cmdListAsProto(pw);
                     return;
-                case CMD_IPCLIENT: {
-                    final String[] ipclientArgs = ((args != null) && (args.length > 1))
-                            ? Arrays.copyOfRange(args, 1, args.length)
-                            : null;
-                    IpClient.dumpAllLogs(pw, ipclientArgs);
-                    return;
-                }
                 case CMD_LIST:
-                    cmdList(pw);
-                    return;
                 default:
                     cmdList(pw);
-                    pw.println("");
-                    IpClient.dumpAllLogs(pw, null);
                     return;
             }
         }
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 602aedb..62a1b03 100644
--- a/services/core/java/com/android/server/connectivity/Vpn.java
+++ b/services/core/java/com/android/server/connectivity/Vpn.java
@@ -60,7 +60,6 @@
 import android.net.NetworkUtils;
 import android.net.RouteInfo;
 import android.net.UidRange;
-import android.net.Uri;
 import android.net.VpnService;
 import android.os.Binder;
 import android.os.Build.VERSION_CODES;
@@ -71,7 +70,6 @@
 import android.os.Looper;
 import android.os.Parcel;
 import android.os.ParcelFileDescriptor;
-import android.os.PatternMatcher;
 import android.os.Process;
 import android.os.RemoteException;
 import android.os.SystemClock;
@@ -100,6 +98,8 @@
 import com.android.server.LocalServices;
 import com.android.server.net.BaseNetworkObserver;
 
+import libcore.io.IoUtils;
+
 import java.io.File;
 import java.io.IOException;
 import java.io.InputStream;
@@ -121,8 +121,6 @@
 import java.util.TreeSet;
 import java.util.concurrent.atomic.AtomicInteger;
 
-import libcore.io.IoUtils;
-
 /**
  * @hide
  */
@@ -346,11 +344,18 @@
      *
      * @return {@code true} if VPN lockdown is enabled.
      */
-    public boolean getLockdown() {
+    public synchronized boolean getLockdown() {
         return mLockdown;
     }
 
     /**
+     * Returns whether VPN is configured as always-on.
+     */
+    public synchronized boolean getAlwaysOn() {
+        return mAlwaysOn;
+    }
+
+    /**
      * Checks if a VPN app supports always-on mode.
      *
      * In order to support the always-on feature, an app has to
@@ -788,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 9223739..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;
@@ -28,6 +35,7 @@
 import android.annotation.NonNull;
 import android.annotation.Nullable;
 import android.annotation.Size;
+import android.annotation.UserIdInt;
 import android.app.AlarmManager;
 import android.content.BroadcastReceiver;
 import android.content.ContentResolver;
@@ -39,21 +47,25 @@
 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;
 import android.util.MathUtils;
 import android.util.Slog;
+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;
@@ -63,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;
@@ -101,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
@@ -196,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]);
@@ -207,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];
@@ -276,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,
@@ -384,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];
@@ -419,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,
@@ -443,7 +412,7 @@
 
     public ColorDisplayService(Context context) {
         super(context);
-        mHandler = new TintHandler(Looper.getMainLooper());
+        mHandler = new TintHandler(DisplayThread.get().getLooper());
     }
 
     @Override
@@ -546,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());
@@ -622,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());
             }
         }
 
@@ -663,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);
 
@@ -688,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();
         }
 
@@ -715,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;
         }
 
@@ -730,7 +684,7 @@
 
         mNightDisplayTintController
                 .setUp(getContext(), DisplayTransformManager.needsLinearColorMatrix(mode));
-        mNightDisplayTintController.setMatrix(mNightDisplayController.getColorTemperature());
+        mNightDisplayTintController.setMatrix(getNightDisplayColorTemperatureSetting());
 
         updateDisplayWhiteBalanceStatus();
 
@@ -866,6 +820,12 @@
         if (mDisplayWhiteBalanceListener != null && oldActivated != activated) {
             mDisplayWhiteBalanceListener.onDisplayWhiteBalanceStatusChanged(activated);
         }
+
+        // If disabled, clear the tint. If enabled, do nothing more here and let the next
+        // temperature update set the correct tint.
+        if (!activated) {
+            applyTint(mDisplayWhiteBalanceTintController, false);
+        }
     }
 
     private boolean isDisplayWhiteBalanceSettingEnabled() {
@@ -878,6 +838,86 @@
         return dtm.isDeviceColorManaged();
     }
 
+    private int getTransformCapabilitiesInternal() {
+        int availabilityFlags = ColorDisplayManager.CAPABILITY_NONE;
+        if (SurfaceControl.getProtectedContentSupport()) {
+            availabilityFlags |= ColorDisplayManager.CAPABILITY_PROTECTED_CONTENT;
+        }
+        final Resources res = getContext().getResources();
+        if (res.getBoolean(R.bool.config_setColorTransformAccelerated)) {
+            availabilityFlags |= ColorDisplayManager.CAPABILITY_HARDWARE_ACCELERATION_GLOBAL;
+        }
+        if (res.getBoolean(R.bool.config_setColorTransformAcceleratedPerLayer)) {
+            availabilityFlags |= ColorDisplayManager.CAPABILITY_HARDWARE_ACCELERATION_PER_APP;
+        }
+        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.
@@ -907,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:");
@@ -918,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);
@@ -964,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);
@@ -991,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();
 
@@ -1060,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);
             }
         }
 
@@ -1188,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.
      */
@@ -1226,10 +1484,10 @@
          * Adds a {@link WeakReference<ColorTransformController>} for a newly started activity, and
          * invokes {@link ColorTransformController#applyAppSaturation(float[], float[])} if needed.
          */
-        public boolean attachColorTransformController(String packageName, int uid,
+        public boolean attachColorTransformController(String packageName, @UserIdInt int userId,
                 WeakReference<ColorTransformController> controller) {
             return mAppSaturationController
-                    .addColorTransformController(packageName, uid, controller);
+                    .addColorTransformController(packageName, userId, controller);
         }
     }
 
@@ -1247,7 +1505,7 @@
 
     private final class TintHandler extends Handler {
 
-        TintHandler(Looper looper) {
+        private TintHandler(Looper looper) {
             super(looper, null, true /* async */);
         }
 
@@ -1258,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;
             }
         }
     }
@@ -1267,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() {
@@ -1318,9 +1608,152 @@
             }
         }
 
+        public int getTransformCapabilities() {
+            getContext().enforceCallingPermission(
+                    Manifest.permission.CONTROL_DISPLAY_COLOR_TRANSFORMS,
+                    "Permission required to query transform capabilities");
+            final long token = Binder.clearCallingIdentity();
+            try {
+                return getTransformCapabilitiesInternal();
+            } finally {
+                Binder.restoreCallingIdentity(token);
+            }
+        }
+
+        @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/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/hdmi/HdmiCecLocalDeviceAudioSystem.java b/services/core/java/com/android/server/hdmi/HdmiCecLocalDeviceAudioSystem.java
index 63214ed..cac1a95 100644
--- a/services/core/java/com/android/server/hdmi/HdmiCecLocalDeviceAudioSystem.java
+++ b/services/core/java/com/android/server/hdmi/HdmiCecLocalDeviceAudioSystem.java
@@ -268,7 +268,7 @@
         mTvSystemAudioModeSupport = false;
         // Record the last state of System Audio Control before going to standby
         synchronized (mLock) {
-            mService.writeStringSetting(
+            mService.writeStringSystemProperty(
                     Constants.PROPERTY_LAST_SYSTEM_AUDIO_CONTROL,
                     mSystemAudioActivated ? "true" : "false");
         }
@@ -330,7 +330,7 @@
     @ServiceThreadOnly
     protected void setPreferredAddress(int addr) {
         assertRunOnServiceThread();
-        mService.writeStringSetting(
+        mService.writeStringSystemProperty(
                 Constants.PROPERTY_PREFERRED_ADDRESS_AUDIO_SYSTEM, String.valueOf(addr));
     }
 
@@ -469,7 +469,7 @@
     protected boolean handleRequestArcInitiate(HdmiCecMessage message) {
         assertRunOnServiceThread();
         removeAction(ArcInitiationActionFromAvr.class);
-        if (!mService.readBooleanSetting(Constants.PROPERTY_ARC_SUPPORT, true)) {
+        if (!mService.readBooleanSystemProperty(Constants.PROPERTY_ARC_SUPPORT, true)) {
             mService.maySendFeatureAbortCommand(message, Constants.ABORT_UNRECOGNIZED_OPCODE);
         } else if (!isDirectConnectToTv()) {
             HdmiLogger.debug("AVR device is not directly connected with TV");
@@ -829,7 +829,7 @@
         boolean currentMuteStatus =
                 mService.getAudioManager().isStreamMute(AudioManager.STREAM_MUSIC);
         if (currentMuteStatus == newSystemAudioMode) {
-            if (mService.readBooleanSetting(
+            if (mService.readBooleanSystemProperty(
                     Constants.PROPERTY_SYSTEM_AUDIO_MODE_MUTING_ENABLE, true)
                             || newSystemAudioMode) {
                 mService.getAudioManager()
diff --git a/services/core/java/com/android/server/hdmi/HdmiCecLocalDevicePlayback.java b/services/core/java/com/android/server/hdmi/HdmiCecLocalDevicePlayback.java
index 7a0c279..ef7d241 100644
--- a/services/core/java/com/android/server/hdmi/HdmiCecLocalDevicePlayback.java
+++ b/services/core/java/com/android/server/hdmi/HdmiCecLocalDevicePlayback.java
@@ -100,7 +100,7 @@
     @ServiceThreadOnly
     protected void setPreferredAddress(int addr) {
         assertRunOnServiceThread();
-        mService.writeStringSetting(Constants.PROPERTY_PREFERRED_ADDRESS_PLAYBACK,
+        mService.writeStringSystemProperty(Constants.PROPERTY_PREFERRED_ADDRESS_PLAYBACK,
                 String.valueOf(addr));
     }
 
diff --git a/services/core/java/com/android/server/hdmi/HdmiControlService.java b/services/core/java/com/android/server/hdmi/HdmiControlService.java
index 46219d5..f3a1e46 100644
--- a/services/core/java/com/android/server/hdmi/HdmiControlService.java
+++ b/services/core/java/com/android/server/hdmi/HdmiControlService.java
@@ -657,9 +657,13 @@
         Global.putInt(cr, key, toInt(value));
     }
 
-    void writeStringSetting(String key, String value) {
-        ContentResolver cr = getContext().getContentResolver();
-        Global.putString(cr, key, value);
+    void writeStringSystemProperty(String key, String value) {
+        SystemProperties.set(key, value);
+    }
+
+    @VisibleForTesting
+    boolean readBooleanSystemProperty(String key, boolean defVal) {
+        return SystemProperties.getBoolean(key, defVal);
     }
 
     private void initializeCec(int initiatedBy) {
diff --git a/services/core/java/com/android/server/inputmethod/InputMethodManagerInternal.java b/services/core/java/com/android/server/inputmethod/InputMethodManagerInternal.java
index d4b8eb2..944a95d 100644
--- a/services/core/java/com/android/server/inputmethod/InputMethodManagerInternal.java
+++ b/services/core/java/com/android/server/inputmethod/InputMethodManagerInternal.java
@@ -18,7 +18,6 @@
 
 import android.annotation.NonNull;
 import android.annotation.UserIdInt;
-import android.content.ComponentName;
 import android.view.inputmethod.InputMethodInfo;
 
 import com.android.server.LocalServices;
@@ -42,11 +41,6 @@
     public abstract void hideCurrentInputMethod();
 
     /**
-     * Switches to VR InputMethod defined in the packageName of {@param componentName}.
-     */
-    public abstract void startVrInputMethodNoCheck(ComponentName componentName);
-
-    /**
      * Returns the list of installed input methods for the specified user.
      *
      * @param userId The user ID to be queried.
@@ -76,10 +70,6 @@
                 }
 
                 @Override
-                public void startVrInputMethodNoCheck(ComponentName componentName) {
-                }
-
-                @Override
                 public List<InputMethodInfo> getInputMethodListAsUser(int userId) {
                     return Collections.emptyList();
                 }
diff --git a/services/core/java/com/android/server/inputmethod/InputMethodManagerService.java b/services/core/java/com/android/server/inputmethod/InputMethodManagerService.java
index 2d197bb..4db541c 100644
--- a/services/core/java/com/android/server/inputmethod/InputMethodManagerService.java
+++ b/services/core/java/com/android/server/inputmethod/InputMethodManagerService.java
@@ -91,8 +91,6 @@
 import android.os.UserManager;
 import android.os.UserManagerInternal;
 import android.provider.Settings;
-import android.service.vr.IVrManager;
-import android.service.vr.IVrStateCallbacks;
 import android.text.TextUtils;
 import android.text.style.SuggestionSpan;
 import android.util.ArrayMap;
@@ -203,7 +201,6 @@
     static final int MSG_CREATE_SESSION = 1050;
 
     static final int MSG_START_INPUT = 2000;
-    static final int MSG_START_VR_INPUT = 2010;
 
     static final int MSG_UNBIND_CLIENT = 3000;
     static final int MSG_BIND_CLIENT = 3010;
@@ -291,6 +288,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
@@ -307,6 +306,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;
@@ -381,28 +381,6 @@
         }
     }
 
-    /**
-     * VR state callback.
-     * Listens for when VR mode finishes.
-     */
-    private final IVrStateCallbacks mVrStateCallbacks = new IVrStateCallbacks.Stub() {
-        @Override
-        public void onVrStateChanged(boolean enabled) {
-            if (!enabled) {
-                restoreNonVrImeFromSettingsNoCheck();
-            }
-        }
-    };
-
-    private void restoreNonVrImeFromSettingsNoCheck() {
-        // switch back to non-VR InputMethod from settings.
-        synchronized (mMethodMap) {
-            if (!mIsVrImeStarted) return;
-            mIsVrImeStarted = false;
-            updateFromSettingsLocked(false);
-        }
-    }
-
     private static final class ClientDeathRecipient implements IBinder.DeathRecipient {
         private final InputMethodManagerService mImms;
         private final IInputMethodClient mClient;
@@ -428,6 +406,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
@@ -597,9 +579,6 @@
 
     final ImeDisplayValidator mImeDisplayValidator;
 
-    /** True if VR IME started by {@link #startVrInputMethodNoCheck}. */
-    boolean mIsVrImeStarted;
-
     /**
      * If non-null, this is the input method service we are currently connected
      * to.
@@ -643,6 +622,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>
      */
@@ -977,31 +960,6 @@
     }
 
     /**
-     * Start a VR InputMethod that matches IME with package name of {@param component}.
-     * Note: This method is called from {@link android.app.VrManager}.
-     */
-    private void startVrInputMethodNoCheck(@Nullable ComponentName component) {
-        if (component == null) {
-            // clear the current VR-only IME (if any) and restore normal IME.
-            restoreNonVrImeFromSettingsNoCheck();
-            return;
-        }
-
-        synchronized (mMethodMap) {
-            String packageName = component.getPackageName();
-            for (InputMethodInfo info : mMethodList) {
-                if (TextUtils.equals(info.getPackageName(), packageName) && info.isVrOnly()) {
-                    // set this is as current inputMethod without updating settings.
-                    setInputMethodEnabledLocked(info.getId(), true);
-                    setInputMethodLocked(info.getId(), NOT_A_SUBTYPE_ID);
-                    mIsVrImeStarted = true;
-                    break;
-                }
-            }
-        }
-    }
-
-    /**
      * Handles {@link Intent#ACTION_LOCALE_CHANGED}.
      *
      * <p>Note: For historical reasons, {@link Intent#ACTION_LOCALE_CHANGED} has been sent to all
@@ -1414,6 +1372,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);
@@ -1446,21 +1405,12 @@
 
         // 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);
         mSwitchingController = InputMethodSubtypeSwitchingController.createInstanceLocked(
                 mSettings, context);
-        // Register VR-state listener.
-        IVrManager vrManager = (IVrManager) ServiceManager.getService(Context.VR_SERVICE);
-        if (vrManager != null) {
-            try {
-                vrManager.registerListener(mVrStateCallbacks);
-            } catch (RemoteException e) {
-                Slog.e(TAG, "Failed to register VR mode state listener.");
-            }
-        }
     }
 
     private void resetDefaultImeLocked(Context context) {
@@ -1607,7 +1557,7 @@
     // 1) it comes from the system process
     // 2) the calling process' user id is identical to the current user id IMMS thinks.
     @GuardedBy("mMethodMap")
-    private boolean calledFromValidUserLocked(boolean allowCrossProfileAccess) {
+    private boolean calledFromValidUserLocked() {
         final int uid = Binder.getCallingUid();
         final int userId = UserHandle.getUserId(uid);
         if (DEBUG) {
@@ -1623,7 +1573,7 @@
         if (userId == mSettings.getCurrentUserId()) {
             return true;
         }
-        if (allowCrossProfileAccess && mSettings.isCurrentProfile(userId)) {
+        if (!PER_PROFILE_IME_ENABLED && mSettings.isCurrentProfile(userId)) {
             return true;
         }
 
@@ -1688,25 +1638,7 @@
             }
             final long ident = Binder.clearCallingIdentity();
             try {
-                return getInputMethodListLocked(false /* isVrOnly */, resolvedUserIds[0]);
-            } finally {
-                Binder.restoreCallingIdentity(ident);
-            }
-        }
-    }
-
-    @Override
-    public List<InputMethodInfo> getVrInputMethodList() {
-        final int callingUserId = UserHandle.getCallingUserId();
-        synchronized (mMethodMap) {
-            final int[] resolvedUserIds = InputMethodUtils.resolveUserId(callingUserId,
-                    mSettings.getCurrentUserId(), null);
-            if (resolvedUserIds.length != 1) {
-                return Collections.emptyList();
-            }
-            final long ident = Binder.clearCallingIdentity();
-            try {
-                return getInputMethodListLocked(true /* isVrOnly */, resolvedUserIds[0]);
+                return getInputMethodListLocked(resolvedUserIds[0]);
             } finally {
                 Binder.restoreCallingIdentity(ident);
             }
@@ -1732,8 +1664,7 @@
     }
 
     @GuardedBy("mMethodMap")
-    private List<InputMethodInfo> getInputMethodListLocked(boolean isVrOnly,
-            @UserIdInt int userId) {
+    private List<InputMethodInfo> getInputMethodListLocked(@UserIdInt int userId) {
         final ArrayList<InputMethodInfo> methodList;
         if (userId == mSettings.getCurrentUserId()) {
             // Create a copy.
@@ -1747,7 +1678,6 @@
             queryInputMethodServicesInternal(mContext, userId, additionalSubtypeMap, methodMap,
                     methodList);
         }
-        methodList.removeIf(imi -> imi.isVrOnly() != isVrOnly);
         return methodList;
     }
 
@@ -1764,7 +1694,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();
     }
 
@@ -1820,7 +1750,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);
     }
@@ -2021,8 +1951,8 @@
         }
         // Compute the final shown display ID with validated cs.selfReportedDisplayId for this
         // session & other conditions.
-        final int displayIdToShowIme = computeImeDisplayIdForTarget(
-                cs.selfReportedDisplayId, mIsVrImeStarted, mImeDisplayValidator);
+        final int displayIdToShowIme = computeImeDisplayIdForTarget(cs.selfReportedDisplayId,
+                mImeDisplayValidator);
 
         if (mCurClient != cs) {
             // Was the keyguard locked when switching over to the new client?
@@ -2131,17 +2061,11 @@
      * Find the display where the IME should be shown.
      *
      * @param displayId the ID of the display where the IME client target is.
-     * @param isVrImeStarted {@code true} if VR IME started, {@code false} otherwise.
      * @param checker instance of {@link ImeDisplayValidator} which is used for
      *                checking display config to adjust the final target display.
      * @return The ID of the display where the IME should be shown.
      */
-    static int computeImeDisplayIdForTarget(int displayId, boolean isVrImeStarted,
-            @NonNull ImeDisplayValidator checker) {
-        // For VR IME, we always show in default display.
-        if (isVrImeStarted) {
-            return DEFAULT_DISPLAY;
-        }
+    static int computeImeDisplayIdForTarget(int displayId, @NonNull ImeDisplayValidator checker) {
         if (displayId == DEFAULT_DISPLAY || displayId == INVALID_DISPLAY) {
             // We always assume that the default display id suitable to show the IME window.
             return DEFAULT_DISPLAY;
@@ -2352,7 +2276,10 @@
         if (mSwitchingDialog != null) return false;
         if (mWindowManagerInternal.isKeyguardShowingAndNotOccluded()
                 && mKeyguardManager != null && mKeyguardManager.isKeyguardSecure()) return false;
-        if ((visibility & InputMethodService.IME_ACTIVE) == 0) return false;
+        if ((visibility & InputMethodService.IME_ACTIVE) == 0
+                || (visibility & InputMethodService.IME_INVISIBLE) != 0) {
+            return false;
+        }
         if (mWindowManagerInternal.isHardKeyboardAvailable()) {
             if (mHardKeyboardBehavior == HardKeyboardBehavior.WIRELESS_AFFORDANCE) {
                 // When physical keyboard is attached, we show the ime switcher (or notification if
@@ -2460,6 +2387,12 @@
         if (mCurToken == null) {
             return;
         }
+        if (DEBUG) {
+            Slog.d(TAG, "IME window vis: " + vis
+                    + " active: " + (vis & InputMethodService.IME_ACTIVE)
+                    + " inv: " + (vis & InputMethodService.IME_INVISIBLE));
+        }
+
         // TODO: Move this clearing calling identity block to setImeWindowStatus after making sure
         // all updateSystemUi happens on system previlege.
         final long ident = Binder.clearCallingIdentity();
@@ -2651,7 +2584,7 @@
             ResultReceiver resultReceiver) {
         int uid = Binder.getCallingUid();
         synchronized (mMethodMap) {
-            if (!calledFromValidUserLocked(!PER_PROFILE_IME_ENABLED)) {
+            if (!calledFromValidUserLocked()) {
                 return false;
             }
             final long ident = Binder.clearCallingIdentity();
@@ -2736,7 +2669,7 @@
             ResultReceiver resultReceiver) {
         int uid = Binder.getCallingUid();
         synchronized (mMethodMap) {
-            if (!calledFromValidUserLocked(!PER_PROFILE_IME_ENABLED)) {
+            if (!calledFromValidUserLocked()) {
                 return false;
             }
             final long ident = Binder.clearCallingIdentity();
@@ -2918,6 +2851,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) {
@@ -3087,7 +3028,7 @@
     public void showInputMethodPickerFromClient(
             IInputMethodClient client, int auxiliarySubtypeMode) {
         synchronized (mMethodMap) {
-            if (!calledFromValidUserLocked(!PER_PROFILE_IME_ENABLED)) {
+            if (!calledFromValidUserLocked()) {
                 return;
             }
             if(!canShowInputMethodPickerLocked(client)) {
@@ -3159,7 +3100,7 @@
             IInputMethodClient client, String inputMethodId) {
         synchronized (mMethodMap) {
             // TODO(yukawa): Should we verify the display ID?
-            if (!calledFromValidUserLocked(!PER_PROFILE_IME_ENABLED)) {
+            if (!calledFromValidUserLocked()) {
                 return;
             }
             executeOrSendMessage(mCurMethod, mCaller.obtainMessageO(
@@ -3274,7 +3215,7 @@
     @Override
     public InputMethodSubtype getLastInputMethodSubtype() {
         synchronized (mMethodMap) {
-            if (!calledFromValidUserLocked(!PER_PROFILE_IME_ENABLED)) {
+            if (!calledFromValidUserLocked()) {
                 return null;
             }
             final Pair<String, String> lastIme = mSettings.getLastInputMethodAndSubtypeLocked();
@@ -3312,7 +3253,7 @@
             }
         }
         synchronized (mMethodMap) {
-            if (!calledFromValidUserLocked(!PER_PROFILE_IME_ENABLED)) {
+            if (!calledFromValidUserLocked()) {
                 return;
             }
             if (!mSystemReady) {
@@ -3581,7 +3522,7 @@
                 try {
                     setEnabledSessionInMainThread(session);
                     session.method.startInput(startInputToken, inputContext, missingMethods,
-                            editorInfo, restarting);
+                            editorInfo, restarting, session.client.shouldPreRenderIme);
                 } catch (RemoteException e) {
                 }
                 args.recycle();
@@ -3627,9 +3568,6 @@
             case MSG_SET_INTERACTIVE:
                 handleSetInteractive(msg.arg1 != 0);
                 return true;
-            case MSG_START_VR_INPUT:
-                startVrInputMethodNoCheck((ComponentName) msg.obj);
-                return true;
             case MSG_REPORT_FULLSCREEN_MODE: {
                 final boolean fullscreen = msg.arg1 != 0;
                 final ClientState clientState = (ClientState)msg.obj;
@@ -3716,6 +3654,9 @@
             try {
                 final InputMethodInfo imi = new InputMethodInfo(context, ri,
                         additionalSubtypeMap.get(imeId));
+                if (imi.isVrOnly()) {
+                    continue;  // Skip VR-only IME, which isn't supported for now.
+                }
                 methodList.add(imi);
                 methodMap.put(imi.getId(), imi);
                 if (DEBUG) {
@@ -4099,17 +4040,7 @@
 
     private void setSelectedInputMethodAndSubtypeLocked(InputMethodInfo imi, int subtypeId,
             boolean setSubtypeOnly) {
-        // Updates to InputMethod are transient in VR mode. Its not included in history.
-        final boolean isVrInput = imi != null && imi.isVrOnly();
-        if (!isVrInput) {
-            // Update the history of InputMethod and Subtype
-            mSettings.saveCurrentInputMethodAndSubtypeToHistory(mCurMethodId, mCurrentSubtype);
-        }
-
-        if (isVrInput) {
-            // Updates to InputMethod are transient in VR mode. Any changes to Settings are skipped.
-            return;
-        }
+        mSettings.saveCurrentInputMethodAndSubtypeToHistory(mCurMethodId, mCurrentSubtype);
 
         // Set Subtype here
         if (imi == null || subtypeId < 0) {
@@ -4158,7 +4089,7 @@
     public InputMethodSubtype getCurrentInputMethodSubtype() {
         synchronized (mMethodMap) {
             // TODO: Make this work even for non-current users?
-            if (!calledFromValidUserLocked(!PER_PROFILE_IME_ENABLED)) {
+            if (!calledFromValidUserLocked()) {
                 return null;
             }
             return getCurrentInputMethodSubtypeLocked();
@@ -4204,28 +4135,9 @@
         return mCurrentSubtype;
     }
 
-    @Override
-    public boolean setCurrentInputMethodSubtype(InputMethodSubtype subtype) {
-        synchronized (mMethodMap) {
-            // TODO: Make this work even for non-current users?
-            if (!calledFromValidUserLocked(!PER_PROFILE_IME_ENABLED)) {
-                return false;
-            }
-            if (subtype != null && mCurMethodId != null) {
-                InputMethodInfo imi = mMethodMap.get(mCurMethodId);
-                int subtypeId = InputMethodUtils.getSubtypeIdFromHashCode(imi, subtype.hashCode());
-                if (subtypeId != NOT_A_SUBTYPE_ID) {
-                    setInputMethodLocked(mCurMethodId, subtypeId);
-                    return true;
-                }
-            }
-            return false;
-        }
-    }
-
     private List<InputMethodInfo> getInputMethodListAsUser(@UserIdInt int userId) {
         synchronized (mMethodMap) {
-            return getInputMethodListLocked(false, userId);
+            return getInputMethodListLocked(userId);
         }
     }
 
@@ -4257,11 +4169,6 @@
         }
 
         @Override
-        public void startVrInputMethodNoCheck(@Nullable ComponentName componentName) {
-            mService.mHandler.obtainMessage(MSG_START_VR_INPUT, componentName).sendToTarget();
-        }
-
-        @Override
         public List<InputMethodInfo> getInputMethodListAsUser(int userId) {
             return mService.getInputMethodListAsUser(userId);
         }
@@ -4543,6 +4450,7 @@
         @ShellCommandResult
         private int refreshDebugProperties() {
             DebugFlags.FLAG_OPTIMIZE_START_INPUT.refresh();
+            DebugFlags.FLAG_PRE_RENDER_IME_VIEWS.refresh();
             return ShellCommandResult.SUCCESS;
         }
 
@@ -4648,7 +4556,7 @@
                     mSettings.getCurrentUserId(), shellCommand.getErrPrintWriter());
             for (int userId : userIds) {
                 final List<InputMethodInfo> methods = all
-                        ? getInputMethodListLocked(false, userId)
+                        ? getInputMethodListLocked(userId)
                         : getEnabledInputMethodListLocked(userId);
                 if (userIds.length > 1) {
                     pr.print("User #");
diff --git a/services/core/java/com/android/server/inputmethod/InputMethodUtils.java b/services/core/java/com/android/server/inputmethod/InputMethodUtils.java
index 88d1a9c..326984c 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,15 +859,17 @@
             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;
@@ -869,11 +877,11 @@
             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/inputmethod/MultiClientInputMethodManagerService.java b/services/core/java/com/android/server/inputmethod/MultiClientInputMethodManagerService.java
index 6f0c5e8..3222143 100644
--- a/services/core/java/com/android/server/inputmethod/MultiClientInputMethodManagerService.java
+++ b/services/core/java/com/android/server/inputmethod/MultiClientInputMethodManagerService.java
@@ -159,11 +159,6 @@
                         }
 
                         @Override
-                        public void startVrInputMethodNoCheck(ComponentName componentName) {
-                            reportNotSupported();
-                        }
-
-                        @Override
                         public List<InputMethodInfo> getInputMethodListAsUser(
                                 @UserIdInt int userId) {
                             return userIdToInputMethodInfoMapper.getAsList(userId);
@@ -1244,13 +1239,6 @@
 
         @BinderThread
         @Override
-        public List<InputMethodInfo> getVrInputMethodList() {
-            reportNotSupported();
-            return Collections.emptyList();
-        }
-
-        @BinderThread
-        @Override
         public List<InputMethodInfo> getEnabledInputMethodList() {
             return mInputMethodInfoMap.getAsList(UserHandle.getUserId(Binder.getCallingUid()));
         }
@@ -1531,13 +1519,6 @@
 
         @BinderThread
         @Override
-        public boolean setCurrentInputMethodSubtype(InputMethodSubtype subtype) {
-            reportNotSupported();
-            return false;
-        }
-
-        @BinderThread
-        @Override
         public void setAdditionalInputMethodSubtypes(String imiId, InputMethodSubtype[] subtypes) {
             reportNotSupported();
         }
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 cc0ac9a..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);
         }
     }
 
@@ -443,6 +478,16 @@
                 "qc_window_size_rare_ms";
         private static final String KEY_QUOTA_CONTROLLER_MAX_EXECUTION_TIME_MS =
                 "qc_max_execution_time_ms";
+        private static final String KEY_QUOTA_CONTROLLER_MAX_JOB_COUNT_ACTIVE =
+                "qc_max_job_count_active";
+        private static final String KEY_QUOTA_CONTROLLER_MAX_JOB_COUNT_WORKING =
+                "qc_max_job_count_working";
+        private static final String KEY_QUOTA_CONTROLLER_MAX_JOB_COUNT_FREQUENT =
+                "qc_max_job_count_frequent";
+        private static final String KEY_QUOTA_CONTROLLER_MAX_JOB_COUNT_RARE =
+                "qc_max_job_count_rare";
+        private static final String KEY_QUOTA_CONTROLLER_MAX_JOB_COUNT_PER_ALLOWED_TIME =
+                "qc_max_count_per_allowed_time";
 
         private static final int DEFAULT_MIN_IDLE_COUNT = 1;
         private static final int DEFAULT_MIN_CHARGING_COUNT = 1;
@@ -479,6 +524,15 @@
                 24 * 60 * 60 * 1000L; // 24 hours
         private static final long DEFAULT_QUOTA_CONTROLLER_MAX_EXECUTION_TIME_MS =
                 4 * 60 * 60 * 1000L; // 4 hours
+        private static final int DEFAULT_QUOTA_CONTROLLER_MAX_JOB_COUNT_ACTIVE =
+                200; // 1200/hr
+        private static final int DEFAULT_QUOTA_CONTROLLER_MAX_JOB_COUNT_WORKING =
+                1200; // 600/hr
+        private static final int DEFAULT_QUOTA_CONTROLLER_MAX_JOB_COUNT_FREQUENT =
+                1800; // 225/hr
+        private static final int DEFAULT_QUOTA_CONTROLLER_MAX_JOB_COUNT_RARE =
+                2400; // 100/hr
+        private static final int DEFAULT_QUOTA_CONTROLLER_MAX_JOB_COUNT_PER_ALLOWED_TIME = 20;
 
         /**
          * Minimum # of idle jobs that must be ready in order to force the JMS to schedule things
@@ -527,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 =
@@ -682,6 +735,41 @@
         public long QUOTA_CONTROLLER_MAX_EXECUTION_TIME_MS =
                 DEFAULT_QUOTA_CONTROLLER_MAX_EXECUTION_TIME_MS;
 
+        /**
+         * The maximum number of jobs an app can run within this particular standby bucket's
+         * window size.
+         */
+        public int QUOTA_CONTROLLER_MAX_JOB_COUNT_ACTIVE =
+                DEFAULT_QUOTA_CONTROLLER_MAX_JOB_COUNT_ACTIVE;
+
+        /**
+         * The maximum number of jobs an app can run within this particular standby bucket's
+         * window size.
+         */
+        public int QUOTA_CONTROLLER_MAX_JOB_COUNT_WORKING =
+                DEFAULT_QUOTA_CONTROLLER_MAX_JOB_COUNT_WORKING;
+
+        /**
+         * The maximum number of jobs an app can run within this particular standby bucket's
+         * window size.
+         */
+        public int QUOTA_CONTROLLER_MAX_JOB_COUNT_FREQUENT =
+                DEFAULT_QUOTA_CONTROLLER_MAX_JOB_COUNT_FREQUENT;
+
+        /**
+         * The maximum number of jobs an app can run within this particular standby bucket's
+         * window size.
+         */
+        public int QUOTA_CONTROLLER_MAX_JOB_COUNT_RARE =
+                DEFAULT_QUOTA_CONTROLLER_MAX_JOB_COUNT_RARE;
+
+        /**
+         * The maximum number of jobs that can run within the past
+         * {@link #QUOTA_CONTROLLER_ALLOWED_TIME_PER_PERIOD_MS}.
+         */
+        public int QUOTA_CONTROLLER_MAX_JOB_COUNT_PER_ALLOWED_TIME =
+                DEFAULT_QUOTA_CONTROLLER_MAX_JOB_COUNT_PER_ALLOWED_TIME;
+
         private final KeyValueListParser mParser = new KeyValueListParser(',');
 
         void updateConstantsLocked(String value) {
@@ -712,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);
@@ -767,6 +855,21 @@
             QUOTA_CONTROLLER_MAX_EXECUTION_TIME_MS = mParser.getDurationMillis(
                     KEY_QUOTA_CONTROLLER_MAX_EXECUTION_TIME_MS,
                     DEFAULT_QUOTA_CONTROLLER_MAX_EXECUTION_TIME_MS);
+            QUOTA_CONTROLLER_MAX_JOB_COUNT_ACTIVE = mParser.getInt(
+                    KEY_QUOTA_CONTROLLER_MAX_JOB_COUNT_ACTIVE,
+                    DEFAULT_QUOTA_CONTROLLER_MAX_JOB_COUNT_ACTIVE);
+            QUOTA_CONTROLLER_MAX_JOB_COUNT_WORKING = mParser.getInt(
+                    KEY_QUOTA_CONTROLLER_MAX_JOB_COUNT_WORKING,
+                    DEFAULT_QUOTA_CONTROLLER_MAX_JOB_COUNT_WORKING);
+            QUOTA_CONTROLLER_MAX_JOB_COUNT_FREQUENT = mParser.getInt(
+                    KEY_QUOTA_CONTROLLER_MAX_JOB_COUNT_FREQUENT,
+                    DEFAULT_QUOTA_CONTROLLER_MAX_JOB_COUNT_FREQUENT);
+            QUOTA_CONTROLLER_MAX_JOB_COUNT_RARE = mParser.getInt(
+                    KEY_QUOTA_CONTROLLER_MAX_JOB_COUNT_RARE,
+                    DEFAULT_QUOTA_CONTROLLER_MAX_JOB_COUNT_RARE);
+            QUOTA_CONTROLLER_MAX_JOB_COUNT_PER_ALLOWED_TIME = mParser.getInt(
+                    KEY_QUOTA_CONTROLLER_MAX_JOB_COUNT_PER_ALLOWED_TIME,
+                    DEFAULT_QUOTA_CONTROLLER_MAX_JOB_COUNT_PER_ALLOWED_TIME);
         }
 
         void dump(IndentingPrintWriter pw) {
@@ -782,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();
@@ -823,6 +928,16 @@
                     QUOTA_CONTROLLER_WINDOW_SIZE_RARE_MS).println();
             pw.printPair(KEY_QUOTA_CONTROLLER_MAX_EXECUTION_TIME_MS,
                     QUOTA_CONTROLLER_MAX_EXECUTION_TIME_MS).println();
+            pw.printPair(KEY_QUOTA_CONTROLLER_MAX_JOB_COUNT_ACTIVE,
+                    QUOTA_CONTROLLER_MAX_JOB_COUNT_ACTIVE).println();
+            pw.printPair(KEY_QUOTA_CONTROLLER_MAX_JOB_COUNT_WORKING,
+                    QUOTA_CONTROLLER_MAX_JOB_COUNT_WORKING).println();
+            pw.printPair(KEY_QUOTA_CONTROLLER_MAX_JOB_COUNT_FREQUENT,
+                    QUOTA_CONTROLLER_MAX_JOB_COUNT_FREQUENT).println();
+            pw.printPair(KEY_QUOTA_CONTROLLER_MAX_JOB_COUNT_RARE,
+                    QUOTA_CONTROLLER_MAX_JOB_COUNT_RARE).println();
+            pw.printPair(KEY_QUOTA_CONTROLLER_MAX_JOB_COUNT_PER_ALLOWED_TIME,
+                    QUOTA_CONTROLLER_MAX_JOB_COUNT_PER_ALLOWED_TIME).println();
             pw.decreaseIndent();
         }
 
@@ -838,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);
@@ -872,6 +991,16 @@
                     QUOTA_CONTROLLER_WINDOW_SIZE_RARE_MS);
             proto.write(ConstantsProto.QuotaController.MAX_EXECUTION_TIME_MS,
                     QUOTA_CONTROLLER_MAX_EXECUTION_TIME_MS);
+            proto.write(ConstantsProto.QuotaController.MAX_JOB_COUNT_ACTIVE,
+                    QUOTA_CONTROLLER_MAX_JOB_COUNT_ACTIVE);
+            proto.write(ConstantsProto.QuotaController.MAX_JOB_COUNT_WORKING,
+                    QUOTA_CONTROLLER_MAX_JOB_COUNT_WORKING);
+            proto.write(ConstantsProto.QuotaController.MAX_JOB_COUNT_FREQUENT,
+                    QUOTA_CONTROLLER_MAX_JOB_COUNT_FREQUENT);
+            proto.write(ConstantsProto.QuotaController.MAX_JOB_COUNT_RARE,
+                    QUOTA_CONTROLLER_MAX_JOB_COUNT_RARE);
+            proto.write(ConstantsProto.QuotaController.MAX_JOB_COUNT_PER_ALLOWED_TIME,
+                    QUOTA_CONTROLLER_MAX_JOB_COUNT_PER_ALLOWED_TIME);
             proto.end(qcToken);
 
             proto.end(token);
@@ -3282,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;
@@ -3459,7 +3590,7 @@
             }
             pw.println();
 
-            mConcurrencyManager.dumpLocked(pw);
+            mConcurrencyManager.dumpLocked(pw, now, nowElapsed);
 
             pw.println();
             pw.print("PersistStats: ");
@@ -3471,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) -> {
@@ -3614,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/job/controllers/QuotaController.java b/services/core/java/com/android/server/job/controllers/QuotaController.java
index c16d1b4..5a0b991 100644
--- a/services/core/java/com/android/server/job/controllers/QuotaController.java
+++ b/services/core/java/com/android/server/job/controllers/QuotaController.java
@@ -230,10 +230,10 @@
     @VisibleForTesting
     static class ExecutionStats {
         /**
-         * The time at which this record should be considered invalid, in the elapsed realtime
-         * timebase.
+         * The time after which this record should be considered invalid (out of date), in the
+         * elapsed realtime timebase.
          */
-        public long invalidTimeElapsed;
+        public long expirationTimeElapsed;
 
         public long windowSizeMs;
 
@@ -241,29 +241,45 @@
         public long executionTimeInWindowMs;
         public int bgJobCountInWindow;
 
-        /** The total amount of time the app ran in the last {@link MAX_PERIOD_MS}. */
+        /** The total amount of time the app ran in the last {@link #MAX_PERIOD_MS}. */
         public long executionTimeInMaxPeriodMs;
         public int bgJobCountInMaxPeriod;
 
         /**
-         * The time after which the sum of all the app's sessions plus {@link mQuotaBufferMs} equals
-         * the quota. This is only valid if
-         * executionTimeInWindowMs >= {@link mAllowedTimePerPeriodMs} or
-         * executionTimeInMaxPeriodMs >= {@link mMaxExecutionTimeMs}.
+         * The time after which the sum of all the app's sessions plus {@link #mQuotaBufferMs}
+         * equals the quota. This is only valid if
+         * executionTimeInWindowMs >= {@link #mAllowedTimePerPeriodMs} or
+         * executionTimeInMaxPeriodMs >= {@link #mMaxExecutionTimeMs}.
          */
         public long quotaCutoffTimeElapsed;
 
+        /**
+         * The time after which {@link #jobCountInAllowedTime} should be considered invalid, in the
+         * elapsed realtime timebase.
+         */
+        public long jobCountExpirationTimeElapsed;
+
+        /**
+         * The number of jobs that ran in at least the last {@link #mAllowedTimePerPeriodMs}.
+         * It may contain a few stale entries since cleanup won't happen exactly every
+         * {@link #mAllowedTimePerPeriodMs}.
+         */
+        public int jobCountInAllowedTime;
+
         @Override
         public String toString() {
             return new StringBuilder()
-                    .append("invalidTime=").append(invalidTimeElapsed).append(", ")
+                    .append("expirationTime=").append(expirationTimeElapsed).append(", ")
                     .append("windowSize=").append(windowSizeMs).append(", ")
                     .append("executionTimeInWindow=").append(executionTimeInWindowMs).append(", ")
                     .append("bgJobCountInWindow=").append(bgJobCountInWindow).append(", ")
                     .append("executionTimeInMaxPeriod=").append(executionTimeInMaxPeriodMs)
                     .append(", ")
                     .append("bgJobCountInMaxPeriod=").append(bgJobCountInMaxPeriod).append(", ")
-                    .append("quotaCutoffTime=").append(quotaCutoffTimeElapsed)
+                    .append("quotaCutoffTime=").append(quotaCutoffTimeElapsed).append(", ")
+                    .append("jobCountExpirationTime").append(jobCountExpirationTimeElapsed)
+                    .append(", ")
+                    .append("jobCountInAllowedTime").append(jobCountInAllowedTime)
                     .toString();
         }
 
@@ -271,13 +287,15 @@
         public boolean equals(Object obj) {
             if (obj instanceof ExecutionStats) {
                 ExecutionStats other = (ExecutionStats) obj;
-                return this.invalidTimeElapsed == other.invalidTimeElapsed
+                return this.expirationTimeElapsed == other.expirationTimeElapsed
                         && this.windowSizeMs == other.windowSizeMs
                         && this.executionTimeInWindowMs == other.executionTimeInWindowMs
                         && this.bgJobCountInWindow == other.bgJobCountInWindow
                         && this.executionTimeInMaxPeriodMs == other.executionTimeInMaxPeriodMs
                         && this.bgJobCountInMaxPeriod == other.bgJobCountInMaxPeriod
-                        && this.quotaCutoffTimeElapsed == other.quotaCutoffTimeElapsed;
+                        && this.quotaCutoffTimeElapsed == other.quotaCutoffTimeElapsed
+                        && this.jobCountExpirationTimeElapsed == other.jobCountExpirationTimeElapsed
+                        && this.jobCountInAllowedTime == other.jobCountInAllowedTime;
             } else {
                 return false;
             }
@@ -286,13 +304,15 @@
         @Override
         public int hashCode() {
             int result = 0;
-            result = 31 * result + hashLong(invalidTimeElapsed);
+            result = 31 * result + hashLong(expirationTimeElapsed);
             result = 31 * result + hashLong(windowSizeMs);
             result = 31 * result + hashLong(executionTimeInWindowMs);
             result = 31 * result + bgJobCountInWindow;
             result = 31 * result + hashLong(executionTimeInMaxPeriodMs);
             result = 31 * result + bgJobCountInMaxPeriod;
             result = 31 * result + hashLong(quotaCutoffTimeElapsed);
+            result = 31 * result + hashLong(jobCountExpirationTimeElapsed);
+            result = 31 * result + jobCountInAllowedTime;
             return result;
         }
     }
@@ -320,7 +340,7 @@
 
     /**
      * List of jobs that started while the UID was in the TOP state. There will be no more than
-     * 16 ({@link JobSchedulerService.MAX_JOB_CONTEXTS_COUNT}) running at once, so an ArraySet is
+     * 16 ({@link JobSchedulerService#MAX_JOB_CONTEXTS_COUNT}) running at once, so an ArraySet is
      * fine.
      */
     private final ArraySet<JobStatus> mTopStartedJobs = new ArraySet<>();
@@ -343,7 +363,7 @@
     private long mAllowedTimePerPeriodMs = 10 * MINUTE_IN_MILLIS;
 
     /**
-     * The maximum amount of time an app can have its jobs running within a {@link MAX_PERIOD_MS}
+     * The maximum amount of time an app can have its jobs running within a {@link #MAX_PERIOD_MS}
      * window.
      */
     private long mMaxExecutionTimeMs = 4 * 60 * MINUTE_IN_MILLIS;
@@ -355,17 +375,20 @@
     private long mQuotaBufferMs = 30 * 1000L; // 30 seconds
 
     /**
-     * {@link mAllowedTimePerPeriodMs} - {@link mQuotaBufferMs}. This can be used to determine when
-     * an app will have enough quota to transition from out-of-quota to in-quota.
+     * {@link #mAllowedTimePerPeriodMs} - {@link #mQuotaBufferMs}. This can be used to determine
+     * when an app will have enough quota to transition from out-of-quota to in-quota.
      */
     private long mAllowedTimeIntoQuotaMs = mAllowedTimePerPeriodMs - mQuotaBufferMs;
 
     /**
-     * {@link mMaxExecutionTimeMs} - {@link mQuotaBufferMs}. This can be used to determine when an
+     * {@link #mMaxExecutionTimeMs} - {@link #mQuotaBufferMs}. This can be used to determine when an
      * app will have enough quota to transition from out-of-quota to in-quota.
      */
     private long mMaxExecutionTimeIntoQuotaMs = mMaxExecutionTimeMs - mQuotaBufferMs;
 
+    /** The maximum number of jobs that can run within the past {@link #mAllowedTimePerPeriodMs}. */
+    private int mMaxJobCountPerAllowedTime = 20;
+
     private long mNextCleanupTimeElapsed = 0;
     private final AlarmManager.OnAlarmListener mSessionCleanupAlarmListener =
             new AlarmManager.OnAlarmListener() {
@@ -412,6 +435,23 @@
     /** The maximum period any bucket can have. */
     private static final long MAX_PERIOD_MS = 24 * 60 * MINUTE_IN_MILLIS;
 
+    /**
+     * The maximum number of jobs based on its standby bucket. For each max value count in the
+     * array, the app will not be allowed to run more than that many number of jobs within the
+     * latest time interval of its rolling window size.
+     *
+     * @see #mBucketPeriodsMs
+     */
+    private final int[] mMaxBucketJobCounts = new int[] {
+            200,  // ACTIVE   -- 1200/hr
+            1200, // WORKING  -- 600/hr
+            1800, // FREQUENT -- 225/hr
+            2400  // RARE     -- 100/hr
+    };
+
+    /** The minimum number of jobs that any bucket will be allowed to run. */
+    private static final int MIN_BUCKET_JOB_COUNT = 100;
+
     /** An app has reached its quota. The message should contain a {@link Package} object. */
     private static final int MSG_REACHED_QUOTA = 0;
     /** Drop any old timing sessions. */
@@ -463,17 +503,21 @@
     @Override
     public void prepareForExecutionLocked(JobStatus jobStatus) {
         if (DEBUG) Slog.d(TAG, "Prepping for " + jobStatus.toShortString());
+
+        final int uid = jobStatus.getSourceUid();
+        if (mActivityManagerInternal.getUidProcessState(uid) <= ActivityManager.PROCESS_STATE_TOP) {
+            mTopStartedJobs.add(jobStatus);
+            // Top jobs won't count towards quota so there's no need to involve the Timer.
+            return;
+        }
+
         final int userId = jobStatus.getSourceUserId();
         final String packageName = jobStatus.getSourcePackageName();
-        final int uid = jobStatus.getSourceUid();
         Timer timer = mPkgTimers.get(userId, packageName);
         if (timer == null) {
             timer = new Timer(uid, userId, packageName);
             mPkgTimers.add(userId, packageName, timer);
         }
-        if (mActivityManagerInternal.getUidProcessState(uid) == ActivityManager.PROCESS_STATE_TOP) {
-            mTopStartedJobs.add(jobStatus);
-        }
         timer.startTrackingJob(jobStatus);
     }
 
@@ -548,6 +592,36 @@
             mMaxExecutionTimeIntoQuotaMs = mMaxExecutionTimeMs - mQuotaBufferMs;
             changed = true;
         }
+        int newMaxCountPerAllowedPeriod = Math.max(10,
+                mConstants.QUOTA_CONTROLLER_MAX_JOB_COUNT_PER_ALLOWED_TIME);
+        if (mMaxJobCountPerAllowedTime != newMaxCountPerAllowedPeriod) {
+            mMaxJobCountPerAllowedTime = newMaxCountPerAllowedPeriod;
+            changed = true;
+        }
+        int newActiveMaxJobCount = Math.max(mMaxJobCountPerAllowedTime,
+                Math.max(MIN_BUCKET_JOB_COUNT, mConstants.QUOTA_CONTROLLER_MAX_JOB_COUNT_ACTIVE));
+        if (mMaxBucketJobCounts[ACTIVE_INDEX] != newActiveMaxJobCount) {
+            mMaxBucketJobCounts[ACTIVE_INDEX] = newActiveMaxJobCount;
+            changed = true;
+        }
+        int newWorkingMaxJobCount = Math.max(mMaxJobCountPerAllowedTime,
+                Math.max(MIN_BUCKET_JOB_COUNT, mConstants.QUOTA_CONTROLLER_MAX_JOB_COUNT_WORKING));
+        if (mMaxBucketJobCounts[WORKING_INDEX] != newWorkingMaxJobCount) {
+            mMaxBucketJobCounts[WORKING_INDEX] = newWorkingMaxJobCount;
+            changed = true;
+        }
+        int newFrequentMaxJobCount = Math.max(mMaxJobCountPerAllowedTime,
+                Math.max(MIN_BUCKET_JOB_COUNT, mConstants.QUOTA_CONTROLLER_MAX_JOB_COUNT_FREQUENT));
+        if (mMaxBucketJobCounts[FREQUENT_INDEX] != newFrequentMaxJobCount) {
+            mMaxBucketJobCounts[FREQUENT_INDEX] = newFrequentMaxJobCount;
+            changed = true;
+        }
+        int newRareMaxJobCount = Math.max(mMaxJobCountPerAllowedTime,
+                Math.max(MIN_BUCKET_JOB_COUNT, mConstants.QUOTA_CONTROLLER_MAX_JOB_COUNT_RARE));
+        if (mMaxBucketJobCounts[RARE_INDEX] != newRareMaxJobCount) {
+            mMaxBucketJobCounts[RARE_INDEX] = newRareMaxJobCount;
+            changed = true;
+        }
 
         if (changed) {
             // Update job bookkeeping out of band.
@@ -631,18 +705,39 @@
         return isTopStartedJob(jobStatus)
                 || isUidInForeground(jobStatus.getSourceUid())
                 || isWithinQuotaLocked(
-                      jobStatus.getSourceUserId(), jobStatus.getSourcePackageName(), standbyBucket);
+                jobStatus.getSourceUserId(), jobStatus.getSourcePackageName(), standbyBucket);
     }
 
-    private boolean isWithinQuotaLocked(final int userId, @NonNull final String packageName,
+    @VisibleForTesting
+    boolean isWithinQuotaLocked(final int userId, @NonNull final String packageName,
             final int standbyBucket) {
         if (standbyBucket == NEVER_INDEX) return false;
         // This check is needed in case the flag is toggled after a job has been registered.
         if (!mShouldThrottle) return true;
 
         // Quota constraint is not enforced while charging or when parole is on.
-        return mChargeTracker.isCharging() || mInParole
-                || getRemainingExecutionTimeLocked(userId, packageName, standbyBucket) > 0;
+        if (mChargeTracker.isCharging() || mInParole) {
+            return true;
+        }
+
+        return getRemainingExecutionTimeLocked(userId, packageName, standbyBucket) > 0
+                && isUnderJobCountQuotaLocked(userId, packageName, standbyBucket);
+    }
+
+    private boolean isUnderJobCountQuotaLocked(final int userId, @NonNull final String packageName,
+            final int standbyBucket) {
+        ExecutionStats stats = getExecutionStatsLocked(userId, packageName, standbyBucket, false);
+        return isUnderJobCountQuotaLocked(stats, standbyBucket);
+    }
+
+    private boolean isUnderJobCountQuotaLocked(@NonNull ExecutionStats stats,
+            final int standbyBucket) {
+        final long now = sElapsedRealtimeClock.millis();
+        final boolean isUnderAllowedTimeQuota =
+                (stats.jobCountExpirationTimeElapsed <= now
+                        || stats.jobCountInAllowedTime < mMaxJobCountPerAllowedTime);
+        return isUnderAllowedTimeQuota
+                && (stats.bgJobCountInWindow < mMaxBucketJobCounts[standbyBucket]);
     }
 
     @VisibleForTesting
@@ -679,6 +774,13 @@
     @NonNull
     ExecutionStats getExecutionStatsLocked(final int userId, @NonNull final String packageName,
             final int standbyBucket) {
+        return getExecutionStatsLocked(userId, packageName, standbyBucket, true);
+    }
+
+    @NonNull
+    private ExecutionStats getExecutionStatsLocked(final int userId,
+            @NonNull final String packageName, final int standbyBucket,
+            final boolean refreshStatsIfOld) {
         if (standbyBucket == NEVER_INDEX) {
             Slog.wtf(TAG, "getExecutionStatsLocked called for a NEVER app.");
             return new ExecutionStats();
@@ -693,14 +795,16 @@
             stats = new ExecutionStats();
             appStats[standbyBucket] = stats;
         }
-        final long bucketWindowSizeMs = mBucketPeriodsMs[standbyBucket];
-        Timer timer = mPkgTimers.get(userId, packageName);
-        if ((timer != null && timer.isActive())
-                || stats.invalidTimeElapsed <= sElapsedRealtimeClock.millis()
-                || stats.windowSizeMs != bucketWindowSizeMs) {
-            // The stats are no longer valid.
-            stats.windowSizeMs = bucketWindowSizeMs;
-            updateExecutionStatsLocked(userId, packageName, stats);
+        if (refreshStatsIfOld) {
+            final long bucketWindowSizeMs = mBucketPeriodsMs[standbyBucket];
+            Timer timer = mPkgTimers.get(userId, packageName);
+            if ((timer != null && timer.isActive())
+                    || stats.expirationTimeElapsed <= sElapsedRealtimeClock.millis()
+                    || stats.windowSizeMs != bucketWindowSizeMs) {
+                // The stats are no longer valid.
+                stats.windowSizeMs = bucketWindowSizeMs;
+                updateExecutionStatsLocked(userId, packageName, stats);
+            }
         }
 
         return stats;
@@ -717,14 +821,14 @@
 
         Timer timer = mPkgTimers.get(userId, packageName);
         final long nowElapsed = sElapsedRealtimeClock.millis();
-        stats.invalidTimeElapsed = nowElapsed + MAX_PERIOD_MS;
+        stats.expirationTimeElapsed = nowElapsed + MAX_PERIOD_MS;
         if (timer != null && timer.isActive()) {
             stats.executionTimeInWindowMs =
                     stats.executionTimeInMaxPeriodMs = timer.getCurrentDuration(nowElapsed);
             stats.bgJobCountInWindow = stats.bgJobCountInMaxPeriod = timer.getBgJobCount();
             // If the timer is active, the value will be stale at the next method call, so
             // invalidate now.
-            stats.invalidTimeElapsed = nowElapsed;
+            stats.expirationTimeElapsed = nowElapsed;
             if (stats.executionTimeInWindowMs >= mAllowedTimeIntoQuotaMs) {
                 stats.quotaCutoffTimeElapsed = Math.max(stats.quotaCutoffTimeElapsed,
                         nowElapsed - mAllowedTimeIntoQuotaMs);
@@ -800,7 +904,7 @@
                 break;
             }
         }
-        stats.invalidTimeElapsed = nowElapsed + emptyTimeMs;
+        stats.expirationTimeElapsed = nowElapsed + emptyTimeMs;
     }
 
     private void invalidateAllExecutionStatsLocked(final int userId,
@@ -811,13 +915,35 @@
             for (int i = 0; i < appStats.length; ++i) {
                 ExecutionStats stats = appStats[i];
                 if (stats != null) {
-                    stats.invalidTimeElapsed = nowElapsed;
+                    stats.expirationTimeElapsed = nowElapsed;
                 }
             }
         }
     }
 
     @VisibleForTesting
+    void incrementJobCount(final int userId, @NonNull final String packageName, int count) {
+        final long now = sElapsedRealtimeClock.millis();
+        ExecutionStats[] appStats = mExecutionStatsCache.get(userId, packageName);
+        if (appStats == null) {
+            appStats = new ExecutionStats[mBucketPeriodsMs.length];
+            mExecutionStatsCache.add(userId, packageName, appStats);
+        }
+        for (int i = 0; i < appStats.length; ++i) {
+            ExecutionStats stats = appStats[i];
+            if (stats == null) {
+                stats = new ExecutionStats();
+                appStats[i] = stats;
+            }
+            if (stats.jobCountExpirationTimeElapsed <= now) {
+                stats.jobCountExpirationTimeElapsed = now + mAllowedTimePerPeriodMs;
+                stats.jobCountInAllowedTime = 0;
+            }
+            stats.jobCountInAllowedTime += count;
+        }
+    }
+
+    @VisibleForTesting
     void saveTimingSession(final int userId, @NonNull final String packageName,
             @NonNull final TimingSession session) {
         synchronized (mLock) {
@@ -1023,9 +1149,12 @@
 
         final String pkgString = string(userId, packageName);
         ExecutionStats stats = getExecutionStatsLocked(userId, packageName, standbyBucket);
+        final boolean isUnderJobCountQuota = isUnderJobCountQuotaLocked(stats, standbyBucket);
+
         QcAlarmListener alarmListener = mInQuotaAlarmListeners.get(userId, packageName);
         if (stats.executionTimeInWindowMs < mAllowedTimePerPeriodMs
-                && stats.executionTimeInMaxPeriodMs < mMaxExecutionTimeMs) {
+                && stats.executionTimeInMaxPeriodMs < mMaxExecutionTimeMs
+                && isUnderJobCountQuota) {
             // Already in quota. Why was this method called?
             if (DEBUG) {
                 Slog.e(TAG, "maybeScheduleStartAlarmLocked called for " + pkgString
@@ -1042,18 +1171,22 @@
             mHandler.obtainMessage(MSG_CHECK_PACKAGE, userId, 0, packageName).sendToTarget();
             return;
         }
+
         if (alarmListener == null) {
             alarmListener = new QcAlarmListener(userId, packageName);
             mInQuotaAlarmListeners.add(userId, packageName, alarmListener);
         }
 
         // The time this app will have quota again.
-        long inQuotaTimeElapsed =
-                stats.quotaCutoffTimeElapsed + stats.windowSizeMs;
+        long inQuotaTimeElapsed = stats.quotaCutoffTimeElapsed + stats.windowSizeMs;
         if (stats.executionTimeInMaxPeriodMs >= mMaxExecutionTimeMs) {
             inQuotaTimeElapsed = Math.max(inQuotaTimeElapsed,
                     stats.quotaCutoffTimeElapsed + MAX_PERIOD_MS);
         }
+        if (!isUnderJobCountQuota) {
+            inQuotaTimeElapsed = Math.max(inQuotaTimeElapsed,
+                    stats.jobCountExpirationTimeElapsed + mAllowedTimePerPeriodMs);
+        }
         // Only schedule the alarm if:
         // 1. There isn't one currently scheduled
         // 2. The new alarm is significantly earlier than the previous alarm (which could be the
@@ -1228,6 +1361,7 @@
                 mRunningBgJobs.add(jobStatus);
                 if (shouldTrackLocked()) {
                     mBgJobCount++;
+                    incrementJobCount(mPkg.userId, mPkg.packageName, 1);
                     if (mRunningBgJobs.size() == 1) {
                         // Started tracking the first job.
                         mStartTimeElapsed = sElapsedRealtimeClock.millis();
@@ -1324,6 +1458,7 @@
                         // repeatedly plugged in and unplugged, or an app changes foreground state
                         // very frequently, the job count for a package may be artificially high.
                         mBgJobCount = mRunningBgJobs.size();
+                        incrementJobCount(mPkg.userId, mPkg.packageName, mBgJobCount);
                         // Starting the timer means that all cached execution stats are now
                         // incorrect.
                         invalidateAllExecutionStatsLocked(mPkg.userId, mPkg.packageName);
@@ -1604,6 +1739,12 @@
 
     @VisibleForTesting
     @NonNull
+    int[] getBucketMaxJobCounts() {
+        return mMaxBucketJobCounts;
+    }
+
+    @VisibleForTesting
+    @NonNull
     long[] getBucketWindowSizes() {
         return mBucketPeriodsMs;
     }
@@ -1631,6 +1772,11 @@
     }
 
     @VisibleForTesting
+    int getMaxJobCountPerAllowedTime() {
+        return mMaxJobCountPerAllowedTime;
+    }
+
+    @VisibleForTesting
     @Nullable
     List<TimingSession> getTimingSessions(int userId, String packageName) {
         return mTimingSessions.get(userId, packageName);
diff --git a/services/core/java/com/android/server/location/ActivityRecognitionProxy.java b/services/core/java/com/android/server/location/ActivityRecognitionProxy.java
new file mode 100644
index 0000000..22fabb2
--- /dev/null
+++ b/services/core/java/com/android/server/location/ActivityRecognitionProxy.java
@@ -0,0 +1,118 @@
+/*
+ * Copyright (C) 2014 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License
+ */
+
+package com.android.server.location;
+
+import android.content.Context;
+import android.hardware.location.ActivityRecognitionHardware;
+import android.hardware.location.IActivityRecognitionHardwareClient;
+import android.hardware.location.IActivityRecognitionHardwareWatcher;
+import android.os.IBinder;
+import android.os.RemoteException;
+import android.util.Log;
+
+import com.android.internal.os.BackgroundThread;
+import com.android.server.ServiceWatcher;
+
+/**
+ * Proxy class to bind GmsCore to the ActivityRecognitionHardware.
+ *
+ * @hide
+ */
+public class ActivityRecognitionProxy {
+
+    private static final String TAG = "ActivityRecognitionProxy";
+
+    /**
+     * Creates an instance of the proxy and binds it to the appropriate FusedProvider.
+     *
+     * @return An instance of the proxy if it could be bound, null otherwise.
+     */
+    public static ActivityRecognitionProxy createAndBind(
+            Context context,
+            boolean activityRecognitionHardwareIsSupported,
+            ActivityRecognitionHardware activityRecognitionHardware,
+            int overlaySwitchResId,
+            int defaultServicePackageNameResId,
+            int initialPackageNameResId) {
+        ActivityRecognitionProxy activityRecognitionProxy = new ActivityRecognitionProxy(
+                context,
+                activityRecognitionHardwareIsSupported,
+                activityRecognitionHardware,
+                overlaySwitchResId,
+                defaultServicePackageNameResId,
+                initialPackageNameResId);
+
+        if (activityRecognitionProxy.mServiceWatcher.start()) {
+            return activityRecognitionProxy;
+        } else {
+            return null;
+        }
+    }
+
+    private final ServiceWatcher mServiceWatcher;
+    private final boolean mIsSupported;
+    private final ActivityRecognitionHardware mInstance;
+
+    private ActivityRecognitionProxy(
+            Context context,
+            boolean activityRecognitionHardwareIsSupported,
+            ActivityRecognitionHardware activityRecognitionHardware,
+            int overlaySwitchResId,
+            int defaultServicePackageNameResId,
+            int initialPackageNameResId) {
+        mIsSupported = activityRecognitionHardwareIsSupported;
+        mInstance = activityRecognitionHardware;
+
+        mServiceWatcher = new ServiceWatcher(
+                context,
+                TAG,
+                "com.android.location.service.ActivityRecognitionProvider",
+                overlaySwitchResId,
+                defaultServicePackageNameResId,
+                initialPackageNameResId,
+                BackgroundThread.getHandler()) {
+            @Override
+            protected void onBind() {
+                runOnBinder(ActivityRecognitionProxy.this::initializeService);
+            }
+        };
+    }
+
+    private void initializeService(IBinder binder) {
+        try {
+            String descriptor = binder.getInterfaceDescriptor();
+
+            if (IActivityRecognitionHardwareWatcher.class.getCanonicalName().equals(
+                    descriptor)) {
+                IActivityRecognitionHardwareWatcher watcher =
+                        IActivityRecognitionHardwareWatcher.Stub.asInterface(binder);
+                if (mInstance != null) {
+                    watcher.onInstanceChanged(mInstance);
+                }
+            } else if (IActivityRecognitionHardwareClient.class.getCanonicalName()
+                    .equals(descriptor)) {
+                IActivityRecognitionHardwareClient client =
+                        IActivityRecognitionHardwareClient.Stub.asInterface(binder);
+                client.onAvailabilityChanged(mIsSupported, mInstance);
+            } else {
+                Log.e(TAG, "Invalid descriptor found on connection: " + descriptor);
+            }
+        } catch (RemoteException e) {
+            Log.w(TAG, e);
+        }
+    }
+}
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/locksettings/LockSettingsService.java b/services/core/java/com/android/server/locksettings/LockSettingsService.java
index 8734ceb6..a9ae74f 100644
--- a/services/core/java/com/android/server/locksettings/LockSettingsService.java
+++ b/services/core/java/com/android/server/locksettings/LockSettingsService.java
@@ -947,6 +947,10 @@
     public void setSeparateProfileChallengeEnabled(int userId, boolean enabled,
             String managedUserPassword) {
         checkWritePermission(userId);
+        if (!mLockPatternUtils.hasSecureLockScreen()) {
+            throw new UnsupportedOperationException(
+                    "This operation requires secure lock screen feature.");
+        }
         synchronized (mSeparateChallengeLock) {
             setSeparateProfileChallengeEnabledLocked(userId, enabled, managedUserPassword);
         }
@@ -1305,6 +1309,10 @@
     public void setLockCredential(String credential, int type, String savedCredential,
             int requestedQuality, int userId)
             throws RemoteException {
+        if (!mLockPatternUtils.hasSecureLockScreen()) {
+            throw new UnsupportedOperationException(
+                    "This operation requires secure lock screen feature");
+        }
         checkWritePermission(userId);
         synchronized (mSeparateChallengeLock) {
             setLockCredentialInternal(credential, type, savedCredential, requestedQuality, userId);
@@ -2906,6 +2914,10 @@
         @Override
         public boolean setLockCredentialWithToken(String credential, int type, long tokenHandle,
                 byte[] token, int requestedQuality, int userId) {
+            if (!mLockPatternUtils.hasSecureLockScreen()) {
+                throw new UnsupportedOperationException(
+                        "This operation requires secure lock screen feature.");
+            }
             try {
                 return LockSettingsService.this.setLockCredentialWithToken(credential, type,
                         tokenHandle, token, requestedQuality, userId);
diff --git a/services/core/java/com/android/server/locksettings/LockSettingsShellCommand.java b/services/core/java/com/android/server/locksettings/LockSettingsShellCommand.java
index 07f23ce..6163077 100644
--- a/services/core/java/com/android/server/locksettings/LockSettingsShellCommand.java
+++ b/services/core/java/com/android/server/locksettings/LockSettingsShellCommand.java
@@ -18,6 +18,7 @@
 
 import static android.app.admin.DevicePolicyManager.PASSWORD_QUALITY_ALPHABETIC;
 import static android.app.admin.DevicePolicyManager.PASSWORD_QUALITY_NUMERIC;
+
 import static com.android.internal.widget.LockPatternUtils.stringToPattern;
 
 import android.app.ActivityManager;
@@ -58,6 +59,18 @@
             mCurrentUserId = ActivityManager.getService().getCurrentUser().id;
 
             parseArgs();
+            if (!mLockPatternUtils.hasSecureLockScreen()) {
+                switch (cmd) {
+                    case COMMAND_HELP:
+                    case COMMAND_GET_DISABLED:
+                    case COMMAND_SET_DISABLED:
+                        break;
+                    default:
+                        getErrPrintWriter().println(
+                                "The device does not support lock screen - ignoring the command.");
+                        return -1;
+                }
+            }
             if (!checkCredential()) {
                 return -1;
             }
diff --git a/services/core/java/com/android/server/media/MediaSessionRecord.java b/services/core/java/com/android/server/media/MediaSessionRecord.java
index d8c2432..af790f2 100644
--- a/services/core/java/com/android/server/media/MediaSessionRecord.java
+++ b/services/core/java/com/android/server/media/MediaSessionRecord.java
@@ -78,6 +78,7 @@
     private final String mPackageName;
     private final String mTag;
     private final ControllerLink mController;
+    private final MediaSession.Token mSessionToken;
     private final SessionLink mSession;
     private final SessionCb mSessionCb;
     private final MediaSessionService.ServiceImpl mService;
@@ -128,6 +129,7 @@
         mPackageName = ownerPackageName;
         mTag = tag;
         mController = new ControllerLink(new ControllerStub());
+        mSessionToken = new MediaSession.Token(mController);
         mSession = new SessionLink(new SessionStub());
         mSessionCb = new SessionCb(cb);
         mService = service;
@@ -157,6 +159,15 @@
     }
 
     /**
+     * Get the session token for creating {@link MediaController}.
+     *
+     * @return The session token.
+     */
+    public MediaSession.Token getSessionToken() {
+        return mSessionToken;
+    }
+
+    /**
      * Get the info for this session.
      *
      * @return Info that identifies this session.
diff --git a/services/core/java/com/android/server/media/MediaSessionServiceImpl.java b/services/core/java/com/android/server/media/MediaSessionServiceImpl.java
index e3ae8a7..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;
@@ -51,7 +49,6 @@
 import android.media.session.IOnMediaKeyListener;
 import android.media.session.IOnVolumeKeyLongPressListener;
 import android.media.session.ISession2TokensListener;
-import android.media.session.ISessionController;
 import android.media.session.ISessionManager;
 import android.media.session.MediaSession;
 import android.media.session.MediaSessionManager;
@@ -61,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;
@@ -290,9 +286,7 @@
             return;
         }
         try {
-            mRvc.remoteVolumeChanged(
-                    ISessionController.Stub.asInterface(session.getControllerLink().getBinder()),
-                    flags);
+            mRvc.remoteVolumeChanged(session.getSessionToken(), flags);
         } catch (Exception e) {
             Log.wtf(TAG, "Error sending volume change to system UI.", e);
         }
@@ -618,7 +612,7 @@
             int size = records.size();
             ArrayList<MediaSession.Token> tokens = new ArrayList<MediaSession.Token>();
             for (int i = 0; i < size; i++) {
-                tokens.add(new MediaSession.Token(records.get(i).getControllerLink()));
+                tokens.add(records.get(i).getSessionToken());
             }
             pushRemoteVolumeUpdateLocked(userId);
             for (int i = mSessionsListeners.size() - 1; i >= 0; i--) {
@@ -645,9 +639,7 @@
                     return;
                 }
                 MediaSessionRecord record = user.mPriorityStack.getDefaultRemoteSession(userId);
-                mRvc.updateRemoteController(record == null ? null
-                        : ISessionController.Stub.asInterface(
-                                record.getControllerLink().getBinder()));
+                mRvc.updateRemoteController(record == null ? null : record.getSessionToken());
             } catch (RemoteException e) {
                 Log.wtf(TAG, "Error sending default remote volume to sys ui.", e);
             }
@@ -864,7 +856,7 @@
                 MediaSessionRecord mediaButtonSession = getMediaButtonSessionLocked();
                 if (mediaButtonSession != null) {
                     mCallback.onAddressedPlayerChangedToMediaSession(
-                            new MediaSession.Token(mediaButtonSession.getControllerLink()));
+                            mediaButtonSession.getSessionToken());
                 } else if (mCurrentFullUserRecord.mLastMediaButtonReceiver != null) {
                     mCallback.onAddressedPlayerChangedToMediaButtonReceiver(
                             mCurrentFullUserRecord.mLastMediaButtonReceiver
@@ -1012,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);
             }
@@ -1804,7 +1829,7 @@
                 if (mCurrentFullUserRecord.mCallback != null) {
                     try {
                         mCurrentFullUserRecord.mCallback.onMediaKeyEventDispatchedToMediaSession(
-                                keyEvent, new MediaSession.Token(session.getControllerLink()));
+                                keyEvent, session.getSessionToken());
                     } catch (RemoteException e) {
                         Log.w(TAG, "Failed to send callback", e);
                     }
@@ -2119,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/NotificationDelegate.java b/services/core/java/com/android/server/notification/NotificationDelegate.java
index ee60daa..c2dc554 100644
--- a/services/core/java/com/android/server/notification/NotificationDelegate.java
+++ b/services/core/java/com/android/server/notification/NotificationDelegate.java
@@ -42,7 +42,8 @@
     void onNotificationVisibilityChanged(
             NotificationVisibility[] newlyVisibleKeys,
             NotificationVisibility[] noLongerVisibleKeys);
-    void onNotificationExpansionChanged(String key, boolean userAction, boolean expanded);
+    void onNotificationExpansionChanged(String key, boolean userAction, boolean expanded,
+            int notificationLocation);
     void onNotificationDirectReplied(String key);
     void onNotificationSettingsViewed(String key);
 
diff --git a/services/core/java/com/android/server/notification/NotificationManagerService.java b/services/core/java/com/android/server/notification/NotificationManagerService.java
index dc14247..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();
@@ -868,7 +870,7 @@
 
         @Override
         public void onNotificationExpansionChanged(String key,
-                boolean userAction, boolean expanded) {
+                boolean userAction, boolean expanded, int notificationLocation) {
             synchronized (mNotificationLock) {
                 NotificationRecord r = mNotificationsByKey.get(key);
                 if (r != null) {
@@ -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);
         }
     }
diff --git a/services/core/java/com/android/server/notification/NotificationRecord.java b/services/core/java/com/android/server/notification/NotificationRecord.java
index 5598741..ac965fa 100644
--- a/services/core/java/com/android/server/notification/NotificationRecord.java
+++ b/services/core/java/com/android/server/notification/NotificationRecord.java
@@ -690,6 +690,8 @@
                                     importance));
                 }
             }
+            // We have now gotten all the information out of the adjustments and can forget them.
+            mAdjustments.clear();
         }
     }
 
@@ -1263,7 +1265,7 @@
     public LogMaker getAdjustmentLogMaker() {
         return getLogMaker()
                 .setCategory(MetricsEvent.NOTIFICATION_ITEM)
-                .setType(MetricsEvent.NOTIFICATION_ASSISTANT_ADJUSTMENT);
+                .setType(MetricsEvent.TYPE_NOTIFICATION_ASSISTANT_ADJUSTMENT);
     }
 
     @VisibleForTesting
diff --git a/services/core/java/com/android/server/os/BugreportManagerService.java b/services/core/java/com/android/server/os/BugreportManagerService.java
index e241591..bee7a8b 100644
--- a/services/core/java/com/android/server/os/BugreportManagerService.java
+++ b/services/core/java/com/android/server/os/BugreportManagerService.java
@@ -37,7 +37,6 @@
     @Override
     public void onStart() {
         mService = new BugreportManagerServiceImpl(getContext());
-        // TODO(b/111441001): Needs sepolicy to be submitted first.
-        // publishBinderService(Context.BUGREPORT_SERVICE, mService);
+        publishBinderService(Context.BUGREPORT_SERVICE, mService);
     }
 }
diff --git a/services/core/java/com/android/server/os/BugreportManagerServiceImpl.java b/services/core/java/com/android/server/os/BugreportManagerServiceImpl.java
index 1178cc1..f736056 100644
--- a/services/core/java/com/android/server/os/BugreportManagerServiceImpl.java
+++ b/services/core/java/com/android/server/os/BugreportManagerServiceImpl.java
@@ -44,6 +44,7 @@
  */
 class BugreportManagerServiceImpl extends IDumpstate.Stub {
     private static final String TAG = "BugreportManagerService";
+    private static final String BUGREPORT_SERVICE = "bugreportd";
     private static final long DEFAULT_BUGREPORT_SERVICE_TIMEOUT_MILLIS = 30 * 1000;
 
     private IDumpstate mDs = null;
@@ -64,6 +65,8 @@
         throw new UnsupportedOperationException("setListener is not allowed on this service");
     }
 
+    // TODO(b/111441001): Intercept onFinished here in system server and shutdown
+    // the bugreportd service.
     @Override
     @RequiresPermission(android.Manifest.permission.DUMP)
     public void startBugreport(int callingUidUnused, String callingPackage,
@@ -84,6 +87,14 @@
                 bugreportFd, screenshotFd, bugreportMode, listener);
     }
 
+    @Override
+    @RequiresPermission(android.Manifest.permission.DUMP)
+    public void cancelBugreport() throws RemoteException {
+        // This tells init to cancel bugreportd service.
+        SystemProperties.set("ctl.stop", BUGREPORT_SERVICE);
+        mDs = null;
+    }
+
     private boolean validate(@BugreportParams.BugreportMode int mode) {
         if (mode != BugreportParams.BUGREPORT_MODE_FULL
                 && mode != BugreportParams.BUGREPORT_MODE_INTERACTIVE
@@ -107,7 +118,7 @@
      */
     private IDumpstate getDumpstateService() {
         // Start bugreport service.
-        SystemProperties.set("ctl.start", "bugreport");
+        SystemProperties.set("ctl.start", BUGREPORT_SERVICE);
 
         IDumpstate ds = null;
         boolean timedOut = false;
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/DynamicCodeLoggingService.java b/services/core/java/com/android/server/pm/DynamicCodeLoggingService.java
index 2ae424d..5b765df 100644
--- a/services/core/java/com/android/server/pm/DynamicCodeLoggingService.java
+++ b/services/core/java/com/android/server/pm/DynamicCodeLoggingService.java
@@ -22,63 +22,117 @@
 import android.app.job.JobService;
 import android.content.ComponentName;
 import android.content.Context;
+import android.os.Process;
 import android.os.ServiceManager;
+import android.util.ByteStringUtils;
+import android.util.EventLog;
 import android.util.Log;
 
 import com.android.server.pm.dex.DexLogger;
 
+import java.util.ArrayList;
+import java.util.List;
 import java.util.concurrent.TimeUnit;
+import java.util.regex.Matcher;
+import java.util.regex.Pattern;
 
 /**
- * Scheduled job to trigger logging of app dynamic code loading. This runs daily while idle and
- * charging. The actual logging is performed by {@link DexLogger}.
+ * Scheduled jobs related to logging of app dynamic code loading. The idle logging job runs daily
+ * while idle and charging  and calls {@link DexLogger} to write dynamic code information to the
+ * event log. The audit watching job scans the event log periodically while idle to find AVC audit
+ * messages indicating use of dynamic native code and adds the information to {@link DexLogger}.
  * {@hide}
  */
 public class DynamicCodeLoggingService extends JobService {
     private static final String TAG = DynamicCodeLoggingService.class.getName();
 
-    private static final int JOB_ID = 2030028;
-    private static final long PERIOD_MILLIS = TimeUnit.DAYS.toMillis(1);
-
-    private volatile boolean mStopRequested = false;
-
     private static final boolean DEBUG = false;
 
+    private static final int IDLE_LOGGING_JOB_ID = 2030028;
+    private static final int AUDIT_WATCHING_JOB_ID = 203142925;
+
+    private static final long IDLE_LOGGING_PERIOD_MILLIS = TimeUnit.DAYS.toMillis(1);
+    private static final long AUDIT_WATCHING_PERIOD_MILLIS = TimeUnit.HOURS.toMillis(2);
+
+    private static final int AUDIT_AVC = 1400;  // Defined in linux/audit.h
+    private static final String AVC_PREFIX = "type=" + AUDIT_AVC + " ";
+
+    private static final Pattern EXECUTE_NATIVE_AUDIT_PATTERN =
+            Pattern.compile(".*\\bavc: granted \\{ execute(?:_no_trans|) \\} .*"
+                    + "\\bpath=(?:\"([^\" ]*)\"|([0-9A-F]+)) .*"
+                    + "\\bscontext=u:r:untrusted_app_2(?:5|7):.*"
+                    + "\\btcontext=u:object_r:app_data_file:.*"
+                    + "\\btclass=file\\b.*");
+
+    private volatile boolean mIdleLoggingStopRequested = false;
+    private volatile boolean mAuditWatchingStopRequested = false;
+
     /**
-     * Schedule our job with the {@link JobScheduler}.
+     * Schedule our jobs with the {@link JobScheduler}.
      */
     public static void schedule(Context context) {
         ComponentName serviceName = new ComponentName(
                 "android", DynamicCodeLoggingService.class.getName());
 
         JobScheduler js = (JobScheduler) context.getSystemService(Context.JOB_SCHEDULER_SERVICE);
-        js.schedule(new JobInfo.Builder(JOB_ID, serviceName)
+        js.schedule(new JobInfo.Builder(IDLE_LOGGING_JOB_ID, serviceName)
                 .setRequiresDeviceIdle(true)
                 .setRequiresCharging(true)
-                .setPeriodic(PERIOD_MILLIS)
+                .setPeriodic(IDLE_LOGGING_PERIOD_MILLIS)
                 .build());
+        js.schedule(new JobInfo.Builder(AUDIT_WATCHING_JOB_ID, serviceName)
+                .setRequiresDeviceIdle(true)
+                .setRequiresBatteryNotLow(true)
+                .setPeriodic(AUDIT_WATCHING_PERIOD_MILLIS)
+                .build());
+
         if (DEBUG) {
-            Log.d(TAG, "Job scheduled");
+            Log.d(TAG, "Jobs scheduled");
         }
     }
 
     @Override
     public boolean onStartJob(JobParameters params) {
+        int jobId = params.getJobId();
         if (DEBUG) {
-            Log.d(TAG, "onStartJob");
+            Log.d(TAG, "onStartJob " + jobId);
         }
-        mStopRequested = false;
-        new IdleLoggingThread(params).start();
-        return true;  // Job is running on another thread
+        switch (jobId) {
+            case IDLE_LOGGING_JOB_ID:
+                mIdleLoggingStopRequested = false;
+                new IdleLoggingThread(params).start();
+                return true;  // Job is running on another thread
+            case AUDIT_WATCHING_JOB_ID:
+                mAuditWatchingStopRequested = false;
+                new AuditWatchingThread(params).start();
+                return true;  // Job is running on another thread
+            default:
+                // Shouldn't happen, but indicate nothing is running.
+                return false;
+        }
     }
 
     @Override
     public boolean onStopJob(JobParameters params) {
+        int jobId = params.getJobId();
         if (DEBUG) {
-            Log.d(TAG, "onStopJob");
+            Log.d(TAG, "onStopJob " + jobId);
         }
-        mStopRequested = true;
-        return true;  // Requests job be re-scheduled.
+        switch (jobId) {
+            case IDLE_LOGGING_JOB_ID:
+                mIdleLoggingStopRequested = true;
+                return true;  // Requests job be re-scheduled.
+            case AUDIT_WATCHING_JOB_ID:
+                mAuditWatchingStopRequested = true;
+                return true;  // Requests job be re-scheduled.
+            default:
+                return false;
+        }
+    }
+
+    private static DexLogger getDexLogger() {
+        PackageManagerService pm = (PackageManagerService) ServiceManager.getService("package");
+        return pm.getDexManager().getDexLogger();
     }
 
     private class IdleLoggingThread extends Thread {
@@ -92,14 +146,13 @@
         @Override
         public void run() {
             if (DEBUG) {
-                Log.d(TAG, "Starting logging run");
+                Log.d(TAG, "Starting IdleLoggingJob run");
             }
 
-            PackageManagerService pm = (PackageManagerService) ServiceManager.getService("package");
-            DexLogger dexLogger = pm.getDexManager().getDexLogger();
+            DexLogger dexLogger = getDexLogger();
             for (String packageName : dexLogger.getAllPackagesWithDynamicCodeLoading()) {
-                if (mStopRequested) {
-                    Log.w(TAG, "Stopping logging run at scheduler request");
+                if (mIdleLoggingStopRequested) {
+                    Log.w(TAG, "Stopping IdleLoggingJob run at scheduler request");
                     return;
                 }
 
@@ -108,8 +161,128 @@
 
             jobFinished(mParams, /* reschedule */ false);
             if (DEBUG) {
-                Log.d(TAG, "Finished logging run");
+                Log.d(TAG, "Finished IdleLoggingJob run");
             }
         }
     }
+
+    private class AuditWatchingThread extends Thread {
+        private final JobParameters mParams;
+
+        AuditWatchingThread(JobParameters params) {
+            super("DynamicCodeLoggingService_AuditWatchingJob");
+            mParams = params;
+        }
+
+        @Override
+        public void run() {
+            if (DEBUG) {
+                Log.d(TAG, "Starting AuditWatchingJob run");
+            }
+
+            if (processAuditEvents()) {
+                jobFinished(mParams, /* reschedule */ false);
+                if (DEBUG) {
+                    Log.d(TAG, "Finished AuditWatchingJob run");
+                }
+            }
+        }
+
+        private boolean processAuditEvents() {
+            // Scan the event log for SELinux (avc) audit messages indicating when an
+            // (untrusted) app has executed native code from an app data
+            // file. Matches are recorded in DexLogger.
+            //
+            // These messages come from the kernel audit system via logd. (Note that
+            // some devices may not generate these messages at all, or the format may
+            // be different, in which case nothing will be recorded.)
+            //
+            // The messages use the auditd tag and the uid of the app that executed
+            // the code.
+            //
+            // A typical message might look like this:
+            // type=1400 audit(0.0:521): avc: granted { execute } for comm="executable"
+            //  path="/data/data/com.dummy.app/executable" dev="sda13" ino=1655302
+            //  scontext=u:r:untrusted_app_27:s0:c66,c257,c512,c768
+            //  tcontext=u:object_r:app_data_file:s0:c66,c257,c512,c768 tclass=file
+            //
+            // The information we want is the uid and the path. (Note this may be
+            // either a quoted string, as shown above, or a sequence of hex-encoded
+            // bytes.)
+            //
+            // On each run we process all the matching events in the log. This may
+            // mean re-processing events we have already seen, and in any case there
+            // may be duplicate events for the same app+file. These are de-duplicated
+            // by DexLogger.
+            //
+            // Note that any app can write a message to the event log, including one
+            // that looks exactly like an AVC audit message, so the information may
+            // be spoofed by an app; in such a case the uid we see will be the app
+            // that generated the spoof message.
+
+            try {
+                int[] tags = { EventLog.getTagCode("auditd") };
+                if (tags[0] == -1) {
+                    // auditd is not a registered tag on this system, so there can't be any messages
+                    // of interest.
+                    return true;
+                }
+
+                DexLogger dexLogger = getDexLogger();
+
+                List<EventLog.Event> events = new ArrayList<>();
+                EventLog.readEvents(tags, events);
+
+                for (int i = 0; i < events.size(); ++i) {
+                    if (mAuditWatchingStopRequested) {
+                        Log.w(TAG, "Stopping AuditWatchingJob run at scheduler request");
+                        return false;
+                    }
+
+                    EventLog.Event event = events.get(i);
+
+                    // Discard clearly unrelated messages as quickly as we can.
+                    int uid = event.getUid();
+                    if (!Process.isApplicationUid(uid)) {
+                        continue;
+                    }
+                    Object data = event.getData();
+                    if (!(data instanceof String)) {
+                        continue;
+                    }
+                    String message = (String) data;
+                    if (!message.startsWith(AVC_PREFIX)) {
+                        continue;
+                    }
+
+                    // And then use a regular expression to verify it's one of the messages we're
+                    // interested in and to extract the path of the file being loaded.
+                    Matcher matcher = EXECUTE_NATIVE_AUDIT_PATTERN.matcher(message);
+                    if (!matcher.matches()) {
+                        continue;
+                    }
+                    String path = matcher.group(1);
+                    if (path == null) {
+                        // If the path contains spaces or various weird characters the kernel
+                        // hex-encodes the bytes; we need to undo that.
+                        path = unhex(matcher.group(2));
+                    }
+                    dexLogger.recordNative(uid, path);
+                }
+
+                return true;
+            } catch (Exception e) {
+                Log.e(TAG, "AuditWatchingJob failed", e);
+                return true;
+            }
+        }
+    }
+
+    private static String unhex(String hexEncodedPath) {
+        byte[] bytes = ByteStringUtils.fromHexToByteArray(hexEncodedPath);
+        if (bytes == null || bytes.length == 0) {
+            return "";
+        }
+        return new String(bytes);
+    }
 }
diff --git a/services/core/java/com/android/server/pm/Installer.java b/services/core/java/com/android/server/pm/Installer.java
index 8a6105c..efafdfa 100644
--- a/services/core/java/com/android/server/pm/Installer.java
+++ b/services/core/java/com/android/server/pm/Installer.java
@@ -611,6 +611,31 @@
         }
     }
 
+    public boolean snapshotAppData(String pkg, @UserIdInt int userId, int storageFlags)
+            throws InstallerException {
+        if (!checkBeforeRemote()) return false;
+
+        try {
+            mInstalld.snapshotAppData(null, pkg, userId, storageFlags);
+            return true;
+        } catch (Exception e) {
+            throw InstallerException.from(e);
+        }
+    }
+
+    public boolean restoreAppDataSnapshot(String pkg, @AppIdInt  int appId, long ceDataInode,
+            String seInfo, @UserIdInt int userId, int storageFlags) throws InstallerException {
+        if (!checkBeforeRemote()) return false;
+
+        try {
+            mInstalld.restoreAppDataSnapshot(null, pkg, appId, ceDataInode, seInfo, userId,
+                    storageFlags);
+            return true;
+        } catch (Exception e) {
+            throw InstallerException.from(e);
+        }
+    }
+
     private static void assertValidInstructionSet(String instructionSet)
             throws InstallerException {
         for (String abi : Build.SUPPORTED_ABIS) {
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/OWNERS b/services/core/java/com/android/server/pm/OWNERS
new file mode 100644
index 0000000..60d7925
--- /dev/null
+++ b/services/core/java/com/android/server/pm/OWNERS
@@ -0,0 +1,76 @@
+hackbod@android.com
+hackbod@google.com
+jsharkey@android.com
+jsharkey@google.com
+narayan@google.com
+patb@google.com
+svetoslavganov@android.com
+svetoslavganov@google.com
+toddke@android.com
+toddke@google.com
+
+# dex
+per-file AbstractStatsBase.java = agampe@google.com
+per-file AbstractStatsBase.java = calin@google.com
+per-file AbstractStatsBase.java = ngeoffray@google.com
+per-file BackgroundDexOptService.java = agampe@google.com
+per-file BackgroundDexOptService.java = calin@google.com
+per-file BackgroundDexOptService.java = ngeoffray@google.com
+per-file CompilerStats.java = agampe@google.com
+per-file CompilerStats.java = calin@google.com
+per-file CompilerStats.java = ngeoffray@google.com
+per-file InstructionSets.java = agampe@google.com
+per-file InstructionSets.java = calin@google.com
+per-file InstructionSets.java = ngeoffray@google.com
+per-file OtaDexoptService.java = agampe@google.com
+per-file OtaDexoptService.java = calin@google.com
+per-file OtaDexoptService.java = ngeoffray@google.com
+per-file OtaDexoptShellCommand.java = agampe@google.com
+per-file OtaDexoptShellCommand.java = calin@google.com
+per-file OtaDexoptShellCommand.java = ngeoffray@google.com
+per-file PackageDexOptimizer.java = agampe@google.com
+per-file PackageDexOptimizer.java = calin@google.com
+per-file PackageDexOptimizer.java = ngeoffray@google.com
+per-file PackageManagerServiceCompilerMapping.java = agampe@google.com
+per-file PackageManagerServiceCompilerMapping.java = calin@google.com
+per-file PackageManagerServiceCompilerMapping.java = ngeoffray@google.com
+per-file PackageUsage.java = agampe@google.com
+per-file PackageUsage.java = calin@google.com
+per-file PackageUsage.java = ngeoffray@google.com
+
+# multi user / cross profile
+per-file CrossProfileAppsServiceImpl.java = omakoto@google.com
+per-file CrossProfileAppsServiceImpl.java = yamasani@google.com
+per-file CrossProfileAppsService.java = omakoto@google.com
+per-file CrossProfileAppsService.java = yamasani@google.com
+per-file CrossProfileIntentFilter.java = omakoto@google.com
+per-file CrossProfileIntentFilter.java = yamasani@google.com
+per-file CrossProfileIntentResolver.java = omakoto@google.com
+per-file CrossProfileIntentResolver.java = yamasani@google.com
+per-file UserManagerService.java = omakoto@google.com
+per-file UserManagerService.java = yamasani@google.com
+per-file UserRestrictionsUtils.java = omakoto@google.com
+per-file UserRestrictionsUtils.java = yamasani@google.com
+
+# security
+per-file KeySetHandle.java = cbrubaker@google.com
+per-file KeySetManagerService.java = cbrubaker@google.com
+per-file PackageKeySetData.java = cbrubaker@google.com
+per-file PackageSignatures.java = cbrubaker@google.com
+per-file SELinuxMMAC.java = cbrubaker@google.com
+
+# shortcuts
+per-file LauncherAppsService.java = omakoto@google.com
+per-file ShareTargetInfo.java = omakoto@google.com
+per-file ShortcutBitmapSaver.java = omakoto@google.com
+per-file ShortcutDumpFiles.java = omakoto@google.com
+per-file ShortcutLauncher.java = omakoto@google.com
+per-file ShortcutNonPersistentUser.java = omakoto@google.com
+per-file ShortcutPackage.java = omakoto@google.com
+per-file ShortcutPackageInfo.java = omakoto@google.com
+per-file ShortcutPackageItem.java = omakoto@google.com
+per-file ShortcutParser.java = omakoto@google.com
+per-file ShortcutRequestPinProcessor.java = omakoto@google.com
+per-file ShortcutService.java = omakoto@google.com
+per-file ShortcutUser.java = omakoto@google.com
+
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 bf4e272..eab5c8f 100644
--- a/services/core/java/com/android/server/pm/PackageInstallerService.java
+++ b/services/core/java/com/android/server/pm/PackageInstallerService.java
@@ -310,7 +310,6 @@
             in.setInput(fis, StandardCharsets.UTF_8.name());
 
             int type;
-            PackageInstallerSession currentSession = null;
             while ((type = in.next()) != END_DOCUMENT) {
                 if (type == START_TAG) {
                     final String tag = in.getName();
@@ -320,9 +319,7 @@
                             session = PackageInstallerSession.readFromXml(in, mInternalCallback,
                                     mContext, mPm, mInstallThread.getLooper(), mStagingManager,
                                     mSessionsDir, this);
-                            currentSession = session;
                         } catch (Exception e) {
-                            currentSession = null;
                             Slog.e(TAG, "Could not read session", e);
                             continue;
                         }
@@ -347,10 +344,6 @@
                             addHistoricalSessionLocked(session);
                         }
                         mAllocatedSessions.put(session.sessionId, true);
-                    } else if (currentSession != null
-                            && PackageInstallerSession.TAG_CHILD_SESSION.equals(tag)) {
-                        currentSession.addChildSessionIdInternal(
-                                PackageInstallerSession.readChildSessionIdFromXml(in));
                     }
                 }
             }
@@ -544,7 +537,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);
@@ -1132,6 +1126,7 @@
 
         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);
         }
 
diff --git a/services/core/java/com/android/server/pm/PackageInstallerSession.java b/services/core/java/com/android/server/pm/PackageInstallerSession.java
index 12d335d..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;
     }
@@ -992,8 +998,12 @@
 
         // Read transfers from the original owner stay open, but as the session's data
         // cannot be modified anymore, there is no leak of information. For staged sessions,
-        // further validation may be performed by the staging manager.
+        // further validation is performed by the staging manager.
         if (!params.isMultiPackage) {
+            if ((params.installFlags & PackageManager.INSTALL_APEX) != 0) {
+                // For APEX, validation is done by StagingManager post-commit.
+                return;
+            }
             final PackageInfo pkgInfo = mPm.getPackageInfo(
                     params.appPackageName, PackageManager.GET_SIGNATURES
                             | PackageManager.MATCH_STATIC_SHARED_LIBRARIES /*flags*/, userId);
@@ -1001,16 +1011,7 @@
             resolveStageDirLocked();
 
             try {
-                if ((params.installFlags & PackageManager.INSTALL_APEX) != 0) {
-                    // TODO(b/118865310): Remove this when APEX validation is done via
-                    //                    StagingManager.
-                    validateApexInstallLocked(pkgInfo);
-                } else {
-                    // Verify that stage looks sane with respect to existing application.
-                    // This currently only ensures packageName, versionCode, and certificate
-                    // consistency.
-                    validateApkInstallLocked(pkgInfo);
-                }
+                validateApkInstallLocked(pkgInfo);
             } catch (PackageManagerException e) {
                 throw e;
             } catch (Throwable e) {
@@ -1301,54 +1302,6 @@
                 (params.installFlags & PackageManager.DONT_KILL_APP) != 0;
     }
 
-    @GuardedBy("mLock")
-    private void validateApexInstallLocked(@Nullable PackageInfo pkgInfo)
-            throws PackageManagerException {
-        mResolvedStagedFiles.clear();
-        mResolvedInheritedFiles.clear();
-
-        try {
-            resolveStageDirLocked();
-        } catch (IOException e) {
-            throw new PackageManagerException(INSTALL_FAILED_CONTAINER_ERROR,
-                "Failed to resolve stage location", e);
-        }
-
-        final File[] addedFiles = mResolvedStageDir.listFiles(sAddedFilter);
-        if (ArrayUtils.isEmpty(addedFiles)) {
-            throw new PackageManagerException(INSTALL_FAILED_INVALID_APK, "No packages staged");
-        }
-
-        if (addedFiles.length > 1) {
-            throw new PackageManagerException(INSTALL_FAILED_INVALID_APK,
-                "Only one APEX file at a time might be installed");
-        }
-        File addedFile = addedFiles[0];
-        final ApkLite apk;
-        try {
-            apk = PackageParser.parseApkLite(
-                addedFile, PackageParser.PARSE_COLLECT_CERTIFICATES);
-        } catch (PackageParserException e) {
-            throw PackageManagerException.from(e);
-        }
-
-        mPackageName = apk.packageName;
-        mVersionCode = apk.getLongVersionCode();
-        mSigningDetails = apk.signingDetails;
-        mResolvedBaseFile = addedFile;
-
-        // STOPSHIP: Ensure that we remove the non-staged version of APEX installs in production
-        // because we currently do not verify that signatures are consistent with the previously
-        // installed version in that case.
-        //
-        // When that happens, this hack can be reverted and we can rely on APEXd to map between
-        // APEX files and their package names instead of parsing it out of the AndroidManifest
-        // such as here.
-        if (params.appPackageName == null) {
-            params.appPackageName = mPackageName;
-        }
-    }
-
     /**
      * Validate install by confirming that all application packages are have
      * consistent package name, version code, and signing certificates.
@@ -1911,22 +1864,30 @@
     }
 
     @Override
-    public void addChildSessionId(int sessionId) {
-        final PackageInstallerSession session = mSessionProvider.getSession(sessionId);
-        if (session == null) {
+    public void addChildSessionId(int childSessionId) {
+        final PackageInstallerSession childSession = mSessionProvider.getSession(childSessionId);
+        if (childSession == null) {
             throw new RemoteException("Unable to add child.",
-                    new PackageManagerException("Child session " + sessionId + " does not exist"),
+                    new PackageManagerException("Child session " + childSessionId
+                            + " does not exist"),
+                    false, true).rethrowAsRuntimeException();
+        }
+        // Session groups must be consistent wrt to isStaged parameter. Non-staging session
+        // cannot be grouped with staging sessions.
+        if (this.params.isStaged ^ childSession.params.isStaged) {
+            throw new RemoteException("Unable to add child.",
+                    new PackageManagerException("Child session " + childSessionId
+                            + " and parent session " + this.sessionId + " do not have consistent"
+                            + " staging session settings."),
                     false, true).rethrowAsRuntimeException();
         }
         synchronized (mLock) {
-            final int indexOfSession = mChildSessionIds.indexOfKey(sessionId);
+            final int indexOfSession = mChildSessionIds.indexOfKey(childSessionId);
             if (indexOfSession >= 0) {
                 return;
             }
-            session.setParentSessionId(this.sessionId);
-            // TODO: sanity check, if parent session is staged then child session should be
-            //       marked as staged.
-            addChildSessionIdInternal(sessionId);
+            childSession.setParentSessionId(this.sessionId);
+            addChildSessionIdInternal(childSessionId);
         }
     }
 
@@ -2016,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);
     }
@@ -2038,6 +2003,7 @@
             mStagedSessionApplied = true;
             mStagedSessionFailed = false;
             mStagedSessionErrorCode = SessionInfo.NO_ERROR;
+            mStagedSessionErrorMessage = "";
         }
         mCallback.onStagedSessionChanged(this);
     }
@@ -2057,6 +2023,16 @@
         return mStagedSessionFailed;
     }
 
+    /** {@hide} */
+    @StagedSessionErrorCode int getStagedSessionErrorCode() {
+        return mStagedSessionErrorCode;
+    }
+
+    /** {@hide} */
+    String getStagedSessionErrorMessage() {
+        return mStagedSessionErrorMessage;
+    }
+
     private void destroyInternal() {
         synchronized (mLock) {
             mSealed = true;
@@ -2173,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);
@@ -2221,35 +2199,6 @@
         out.endTag(null, TAG_SESSION);
     }
 
-    private static String[] readGrantedRuntimePermissions(XmlPullParser in)
-            throws IOException, XmlPullParserException {
-        List<String> permissions = null;
-
-        final int outerDepth = in.getDepth();
-        int type;
-        while ((type = in.next()) != XmlPullParser.END_DOCUMENT
-                && (type != XmlPullParser.END_TAG || in.getDepth() > outerDepth)) {
-            if (type == XmlPullParser.END_TAG || type == XmlPullParser.TEXT) {
-                continue;
-            }
-            if (TAG_GRANTED_RUNTIME_PERMISSION.equals(in.getName())) {
-                String permission = readStringAttribute(in, ATTR_NAME);
-                if (permissions == null) {
-                    permissions = new ArrayList<>();
-                }
-                permissions.add(permission);
-            }
-        }
-
-        if (permissions == null) {
-            return null;
-        }
-
-        String[] permissionsArray = new String[permissions.size()];
-        permissions.toArray(permissionsArray);
-        return permissionsArray;
-    }
-
     // Sanity check to be performed when the session is restored from an external file. Only one
     // of the session states should be true, or none of them.
     private static boolean isStagedSessionStateValid(boolean isReady, boolean isApplied,
@@ -2273,8 +2222,6 @@
      * @param sessionProvider
      * @return The newly created session
      */
-    // TODO(patb,109941548): modify readFromXml to consume to the next tag session tag so we
-    //                       can have a complete session for the constructor
     public static PackageInstallerSession readFromXml(@NonNull XmlPullParser in,
             @NonNull PackageInstallerService.InternalCallback callback, @NonNull Context context,
             @NonNull PackageManagerService pm, Looper installerThread,
@@ -2314,8 +2261,6 @@
         params.volumeUuid = readStringAttribute(in, ATTR_VOLUME_UUID);
         params.installReason = readIntAttribute(in, ATTR_INSTALL_REASON);
 
-        params.grantedRuntimePermissions = readGrantedRuntimePermissions(in);
-
         final File appIconFile = buildAppIconFile(sessionId, sessionsDir);
         if (appIconFile.exists()) {
             params.appIcon = BitmapFactory.decodeFile(appIconFile.getAbsolutePath());
@@ -2324,17 +2269,54 @@
         final boolean isReady = readBooleanAttribute(in, ATTR_IS_READY);
         final boolean isFailed = readBooleanAttribute(in, ATTR_IS_FAILED);
         final boolean isApplied = readBooleanAttribute(in, ATTR_IS_APPLIED);
-        final int stagedSessionErrorCode = readIntAttribute(in, ATTR_STAGED_SESSION_ERROR_CODE);
+        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.");
         }
 
+        // Parse sub tags of this session, typically used for repeated values / arrays.
+        // Sub tags can come in any order, therefore we need to keep track of what we find while
+        // parsing and only set the right values at the end.
+
+        // Store the current depth. We should stop parsing when we reach an end tag at the same
+        // depth.
+        List<String> permissions = new ArrayList<>();
+        List<Integer> childSessionIds = new ArrayList<>();
+        int outerDepth = in.getDepth();
+        int type;
+        while ((type = in.next()) != XmlPullParser.END_DOCUMENT
+                && (type != XmlPullParser.END_TAG || in.getDepth() > outerDepth)) {
+            if (type == XmlPullParser.END_TAG || type == XmlPullParser.TEXT) {
+                continue;
+            }
+            if (TAG_GRANTED_RUNTIME_PERMISSION.equals(in.getName())) {
+                permissions.add(readStringAttribute(in, ATTR_NAME));
+            }
+            if (TAG_CHILD_SESSION.equals(in.getName())) {
+                childSessionIds.add(readIntAttribute(in, ATTR_SESSION_ID, SessionInfo.INVALID_ID));
+            }
+        }
+
+        if (permissions.size() > 0) {
+            params.grantedRuntimePermissions = permissions.stream().toArray(String[]::new);
+        }
+
+        int[] childSessionIdsArray;
+        if (childSessionIds.size() > 0) {
+            childSessionIdsArray = childSessionIds.stream().mapToInt(i -> i).toArray();
+        } else {
+            childSessionIdsArray = EMPTY_CHILD_SESSION_ARRAY;
+        }
+
         return new PackageInstallerSession(callback, context, pm, sessionProvider,
                 installerThread, stagingManager, sessionId, userId, installerPackageName,
                 installerUid, params, createdMillis, stageDir, stageCid, prepared, sealed,
-                EMPTY_CHILD_SESSION_ARRAY, parentSessionId, isReady, isFailed, isApplied,
-                stagedSessionErrorCode);
+                childSessionIdsArray, parentSessionId, isReady, isFailed, isApplied,
+                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 09fe26d..6eff815 100644
--- a/services/core/java/com/android/server/pm/PackageManagerService.java
+++ b/services/core/java/com/android/server/pm/PackageManagerService.java
@@ -42,6 +42,7 @@
 import static android.content.pm.PackageManager.FLAG_PERMISSION_SYSTEM_FIXED;
 import static android.content.pm.PackageManager.FLAG_PERMISSION_USER_FIXED;
 import static android.content.pm.PackageManager.FLAG_PERMISSION_USER_SET;
+import static android.content.pm.PackageManager.INSTALL_ALLOW_DOWNGRADE;
 import static android.content.pm.PackageManager.INSTALL_FAILED_ALREADY_EXISTS;
 import static android.content.pm.PackageManager.INSTALL_FAILED_DUPLICATE_PACKAGE;
 import static android.content.pm.PackageManager.INSTALL_FAILED_DUPLICATE_PERMISSION;
@@ -201,6 +202,7 @@
 import android.content.pm.dex.ArtManager;
 import android.content.pm.dex.DexMetadataHelper;
 import android.content.pm.dex.IArtManager;
+import android.content.rollback.IRollbackManager;
 import android.content.res.Resources;
 import android.database.ContentObserver;
 import android.graphics.Bitmap;
@@ -447,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;
@@ -9117,7 +9118,7 @@
                 pkgCompilationReason = PackageManagerService.REASON_BACKGROUND_DEXOPT;
             }
 
-            if (PRECOMPILED_LAYOUT_ENABLED) {
+            if (SystemProperties.getBoolean(PRECOMPILE_LAYOUTS, false)) {
                 mArtManagerService.compileLayouts(pkg);
             }
 
@@ -10572,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());
@@ -13872,6 +13871,38 @@
             }
         }
 
+        // If this is an update to a package that might be potentially downgraded, then we
+        // need to check with the rollback manager whether there's any userdata that might
+        // need to be restored for the package.
+        //
+        // TODO(narayan): Get this working for cases where userId == UserHandle.USER_ALL.
+        if (res.returnCode == PackageManager.INSTALL_SUCCEEDED && !doRestore && update) {
+            IRollbackManager rm = IRollbackManager.Stub.asInterface(
+                    ServiceManager.getService(Context.ROLLBACK_SERVICE));
+
+            final String packageName = res.pkg.applicationInfo.packageName;
+            final String seInfo = res.pkg.applicationInfo.seInfo;
+            final PackageSetting ps;
+            int appId = -1;
+            long ceDataInode = -1;
+            synchronized (mSettings) {
+                ps = mSettings.getPackageLPr(packageName);
+                if (ps != null) {
+                    appId = ps.appId;
+                    ceDataInode = ps.getCeDataInode(userId);
+                }
+            }
+
+            if (ps != null) {
+                try {
+                    rm.restoreUserData(packageName, userId, appId, ceDataInode, seInfo, token);
+                } catch (RemoteException re) {
+                    // Cannot happen, the RollbackManager is hosted in the same process.
+                }
+                doRestore = true;
+            }
+        }
+
         if (!doRestore) {
             // No restore possible, or the Backup Manager was mysteriously not
             // available -- just fire the post-install work request directly.
@@ -14569,6 +14600,9 @@
                     enableRollbackIntent.putExtra(
                             PackageManagerInternal.EXTRA_ENABLE_ROLLBACK_INSTALL_FLAGS,
                             installFlags);
+                    enableRollbackIntent.putExtra(
+                            PackageManagerInternal.EXTRA_ENABLE_ROLLBACK_INSTALLED_USERS,
+                            resolveUserIds(args.user.getIdentifier()));
                     enableRollbackIntent.setDataAndType(Uri.fromFile(new File(origin.resolvedPath)),
                             PACKAGE_MIME_TYPE);
                     enableRollbackIntent.addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION);
@@ -16176,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);
@@ -20635,7 +20669,6 @@
         storage.registerListener(mStorageListener);
 
         mInstallerService.systemReady();
-        mDexManager.systemReady();
         mPackageDexOptimizer.systemReady();
 
         getStorageManagerInternal().addExternalStoragePolicy(
@@ -23791,6 +23824,11 @@
             }
             return mArtManagerService.compileLayouts(pkg);
         }
+
+        @Override
+        public void finishPackageInstall(int token, boolean didLaunch) {
+            PackageManagerService.this.finishPackageInstall(token, didLaunch);
+        }
     }
 
     @GuardedBy("mPackages")
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 7bab0bb..c4d27e5 100644
--- a/services/core/java/com/android/server/pm/StagingManager.java
+++ b/services/core/java/com/android/server/pm/StagingManager.java
@@ -41,7 +41,9 @@
 import com.android.internal.os.BackgroundThread;
 
 import java.util.ArrayList;
+import java.util.Arrays;
 import java.util.List;
+import java.util.stream.Collectors;
 
 /**
  * This class handles staged install sessions, i.e. install sessions that require packages to
@@ -126,12 +128,24 @@
         return false;
     }
 
-    private static boolean submitSessionToApexService(int sessionId, ApexInfoList apexInfoList) {
+    private static boolean submitSessionToApexService(@NonNull PackageInstallerSession session,
+                                                      List<PackageInstallerSession> childSessions,
+                                                      ApexInfoList apexInfoList) {
+        return sendSubmitStagedSessionRequest(
+                session.sessionId,
+                childSessions != null
+                        ? childSessions.stream().mapToInt(s -> s.sessionId).toArray() :
+                        new int[]{},
+                apexInfoList);
+    }
+
+    private static boolean sendSubmitStagedSessionRequest(
+            int sessionId, int[] childSessionIds, ApexInfoList apexInfoList) {
         final IApexService apex = IApexService.Stub.asInterface(
                 ServiceManager.getService("apexservice"));
         boolean success;
         try {
-            success = apex.submitStagedSession(sessionId, new int[0], apexInfoList);
+            success = apex.submitStagedSession(sessionId, childSessionIds, apexInfoList);
         } catch (RemoteException re) {
             Slog.e(TAG, "Unable to contact apexservice", re);
             return false;
@@ -139,35 +153,79 @@
         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;
+    }
+
     private void preRebootVerification(@NonNull PackageInstallerSession session) {
         boolean success = true;
-        if ((session.params.installFlags & PackageManager.INSTALL_APEX) != 0) {
 
-            final ApexInfoList apexInfoList = new ApexInfoList();
+        final ApexInfoList apexInfoList = new ApexInfoList();
+        // APEX checks. For single-package sessions, check if they contain an APEX. For
+        // multi-package sessions, find all the child sessions that contain an APEX.
+        if (!session.isMultiPackage()
+                && isApexSession(session)) {
+            success = submitSessionToApexService(session, null, apexInfoList);
 
-            if (!submitSessionToApexService(session.sessionId, apexInfoList)) {
-                success = false;
-            } else {
-                // 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
-                // it applies the staged install.
-                //
-                // TODO: Decide whether we want to fail fast by detecting signature mismatches right
-                // away.
-                for (ApexInfo apexPackage : apexInfoList.apexInfos) {
-                    if (!validateApexSignatureLocked(apexPackage.packagePath,
-                            apexPackage.packageName)) {
-                        success = false;
-                        break;
-                    }
+        } else if (session.isMultiPackage()) {
+            List<PackageInstallerSession> childSessions =
+                    Arrays.stream(session.getChildSessionIds())
+                            // Retrieve cached sessions matching ids.
+                            .mapToObj(i -> mStagedSessions.get(i))
+                            // Filter only the ones containing APEX.
+                            .filter(childSession -> isApexSession(childSession))
+                            .collect(Collectors.toList());
+            if (!childSessions.isEmpty()) {
+                success = submitSessionToApexService(session, childSessions, apexInfoList);
+            } // else this is a staged multi-package session with no APEX files.
+        }
+
+        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
+            // it applies the staged install.
+            //
+            // TODO: Decide whether we want to fail fast by detecting signature mismatches for APKs,
+            // right away.
+            for (ApexInfo apexPackage : apexInfoList.apexInfos) {
+                if (!validateApexSignatureLocked(apexPackage.packagePath,
+                        apexPackage.packageName)) {
+                    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.");
         }
     }
 
@@ -185,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.
     }
 
@@ -206,15 +271,59 @@
         }
     }
 
-    void abortSession(@NonNull PackageInstallerSession sessionInfo) {
-        updateStoredSession(sessionInfo);
+    void abortSession(@NonNull PackageInstallerSession session) {
         synchronized (mStagedSessions) {
-            mStagedSessions.remove(sessionInfo.sessionId);
+            updateStoredSession(session);
+            mStagedSessions.remove(session.sessionId);
         }
     }
 
+    @GuardedBy("mStagedSessions")
+    private boolean isMultiPackageSessionComplete(@NonNull PackageInstallerSession session) {
+        // This method assumes that the argument is either a parent session of a multi-package
+        // i.e. isMultiPackage() returns true, or that it is a child session, i.e.
+        // hasParentSessionId() returns true.
+        if (session.isMultiPackage()) {
+            // Parent session of a multi-package group. Check that we restored all the children.
+            for (int childSession : session.getChildSessionIds()) {
+                if (mStagedSessions.get(childSession) == null) {
+                    return false;
+                }
+            }
+            return true;
+        }
+        if (session.hasParentSessionId()) {
+            PackageInstallerSession parent = mStagedSessions.get(session.getParentSessionId());
+            if (parent == null) {
+                return false;
+            }
+            return isMultiPackageSessionComplete(parent);
+        }
+        Slog.wtf(TAG, "Attempting to restore an invalid multi-package session.");
+        return false;
+    }
+
     void restoreSession(@NonNull PackageInstallerSession session) {
-        updateStoredSession(session);
+        PackageInstallerSession sessionToResume = session;
+        synchronized (mStagedSessions) {
+            mStagedSessions.append(session.sessionId, session);
+            // For multi-package sessions, we don't know in which order they will be restored. We
+            // need to wait until we have restored all the session in a group before restoring them.
+            if (session.isMultiPackage() || session.hasParentSessionId()) {
+                if (!isMultiPackageSessionComplete(session)) {
+                    // Still haven't recovered all sessions of the group, return.
+                    return;
+                }
+                // Group recovered, find the parent if necessary and resume the installation.
+                if (session.hasParentSessionId()) {
+                    sessionToResume = mStagedSessions.get(session.getParentSessionId());
+                }
+            }
+        }
+        checkStateAndResume(sessionToResume);
+    }
+
+    private void checkStateAndResume(@NonNull PackageInstallerSession session) {
         // Check the state of the session and decide what to do next.
         if (session.isStagedSessionFailed() || session.isStagedSessionApplied()) {
             // Final states, nothing to do.
@@ -227,6 +336,8 @@
         } else {
             // Session had already being marked ready. Start the checks to verify if there is any
             // follow-up work.
+            // TODO(b/118865310): should this be synchronous to ensure it completes before
+            //                    systemReady() finishes?
             mBgHandler.post(() -> resumeSession(session));
         }
     }
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/DexLogger.java b/services/core/java/com/android/server/pm/dex/DexLogger.java
index 78fa82c..59cc0cf 100644
--- a/services/core/java/com/android/server/pm/dex/DexLogger.java
+++ b/services/core/java/com/android/server/pm/dex/DexLogger.java
@@ -16,11 +16,15 @@
 
 package com.android.server.pm.dex;
 
+import static com.android.server.pm.dex.PackageDynamicCodeLoading.FILE_TYPE_DEX;
+import static com.android.server.pm.dex.PackageDynamicCodeLoading.FILE_TYPE_NATIVE;
+
 import android.content.pm.ApplicationInfo;
 import android.content.pm.IPackageManager;
 import android.content.pm.PackageInfo;
 import android.os.FileUtils;
 import android.os.RemoteException;
+import android.os.UserHandle;
 import android.os.storage.StorageManager;
 import android.util.ByteStringUtils;
 import android.util.EventLog;
@@ -35,20 +39,23 @@
 import com.android.server.pm.dex.PackageDynamicCodeLoading.PackageDynamicCode;
 
 import java.io.File;
+import java.io.IOException;
 import java.util.Map;
 import java.util.Set;
 
 /**
- * This class is responsible for logging data about secondary dex files.
- * The data logged includes hashes of the name and content of each file.
+ * This class is responsible for logging data about secondary dex files and, despite the name,
+ * native code executed from an app's private directory. The data logged includes hashes of the
+ * name and content of each file.
  */
 public class DexLogger {
     private static final String TAG = "DexLogger";
 
-    // Event log tag & subtag used for SafetyNet logging of dynamic
-    // code loading (DCL) - see b/63927552.
+    // Event log tag & subtags used for SafetyNet logging of dynamic code loading (DCL) -
+    // see b/63927552.
     private static final int SNET_TAG = 0x534e4554;
-    private static final String DCL_SUBTAG = "dcl";
+    private static final String DCL_DEX_SUBTAG = "dcl";
+    private static final String DCL_NATIVE_SUBTAG = "dcln";
 
     private final IPackageManager mPackageManager;
     private final PackageDynamicCodeLoading mPackageDynamicCodeLoading;
@@ -114,12 +121,11 @@
             }
 
             int storageFlags;
-            if (appInfo.deviceProtectedDataDir != null
-                    && FileUtils.contains(appInfo.deviceProtectedDataDir, filePath)) {
-                storageFlags = StorageManager.FLAG_STORAGE_DE;
-            } else if (appInfo.credentialProtectedDataDir != null
-                    && FileUtils.contains(appInfo.credentialProtectedDataDir, filePath)) {
+
+            if (fileIsUnder(filePath, appInfo.credentialProtectedDataDir)) {
                 storageFlags = StorageManager.FLAG_STORAGE_CE;
+            } else if (fileIsUnder(filePath, appInfo.deviceProtectedDataDir)) {
+                storageFlags = StorageManager.FLAG_STORAGE_DE;
             } else {
                 Slog.e(TAG, "Could not infer CE/DE storage for path " + filePath);
                 needWrite |= mPackageDynamicCodeLoading.removeFile(packageName, filePath, userId);
@@ -139,6 +145,9 @@
                         + ": " + e.getMessage());
             }
 
+            String subtag = fileInfo.mFileType == FILE_TYPE_DEX
+                    ? DCL_DEX_SUBTAG
+                    : DCL_NATIVE_SUBTAG;
             String fileName = new File(filePath).getName();
             String message = PackageUtils.computeSha256Digest(fileName.getBytes());
 
@@ -165,7 +174,7 @@
                 }
 
                 if (loadingUid != -1) {
-                    writeDclEvent(loadingUid, message);
+                    writeDclEvent(subtag, loadingUid, message);
                 }
             }
         }
@@ -175,21 +184,58 @@
         }
     }
 
+    private boolean fileIsUnder(String filePath, String directoryPath) {
+        if (directoryPath == null) {
+            return false;
+        }
+
+        try {
+            return FileUtils.contains(new File(directoryPath).getCanonicalPath(),
+                    new File(filePath).getCanonicalPath());
+        } catch (IOException e) {
+            return false;
+        }
+    }
+
     @VisibleForTesting
     PackageDynamicCode getPackageDynamicCodeInfo(String packageName) {
         return mPackageDynamicCodeLoading.getPackageDynamicCodeInfo(packageName);
     }
 
     @VisibleForTesting
-    void writeDclEvent(int uid, String message) {
-        EventLog.writeEvent(SNET_TAG, DCL_SUBTAG, uid, message);
+    void writeDclEvent(String subtag, int uid, String message) {
+        EventLog.writeEvent(SNET_TAG, subtag, uid, message);
     }
 
-    void record(int loaderUserId, String dexPath,
-            String owningPackageName, String loadingPackageName) {
+    void recordDex(int loaderUserId, String dexPath, String owningPackageName,
+            String loadingPackageName) {
         if (mPackageDynamicCodeLoading.record(owningPackageName, dexPath,
-                PackageDynamicCodeLoading.FILE_TYPE_DEX, loaderUserId,
-                loadingPackageName)) {
+                FILE_TYPE_DEX, loaderUserId, loadingPackageName)) {
+            mPackageDynamicCodeLoading.maybeWriteAsync();
+        }
+    }
+
+    /**
+     * Record that an app running in the specified uid has executed native code from the file at
+     * {@link path}.
+     */
+    public void recordNative(int loadingUid, String path) {
+        String[] packages;
+        try {
+            packages = mPackageManager.getPackagesForUid(loadingUid);
+            if (packages == null || packages.length == 0) {
+                return;
+            }
+        } catch (RemoteException e) {
+            // Can't happen, we're local.
+            return;
+        }
+
+        String loadingPackageName = packages[0];
+        int loadingUserId = UserHandle.getUserId(loadingUid);
+
+        if (mPackageDynamicCodeLoading.record(loadingPackageName, path,
+                FILE_TYPE_NATIVE, loadingUserId, loadingPackageName)) {
             mPackageDynamicCodeLoading.maybeWriteAsync();
         }
     }
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 b546836..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
@@ -235,7 +228,7 @@
                     continue;
                 }
 
-                mDexLogger.record(loaderUserId, dexPath, searchResult.mOwningPackageName,
+                mDexLogger.recordDex(loaderUserId, dexPath, searchResult.mOwningPackageName,
                         loadingAppInfo.packageName);
 
                 if (classLoaderContexts != null) {
@@ -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/dex/OWNERS b/services/core/java/com/android/server/pm/dex/OWNERS
new file mode 100644
index 0000000..fcc1f6c
--- /dev/null
+++ b/services/core/java/com/android/server/pm/dex/OWNERS
@@ -0,0 +1,4 @@
+agampe@google.com
+calin@google.com
+ngeoffray@google.com
+sehr@google.com
diff --git a/services/core/java/com/android/server/pm/dex/PackageDynamicCodeLoading.java b/services/core/java/com/android/server/pm/dex/PackageDynamicCodeLoading.java
index 6d4bc82..cc26c9b 100644
--- a/services/core/java/com/android/server/pm/dex/PackageDynamicCodeLoading.java
+++ b/services/core/java/com/android/server/pm/dex/PackageDynamicCodeLoading.java
@@ -53,6 +53,9 @@
     // is represented in the text file format.)
     static final int FILE_TYPE_DEX = 'D';
 
+    // Type code to indicate a secondary file containing native code.
+    static final int FILE_TYPE_NATIVE = 'N';
+
     private static final String TAG = "PackageDynamicCodeLoading";
 
     private static final String FILE_VERSION_HEADER = "DCL1";
@@ -107,7 +110,7 @@
      */
     boolean record(String owningPackageName, String filePath, int fileType, int ownerUserId,
             String loadingPackageName) {
-        if (fileType != FILE_TYPE_DEX) {
+        if (!isValidFileType(fileType)) {
             throw new IllegalArgumentException("Bad file type: " + fileType);
         }
         synchronized (mLock) {
@@ -120,6 +123,10 @@
         }
     }
 
+    private static boolean isValidFileType(int fileType) {
+        return fileType == FILE_TYPE_DEX || fileType == FILE_TYPE_NATIVE;
+    }
+
     /**
      * Return all packages that contain records of secondary dex files. (Note that data updates
      * asynchronously, so {@link #getPackageDynamicCodeInfo} may still return null if passed
@@ -407,7 +414,7 @@
             if (packages.length == 0) {
                 throw new IOException("Malformed line: " + line);
             }
-            if (type != FILE_TYPE_DEX) {
+            if (!isValidFileType(type)) {
                 throw new IOException("Unknown file type: " + line);
             }
 
diff --git a/services/core/java/com/android/server/pm/permission/DefaultPermissionGrantPolicy.java b/services/core/java/com/android/server/pm/permission/DefaultPermissionGrantPolicy.java
index d5af313..20d6d4e 100644
--- a/services/core/java/com/android/server/pm/permission/DefaultPermissionGrantPolicy.java
+++ b/services/core/java/com/android/server/pm/permission/DefaultPermissionGrantPolicy.java
@@ -1290,6 +1290,7 @@
             return mContext.getPackageManager().getPackageInfo(pkg,
                     DEFAULT_PACKAGE_INFO_QUERY_FLAGS | extraFlags);
         } catch (NameNotFoundException e) {
+            Slog.e(TAG, "PackageNot found: " + pkg, e);
             return null;
         }
     }
diff --git a/services/core/java/com/android/server/pm/permission/OWNERS b/services/core/java/com/android/server/pm/permission/OWNERS
index 88b97ea..01dc01e 100644
--- a/services/core/java/com/android/server/pm/permission/OWNERS
+++ b/services/core/java/com/android/server/pm/permission/OWNERS
@@ -1,4 +1,4 @@
-per-file DefaultPermissionGrantPolicy.java = bpoiesz@google.com
+moltmann@google.com
 per-file DefaultPermissionGrantPolicy.java = hackbod@android.com
 per-file DefaultPermissionGrantPolicy.java = jsharkey@android.com
 per-file DefaultPermissionGrantPolicy.java = svetoslavganov@google.com
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/RoleManagerService.java b/services/core/java/com/android/server/role/RoleManagerService.java
index 5516b23..1c7596b 100644
--- a/services/core/java/com/android/server/role/RoleManagerService.java
+++ b/services/core/java/com/android/server/role/RoleManagerService.java
@@ -198,6 +198,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
@@ -342,10 +343,21 @@
     @WorkerThread
     private void notifyRoleHoldersChanged(@NonNull String roleName, @UserIdInt int userId) {
         RemoteCallbackList<IOnRoleHoldersChangedListener> listeners = getListeners(userId);
-        if (listeners == null) {
-            return;
+        if (listeners != null) {
+            notifyRoleHoldersChangedForListeners(listeners, roleName, userId);
         }
 
+        RemoteCallbackList<IOnRoleHoldersChangedListener> allUserListeners = getListeners(
+                UserHandle.USER_ALL);
+        if (allUserListeners != null) {
+            notifyRoleHoldersChangedForListeners(allUserListeners, roleName, userId);
+        }
+    }
+
+    @WorkerThread
+    private void notifyRoleHoldersChangedForListeners(
+            @NonNull RemoteCallbackList<IOnRoleHoldersChangedListener> listeners,
+            @NonNull String roleName, @UserIdInt int userId) {
         int broadcastCount = listeners.beginBroadcast();
         try {
             for (int i = 0; i < broadcastCount; i++) {
@@ -395,7 +407,7 @@
                 Slog.e(LOG_TAG, "user " + userId + " does not exist");
                 return Collections.emptyList();
             }
-            userId = handleIncomingUser(userId, "getRoleHoldersAsUser");
+            userId = handleIncomingUser(userId, "getRoleHoldersAsUser", false);
             getContext().enforceCallingOrSelfPermission(Manifest.permission.MANAGE_ROLE_HOLDERS,
                     "getRoleHoldersAsUser");
 
@@ -423,7 +435,7 @@
                 Slog.e(LOG_TAG, "user " + userId + " does not exist");
                 return;
             }
-            userId = handleIncomingUser(userId, "addRoleHolderAsUser");
+            userId = handleIncomingUser(userId, "addRoleHolderAsUser", false);
             getContext().enforceCallingOrSelfPermission(Manifest.permission.MANAGE_ROLE_HOLDERS,
                     "addRoleHolderAsUser");
 
@@ -440,7 +452,7 @@
                 Slog.e(LOG_TAG, "user " + userId + " does not exist");
                 return;
             }
-            userId = handleIncomingUser(userId, "removeRoleHolderAsUser");
+            userId = handleIncomingUser(userId, "removeRoleHolderAsUser", false);
             getContext().enforceCallingOrSelfPermission(Manifest.permission.MANAGE_ROLE_HOLDERS,
                     "removeRoleHolderAsUser");
 
@@ -457,7 +469,7 @@
                 Slog.e(LOG_TAG, "user " + userId + " does not exist");
                 return;
             }
-            userId = handleIncomingUser(userId, "clearRoleHoldersAsUser");
+            userId = handleIncomingUser(userId, "clearRoleHoldersAsUser", false);
             getContext().enforceCallingOrSelfPermission(Manifest.permission.MANAGE_ROLE_HOLDERS,
                     "clearRoleHoldersAsUser");
 
@@ -468,11 +480,12 @@
         public void addOnRoleHoldersChangedListenerAsUser(
                 @NonNull IOnRoleHoldersChangedListener listener, @UserIdInt int userId) {
             Preconditions.checkNotNull(listener, "listener cannot be null");
-            if (!mUserManagerInternal.exists(userId)) {
+            if (userId != UserHandle.USER_ALL && !mUserManagerInternal.exists(userId)) {
                 Slog.e(LOG_TAG, "user " + userId + " does not exist");
                 return;
             }
-            userId = handleIncomingUser(userId, "addOnRoleHoldersChangedListenerAsUser");
+            userId = handleIncomingUser(userId, "addOnRoleHoldersChangedListenerAsUser",
+                    true);
             getContext().enforceCallingOrSelfPermission(Manifest.permission.OBSERVE_ROLE_HOLDERS,
                     "addOnRoleHoldersChangedListenerAsUser");
 
@@ -485,11 +498,12 @@
         public void removeOnRoleHoldersChangedListenerAsUser(
                 @NonNull IOnRoleHoldersChangedListener listener, @UserIdInt int userId) {
             Preconditions.checkNotNull(listener, "listener cannot be null");
-            if (!mUserManagerInternal.exists(userId)) {
+            if (userId != UserHandle.USER_ALL && !mUserManagerInternal.exists(userId)) {
                 Slog.e(LOG_TAG, "user " + userId + " does not exist");
                 return;
             }
-            userId = handleIncomingUser(userId, "removeOnRoleHoldersChangedListenerAsUser");
+            userId = handleIncomingUser(userId, "removeOnRoleHoldersChangedListenerAsUser",
+                    true);
             getContext().enforceCallingOrSelfPermission(Manifest.permission.OBSERVE_ROLE_HOLDERS,
                     "removeOnRoleHoldersChangedListenerAsUser");
 
@@ -553,9 +567,10 @@
         }
 
         @CheckResult
-        private int handleIncomingUser(@UserIdInt int userId, @NonNull String name) {
+        private int handleIncomingUser(@UserIdInt int userId, @NonNull String name,
+                boolean allowAll) {
             return ActivityManager.handleIncomingUser(getCallingPid(), getCallingUid(), userId,
-                    false, true, name, null);
+                    allowAll, true, name, null);
         }
 
         @Override
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 f0589aa..a4f3064 100644
--- a/services/core/java/com/android/server/rollback/RollbackData.java
+++ b/services/core/java/com/android/server/rollback/RollbackData.java
@@ -29,6 +29,11 @@
  */
 class RollbackData {
     /**
+     * A unique identifier for this rollback.
+     */
+    public final int rollbackId;
+
+    /**
      * The per-package rollback information.
      */
     public final List<PackageRollbackInfo> packages = new ArrayList<>();
@@ -44,7 +49,16 @@
      */
     public Instant timestamp;
 
-    RollbackData(File backupDir) {
+    /**
+     * 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 48ddf8c..8b4c410 100644
--- a/services/core/java/com/android/server/rollback/RollbackManagerServiceImpl.java
+++ b/services/core/java/com/android/server/rollback/RollbackManagerServiceImpl.java
@@ -16,12 +16,9 @@
 
 package com.android.server.rollback;
 
-import android.Manifest;
 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;
@@ -32,25 +29,29 @@
 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.net.Uri;
 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;
 import android.util.Log;
+import android.util.SparseBooleanArray;
 
 import com.android.internal.annotations.GuardedBy;
 import com.android.server.LocalServices;
+import com.android.server.pm.Installer;
+import com.android.server.pm.Installer.InstallerException;
 import com.android.server.pm.PackageManagerServiceUtils;
 
 import java.io.File;
 import java.io.IOException;
+import java.security.SecureRandom;
 import java.time.Instant;
 import java.time.temporal.ChronoUnit;
 import java.util.ArrayList;
@@ -59,9 +60,8 @@
 import java.util.Iterator;
 import java.util.List;
 import java.util.Map;
+import java.util.Random;
 import java.util.Set;
-import java.util.concurrent.LinkedBlockingQueue;
-import java.util.concurrent.TimeUnit;
 
 /**
  * Implementation of service that manages APK level rollbacks.
@@ -79,6 +79,13 @@
     // mLock is held when they are called.
     private final Object mLock = new Object();
 
+    // Used for generating rollback IDs.
+    private final Random mRandom = new SecureRandom();
+
+    // Set of allocated rollback ids
+    @GuardedBy("mLock")
+    private final SparseBooleanArray mAllocatedRollbackIds = new SparseBooleanArray();
+
     // Package rollback data for rollback-enabled installs that have not yet
     // been committed. Maps from sessionId to rollback data.
     @GuardedBy("mLock")
@@ -103,14 +110,22 @@
 
     private final Context mContext;
     private final HandlerThread mHandlerThread;
+    private final Installer mInstaller;
+    private final RollbackPackageHealthObserver mPackageHealthObserver;
 
     RollbackManagerServiceImpl(Context context) {
         mContext = context;
+        // Note that we're calling onStart here because this object is only constructed on
+        // SystemService#onStart.
+        mInstaller = new Installer(mContext);
+        mInstaller.onStart();
         mHandlerThread = new HandlerThread("RollbackManagerServiceHandler");
         mHandlerThread.start();
 
         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
@@ -120,8 +135,8 @@
         // expiration.
         getHandler().post(() -> ensureRollbackDataLoaded());
 
-        PackageInstaller installer = mContext.getPackageManager().getPackageInstaller();
-        installer.registerSessionCallback(new SessionCallback(), getHandler());
+        PackageInstaller packageInstaller = mContext.getPackageManager().getPackageInstaller();
+        packageInstaller.registerSessionCallback(new SessionCallback(), getHandler());
 
         IntentFilter filter = new IntentFilter();
         filter.addAction(Intent.ACTION_PACKAGE_REPLACED);
@@ -158,10 +173,13 @@
                             PackageManagerInternal.EXTRA_ENABLE_ROLLBACK_TOKEN, -1);
                     int installFlags = intent.getIntExtra(
                             PackageManagerInternal.EXTRA_ENABLE_ROLLBACK_INSTALL_FLAGS, 0);
+                    int[] installedUsers = intent.getIntArrayExtra(
+                            PackageManagerInternal.EXTRA_ENABLE_ROLLBACK_INSTALLED_USERS);
                     File newPackageCodePath = new File(intent.getData().getPath());
 
                     getHandler().post(() -> {
-                        boolean success = enableRollback(installFlags, newPackageCodePath);
+                        boolean success = enableRollback(installFlags, newPackageCodePath,
+                                installedUsers);
                         int ret = PackageManagerInternal.ENABLE_ROLLBACK_SUCCEEDED;
                         if (!success) {
                             ret = PackageManagerInternal.ENABLE_ROLLBACK_FAILED;
@@ -198,10 +216,10 @@
         // it's out of date or not, so no need to check package versions here.
 
         for (PackageRollbackInfo info : data.packages) {
-            if (info.packageName.equals(packageName)) {
+            if (info.getPackageName().equals(packageName)) {
                 // TODO: Once the RollbackInfo API supports info about
                 // dependant packages, add that info here.
-                return new RollbackInfo(info);
+                return new RollbackInfo(data.rollbackId, info);
             }
         }
         return null;
@@ -219,7 +237,7 @@
             for (int i = 0; i < mAvailableRollbacks.size(); ++i) {
                 RollbackData data = mAvailableRollbacks.get(i);
                 for (PackageRollbackInfo info : data.packages) {
-                    packageNames.add(info.packageName);
+                    packageNames.add(info.getPackageName());
                 }
             }
         }
@@ -261,25 +279,24 @@
      */
     private void executeRollbackInternal(RollbackInfo rollback,
             String callerPackageName, IntentSender statusReceiver) {
-        String targetPackageName = rollback.targetPackage.packageName;
+        String targetPackageName = rollback.targetPackage.getPackageName();
         Log.i(TAG, "Initiating rollback of " + targetPackageName);
 
         // Get the latest RollbackData for the target package.
-        RollbackData data = getRollbackForPackage(targetPackageName);
+        final RollbackData data = getRollbackForPackage(targetPackageName);
         if (data == null) {
             sendFailure(statusReceiver, "No rollback available for package.");
             return;
         }
 
-        // Verify the latest rollback matches the version requested.
-        // TODO: Check dependant packages too once RollbackInfo includes that
-        // information.
-        for (PackageRollbackInfo info : data.packages) {
-            if (info.packageName.equals(targetPackageName)
-                    && !rollback.targetPackage.higherVersion.equals(info.higherVersion)) {
-                sendFailure(statusReceiver, "Rollback is out of date.");
-                return;
-            }
+        if (data.rollbackId != rollback.getRollbackId()) {
+            sendFailure(statusReceiver, "Rollback for package is out of date.");
+            return;
+        }
+
+        if (data.inProgress) {
+            sendFailure(statusReceiver, "Rollback for package is already in progress.");
+            return;
         }
 
         // Verify the RollbackData is up to date with what's installed on
@@ -291,15 +308,14 @@
         // Figure out how to ensure we don't commit the rollback if
         // roll forward happens at the same time.
         for (PackageRollbackInfo info : data.packages) {
-            PackageRollbackInfo.PackageVersion installedVersion =
-                    getInstalledPackageVersion(info.packageName);
+            VersionedPackage installedVersion = getInstalledPackageVersion(info.getPackageName());
             if (installedVersion == null) {
                 // TODO: Test this case
                 sendFailure(statusReceiver, "Package to roll back is not installed");
                 return;
             }
 
-            if (!info.higherVersion.equals(installedVersion)) {
+            if (!packageVersionsEqual(info.getVersionRolledBackFrom(), installedVersion)) {
                 // TODO: Test this case
                 sendFailure(statusReceiver, "Package version to roll back not installed.");
                 return;
@@ -319,8 +335,14 @@
         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);
@@ -329,13 +351,14 @@
             for (PackageRollbackInfo info : data.packages) {
                 PackageInstaller.SessionParams params = new PackageInstaller.SessionParams(
                         PackageInstaller.SessionParams.MODE_FULL_INSTALL);
+                params.setInstallerPackageName(installerPackageName);
                 params.setAllowDowngrade(true);
                 int sessionId = packageInstaller.createSession(params);
                 PackageInstaller.Session session = packageInstaller.openSession(sessionId);
 
                 // TODO: Will it always be called "base.apk"? What about splits?
                 // What about apex?
-                File packageDir = new File(data.backupDir, info.packageName);
+                File packageDir = new File(data.backupDir, info.getPackageName());
                 File baseApk = new File(packageDir, "base.apk");
                 try (ParcelFileDescriptor fd = ParcelFileDescriptor.open(baseApk,
                         ParcelFileDescriptor.MODE_READ_ONLY)) {
@@ -349,28 +372,39 @@
                 parentSession.addChildSessionId(sessionId);
             }
 
-            final LocalIntentReceiver receiver = new LocalIntentReceiver();
+            final LocalIntentReceiver receiver = new LocalIntentReceiver(
+                    (Intent result) -> {
+                        getHandler().post(() -> {
+                            // We've now completed the rollback, so we mark it as no longer in
+                            // progress.
+                            data.inProgress = false;
+
+                            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;
+                            }
+
+                            addRecentlyExecutedRollback(rollback);
+                            sendSuccess(statusReceiver);
+
+                            Intent broadcast = new Intent(Intent.ACTION_PACKAGE_ROLLBACK_EXECUTED);
+
+                            // 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());
-
-            Intent result = receiver.getResult();
-            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;
-            }
-
-            addRecentlyExecutedRollback(rollback);
-            sendSuccess(statusReceiver);
-
-            Intent broadcast = new Intent(Intent.ACTION_PACKAGE_ROLLBACK_EXECUTED,
-                    Uri.fromParts("package", targetPackageName,
-                        Manifest.permission.MANAGE_ROLLBACKS));
-
-            // TODO: This call emits the warning "Calling a method in the
-            // system process without a qualified user". Fix that.
-            mContext.sendBroadcast(broadcast);
         } catch (IOException e) {
             Log.e(TAG, "Unable to roll back " + targetPackageName, e);
             sendFailure(statusReceiver, "IOException: " + e.toString());
@@ -407,7 +441,7 @@
             while (iter.hasNext()) {
                 RollbackData data = iter.next();
                 for (PackageRollbackInfo info : data.packages) {
-                    if (info.packageName.equals(packageName)) {
+                    if (info.getPackageName().equals(packageName)) {
                         iter.remove();
                         mRollbackStore.deleteAvailableRollback(data);
                         break;
@@ -449,7 +483,15 @@
     @GuardedBy("mLock")
     private void loadAllRollbackDataLocked() {
         mAvailableRollbacks = mRollbackStore.loadAvailableRollbacks();
+        for (RollbackData data : mAvailableRollbacks) {
+            mAllocatedRollbackIds.put(data.rollbackId, true);
+        }
+
         mRecentlyExecutedRollbacks = mRollbackStore.loadRecentlyExecutedRollbacks();
+        for (RollbackInfo info : mRecentlyExecutedRollbacks) {
+            mAllocatedRollbackIds.put(info.getRollbackId(), true);
+        }
+
         scheduleExpiration(0);
     }
 
@@ -461,8 +503,7 @@
     private void onPackageReplaced(String packageName) {
         // TODO: Could this end up incorrectly deleting a rollback for a
         // package that is about to be installed?
-        PackageRollbackInfo.PackageVersion installedVersion =
-                getInstalledPackageVersion(packageName);
+        VersionedPackage installedVersion = getInstalledPackageVersion(packageName);
 
         synchronized (mLock) {
             ensureRollbackDataLoadedLocked();
@@ -470,8 +511,10 @@
             while (iter.hasNext()) {
                 RollbackData data = iter.next();
                 for (PackageRollbackInfo info : data.packages) {
-                    if (info.packageName.equals(packageName)
-                            && !info.higherVersion.equals(installedVersion)) {
+                    if (info.getPackageName().equals(packageName)
+                            && !packageVersionsEqual(
+                                        info.getVersionRolledBackFrom(),
+                                        installedVersion)) {
                         iter.remove();
                         mRollbackStore.deleteAvailableRollback(data);
                         break;
@@ -494,7 +537,7 @@
             boolean changed = false;
             while (iter.hasNext()) {
                 RollbackInfo rollback = iter.next();
-                if (packageName.equals(rollback.targetPackage.packageName)) {
+                if (packageName.equals(rollback.targetPackage.getPackageName())) {
                     iter.remove();
                     changed = true;
                 }
@@ -613,12 +656,13 @@
      * staged for install with rollback enabled. Called before the package has
      * been installed.
      *
-     * @param id the id of the enable rollback request
      * @param installFlags information about what is being installed.
      * @param newPackageCodePath path to the package about to be installed.
+     * @param installedUsers the set of users for which a given package is installed.
      * @return true if enabling the rollback succeeds, false otherwise.
      */
-    private boolean enableRollback(int installFlags, File newPackageCodePath) {
+    private boolean enableRollback(int installFlags, File newPackageCodePath,
+            int[] installedUsers) {
         if ((installFlags & PackageManager.INSTALL_INSTANT_APP) != 0) {
             Log.e(TAG, "Rollbacks not supported for instant app install");
             return false;
@@ -668,8 +712,7 @@
             return false;
         }
 
-        PackageRollbackInfo.PackageVersion newVersion =
-                new PackageRollbackInfo.PackageVersion(newPackage.versionCode);
+        VersionedPackage newVersion = new VersionedPackage(packageName, newPackage.versionCode);
 
         // Get information about the currently installed package.
         PackageManagerInternal pm = LocalServices.getService(PackageManagerInternal.class);
@@ -680,11 +723,29 @@
             Log.e(TAG, packageName + " is not installed");
             return false;
         }
-        PackageRollbackInfo.PackageVersion installedVersion =
-                new PackageRollbackInfo.PackageVersion(installedPackage.getLongVersionCode());
+        VersionedPackage installedVersion = new VersionedPackage(packageName,
+                installedPackage.getLongVersionCode());
 
-        PackageRollbackInfo info = new PackageRollbackInfo(
-                packageName, newVersion, installedVersion);
+        for (int user : installedUsers) {
+            final int storageFlags;
+            if (StorageManager.isFileEncryptedNativeOrEmulated()
+                    && !StorageManager.isUserKeyUnlocked(user)) {
+                // We've encountered a user that hasn't unlocked on a FBE device, so we can't copy
+                // across app user data until the user unlocks their device.
+                Log.e(TAG, "User: " + user + " isn't unlocked, skipping CE userdata backup.");
+                storageFlags = Installer.FLAG_STORAGE_DE;
+            } else {
+                storageFlags = Installer.FLAG_STORAGE_CE | Installer.FLAG_STORAGE_DE;
+            }
+
+            try {
+                mInstaller.snapshotAppData(packageName, user, storageFlags);
+            } catch (InstallerException ie) {
+                Log.e(TAG, "Unable to create app data snapshot for: " + packageName, ie);
+            }
+        }
+
+        PackageRollbackInfo info = new PackageRollbackInfo(newVersion, installedVersion);
 
         RollbackData data;
         try {
@@ -692,7 +753,8 @@
                 mChildSessions.put(childSessionId, parentSessionId);
                 data = mPendingRollbacks.get(parentSessionId);
                 if (data == null) {
-                    data = mRollbackStore.createAvailableRollback();
+                    int rollbackId = allocateRollbackIdLocked();
+                    data = mRollbackStore.createAvailableRollback(rollbackId);
                     mPendingRollbacks.put(parentSessionId, data);
                 }
                 data.packages.add(info);
@@ -715,40 +777,56 @@
         return true;
     }
 
-    // TODO: Don't copy this from PackageManagerShellCommand like this?
-    private static class LocalIntentReceiver {
-        private final LinkedBlockingQueue<Intent> mResult = new LinkedBlockingQueue<>();
-
-        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) {
-                try {
-                    mResult.offer(intent, 5, TimeUnit.SECONDS);
-                } catch (InterruptedException e) {
-                    throw new RuntimeException(e);
-                }
-            }
-        };
-
-        public IntentSender getIntentSender() {
-            return new IntentSender((IIntentSender) mLocalSender);
+    @Override
+    public void restoreUserData(String packageName, int userId, int appId, long ceDataInode,
+            String seInfo, int token) {
+        if (Binder.getCallingUid() != Process.SYSTEM_UID) {
+            throw new SecurityException("restoureUserData may only be called by the system.");
         }
 
-        public Intent getResult() {
+        getHandler().post(() -> {
+            PackageManagerInternal pmi = LocalServices.getService(PackageManagerInternal.class);
+            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;
+            }
+
+            final int storageFlags;
+            if (StorageManager.isFileEncryptedNativeOrEmulated()
+                    && !StorageManager.isUserKeyUnlocked(userId)) {
+                // We've encountered a user that hasn't unlocked on a FBE device, so we can't copy
+                // across app user data until the user unlocks their device.
+                Log.e(TAG, "User: " + userId + " isn't unlocked, skipping CE userdata restore.");
+
+                storageFlags = Installer.FLAG_STORAGE_DE;
+            } else {
+                storageFlags = Installer.FLAG_STORAGE_CE | Installer.FLAG_STORAGE_DE;
+            }
+
             try {
-                return mResult.take();
-            } catch (InterruptedException e) {
-                throw new RuntimeException(e);
+                mInstaller.restoreAppDataSnapshot(packageName, appId, ceDataInode,
+                        seInfo, userId, storageFlags);
+            } catch (InstallerException ie) {
+                Log.e(TAG, "Unable to restore app data snapshot: " + packageName, ie);
             }
-        }
+
+            pmi.finishPackageInstall(token, false);
+        });
     }
 
     /**
      * Gets the version of the package currently installed.
      * Returns null if the package is not currently installed.
      */
-    private PackageRollbackInfo.PackageVersion getInstalledPackageVersion(String packageName) {
+    private VersionedPackage getInstalledPackageVersion(String packageName) {
         PackageManager pm = mContext.getPackageManager();
         PackageInfo pkgInfo = null;
         try {
@@ -757,7 +835,12 @@
             return null;
         }
 
-        return new PackageRollbackInfo.PackageVersion(pkgInfo.getLongVersionCode());
+        return new VersionedPackage(packageName, pkgInfo.getLongVersionCode());
+    }
+
+    private boolean packageVersionsEqual(VersionedPackage a, VersionedPackage b) {
+        return a.getPackageName().equals(b.getPackageName())
+            && a.getLongVersionCode() == b.getLongVersionCode();
     }
 
     private class SessionCallback extends PackageInstaller.SessionCallback {
@@ -805,7 +888,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);
@@ -833,7 +926,7 @@
             for (int i = 0; i < mAvailableRollbacks.size(); ++i) {
                 RollbackData data = mAvailableRollbacks.get(i);
                 for (PackageRollbackInfo info : data.packages) {
-                    if (info.packageName.equals(packageName)) {
+                    if (info.getPackageName().equals(packageName)) {
                         return data;
                     }
                 }
@@ -841,4 +934,19 @@
         }
         return null;
     }
+
+    @GuardedBy("mLock")
+    private int allocateRollbackIdLocked() throws IOException {
+        int n = 0;
+        int rollbackId;
+        do {
+            rollbackId = mRandom.nextInt(Integer.MAX_VALUE - 1) + 1;
+            if (!mAllocatedRollbackIds.get(rollbackId, false)) {
+                mAllocatedRollbackIds.put(rollbackId, true);
+                return rollbackId;
+            }
+        } while (n++ < 32);
+
+        throw new IOException("Failed to allocate rollback ID");
+    }
 }
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..1f2f1cc
--- /dev/null
+++ b/services/core/java/com/android/server/rollback/RollbackPackageHealthObserver.java
@@ -0,0 +1,94 @@
+/*
+ * 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.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 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 Handler mHandler;
+
+    RollbackPackageHealthObserver(Context context) {
+        mContext = context;
+        HandlerThread handlerThread = new HandlerThread("RollbackPackageHealthObserver");
+        handlerThread.start();
+        mHandler = handlerThread.getThreadHandler();
+        PackageWatchdog.getInstance(mContext).registerHealthObserver(this);
+    }
+
+    @Override
+    public boolean onHealthCheckFailed(String packageName) {
+        RollbackManager rollbackManager = mContext.getSystemService(RollbackManager.class);
+        RollbackInfo rollback = rollbackManager.getAvailableRollback(packageName);
+        if (rollback != null) {
+            // TODO(zezeozue): Only rollback if rollback version == failed package version
+            mHandler.post(() -> executeRollback(rollbackManager, rollback));
+            return true;
+        }
+        // Don't handle the notification, no rollbacks available
+        return false;
+    }
+
+    /**
+     * 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 void executeRollback(RollbackManager manager, RollbackInfo rollback) {
+        // TODO(zezeozue): Log initiated metrics
+        LocalIntentReceiver rollbackReceiver = new LocalIntentReceiver((Intent result) -> {
+            mHandler.post(() -> {
+                int status = result.getIntExtra(PackageInstaller.EXTRA_STATUS,
+                        PackageInstaller.STATUS_FAILURE);
+                if (status == PackageInstaller.STATUS_SUCCESS) {
+                    // TODO(zezeozue); Log success metrics
+                    // Rolledback successfully, no action required by other observers
+                } else {
+                    // TODO(zezeozue); Log failure metrics
+                    // Rollback failed other observers should have a shot
+                }
+            });
+        });
+        manager.executeRollback(rollback, rollbackReceiver.getIntentSender());
+    }
+
+    @Override
+    public String getName() {
+        return NAME;
+    }
+}
diff --git a/services/core/java/com/android/server/rollback/RollbackStore.java b/services/core/java/com/android/server/rollback/RollbackStore.java
index f9a838f..7738be9 100644
--- a/services/core/java/com/android/server/rollback/RollbackStore.java
+++ b/services/core/java/com/android/server/rollback/RollbackStore.java
@@ -16,6 +16,7 @@
 
 package com.android.server.rollback;
 
+import android.content.pm.VersionedPackage;
 import android.content.rollback.PackageRollbackInfo;
 import android.content.rollback.RollbackInfo;
 import android.util.Log;
@@ -29,7 +30,6 @@
 import java.io.File;
 import java.io.IOException;
 import java.io.PrintWriter;
-import java.nio.file.Files;
 import java.time.Instant;
 import java.time.format.DateTimeParseException;
 import java.util.ArrayList;
@@ -58,7 +58,7 @@
     //                  base.apk
     //      recently_executed.json
     //
-    // * XXX, YYY are random strings from Files.createTempDirectory
+    // * XXX, YYY are the rollbackIds for the corresponding rollbacks.
     // * rollback.json contains all relevant metadata for the rollback. This
     //   file is not written until the rollback is made available.
     //
@@ -113,13 +113,14 @@
                 JSONArray array = object.getJSONArray("recentlyExecuted");
                 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(packageName,
-                            new PackageRollbackInfo.PackageVersion(higherVersionCode),
-                            new PackageRollbackInfo.PackageVersion(lowerVersionCode));
-                    RollbackInfo rollback = new RollbackInfo(target);
+                    PackageRollbackInfo target = new PackageRollbackInfo(
+                            new VersionedPackage(packageName, higherVersionCode),
+                            new VersionedPackage(packageName, lowerVersionCode));
+                    RollbackInfo rollback = new RollbackInfo(rollbackId, target);
                     recentlyExecutedRollbacks.add(rollback);
                 }
             } catch (IOException | JSONException e) {
@@ -135,9 +136,9 @@
     /**
      * Creates a new RollbackData instance with backupDir assigned.
      */
-    RollbackData createAvailableRollback() throws IOException {
-        File backupDir = Files.createTempDirectory(mAvailableRollbacksDir.toPath(), null).toFile();
-        return new RollbackData(backupDir);
+    RollbackData createAvailableRollback(int rollbackId) throws IOException {
+        File backupDir = new File(mAvailableRollbacksDir, Integer.toString(rollbackId));
+        return new RollbackData(rollbackId, backupDir);
     }
 
     /**
@@ -157,11 +158,14 @@
             JSONArray packagesJson = new JSONArray();
             for (PackageRollbackInfo info : data.packages) {
                 JSONObject infoJson = new JSONObject();
-                infoJson.put("packageName", info.packageName);
-                infoJson.put("higherVersionCode", info.higherVersion.versionCode);
-                infoJson.put("lowerVersionCode", info.lowerVersion.versionCode);
+                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("timestamp", data.timestamp.toString());
 
@@ -178,6 +182,8 @@
      * rollback.
      */
     void deleteAvailableRollback(RollbackData data) {
+        // TODO(narayan): Make sure we delete the userdata snapshot along with the backup of the
+        // actual app.
         removeFile(data.backupDir);
     }
 
@@ -193,9 +199,12 @@
             for (int i = 0; i < recentlyExecutedRollbacks.size(); ++i) {
                 RollbackInfo rollback = recentlyExecutedRollbacks.get(i);
                 JSONObject element = new JSONObject();
-                element.put("packageName", rollback.targetPackage.packageName);
-                element.put("higherVersionCode", rollback.targetPackage.higherVersion.versionCode);
-                element.put("lowerVersionCode", rollback.targetPackage.lowerVersion.versionCode);
+                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());
                 array.put(element);
             }
 
@@ -214,19 +223,22 @@
      */
     private RollbackData loadRollbackData(File backupDir) throws IOException {
         try {
-            RollbackData data = new RollbackData(backupDir);
             File rollbackJsonFile = new File(backupDir, "rollback.json");
             JSONObject dataJson = new JSONObject(
                     IoUtils.readFileAsString(rollbackJsonFile.getAbsolutePath()));
+
+            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(packageName,
-                        new PackageRollbackInfo.PackageVersion(higherVersionCode),
-                        new PackageRollbackInfo.PackageVersion(lowerVersionCode)));
+                data.packages.add(new PackageRollbackInfo(
+                        new VersionedPackage(packageName, higherVersionCode),
+                        new VersionedPackage(packageName, lowerVersionCode)));
             }
 
             data.timestamp = Instant.parse(dataJson.getString("timestamp"));
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 4e71a05..9135d1d 100644
--- a/services/core/java/com/android/server/stats/StatsCompanionService.java
+++ b/services/core/java/com/android/server/stats/StatsCompanionService.java
@@ -96,10 +96,10 @@
 import com.android.internal.os.BinderCallsStats.ExportedCallStat;
 import com.android.internal.os.KernelCpuSpeedReader;
 import com.android.internal.os.KernelCpuThreadReader;
-import com.android.internal.os.KernelUidCpuActiveTimeReader;
-import com.android.internal.os.KernelUidCpuClusterTimeReader;
-import com.android.internal.os.KernelUidCpuFreqTimeReader;
-import com.android.internal.os.KernelUidCpuTimeReader;
+import com.android.internal.os.KernelCpuUidTimeReader.KernelCpuUidActiveTimeReader;
+import com.android.internal.os.KernelCpuUidTimeReader.KernelCpuUidClusterTimeReader;
+import com.android.internal.os.KernelCpuUidTimeReader.KernelCpuUidFreqTimeReader;
+import com.android.internal.os.KernelCpuUidTimeReader.KernelCpuUidUserSysTimeReader;
 import com.android.internal.os.KernelWakelockReader;
 import com.android.internal.os.KernelWakelockStats;
 import com.android.internal.os.LooperStats;
@@ -231,19 +231,25 @@
     private final HashMap<Long, String> mDeletedFiles = new HashMap<>();
     private final CompanionHandler mHandler;
 
-    private KernelUidCpuTimeReader mKernelUidCpuTimeReader = new KernelUidCpuTimeReader();
+    // Disables throttler on CPU time readers.
+    private KernelCpuUidUserSysTimeReader mCpuUidUserSysTimeReader =
+            new KernelCpuUidUserSysTimeReader(false);
     private KernelCpuSpeedReader[] mKernelCpuSpeedReaders;
-    private KernelUidCpuFreqTimeReader mKernelUidCpuFreqTimeReader =
-            new KernelUidCpuFreqTimeReader();
-    private KernelUidCpuActiveTimeReader mKernelUidCpuActiveTimeReader =
-            new KernelUidCpuActiveTimeReader();
-    private KernelUidCpuClusterTimeReader mKernelUidCpuClusterTimeReader =
-            new KernelUidCpuClusterTimeReader();
+    private KernelCpuUidFreqTimeReader mCpuUidFreqTimeReader =
+            new KernelCpuUidFreqTimeReader(false);
+    private KernelCpuUidActiveTimeReader mCpuUidActiveTimeReader =
+            new KernelCpuUidActiveTimeReader(false);
+    private KernelCpuUidClusterTimeReader mCpuUidClusterTimeReader =
+            new KernelCpuUidClusterTimeReader(false);
     private StoragedUidIoStatsReader mStoragedUidIoStatsReader =
             new StoragedUidIoStatsReader();
     @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;
@@ -294,12 +300,6 @@
                     numSpeedSteps);
             firstCpuOfCluster += powerProfile.getNumCoresInCpuCluster(i);
         }
-        // use default throttling in
-        // frameworks/base/core/java/com/android/internal/os/KernelCpuProcReader
-        mKernelUidCpuFreqTimeReader.setThrottleInterval(0);
-        long[] freqs = mKernelUidCpuFreqTimeReader.readFreqs(powerProfile);
-        mKernelUidCpuClusterTimeReader.setThrottleInterval(0);
-        mKernelUidCpuActiveTimeReader.setThrottleInterval(0);
 
         // Enable push notifications of throttling from vendor thermal
         // management subsystem via thermalservice.
@@ -914,7 +914,8 @@
     private void pullKernelUidCpuTime(
             int tagId, long elapsedNanos, long wallClockNanos,
             List<StatsLogEventWrapper> pulledData) {
-        mKernelUidCpuTimeReader.readAbsolute((uid, userTimeUs, systemTimeUs) -> {
+        mCpuUidUserSysTimeReader.readAbsolute((uid, timesUs) -> {
+            long userTimeUs = timesUs[0], systemTimeUs = timesUs[1];
             StatsLogEventWrapper e = new StatsLogEventWrapper(tagId, elapsedNanos, wallClockNanos);
             e.writeInt(uid);
             e.writeLong(userTimeUs);
@@ -926,7 +927,7 @@
     private void pullKernelUidCpuFreqTime(
             int tagId, long elapsedNanos, long wallClockNanos,
             List<StatsLogEventWrapper> pulledData) {
-        mKernelUidCpuFreqTimeReader.readAbsolute((uid, cpuFreqTimeMs) -> {
+        mCpuUidFreqTimeReader.readAbsolute((uid, cpuFreqTimeMs) -> {
             for (int freqIndex = 0; freqIndex < cpuFreqTimeMs.length; ++freqIndex) {
                 if (cpuFreqTimeMs[freqIndex] != 0) {
                     StatsLogEventWrapper e = new StatsLogEventWrapper(tagId, elapsedNanos,
@@ -943,7 +944,7 @@
     private void pullKernelUidCpuClusterTime(
             int tagId, long elapsedNanos, long wallClockNanos,
             List<StatsLogEventWrapper> pulledData) {
-        mKernelUidCpuClusterTimeReader.readAbsolute((uid, cpuClusterTimesMs) -> {
+        mCpuUidClusterTimeReader.readAbsolute((uid, cpuClusterTimesMs) -> {
             for (int i = 0; i < cpuClusterTimesMs.length; i++) {
                 StatsLogEventWrapper e = new StatsLogEventWrapper(tagId, elapsedNanos,
                         wallClockNanos);
@@ -958,7 +959,7 @@
     private void pullKernelUidCpuActiveTime(
             int tagId, long elapsedNanos, long wallClockNanos,
             List<StatsLogEventWrapper> pulledData) {
-        mKernelUidCpuActiveTimeReader.readAbsolute((uid, cpuActiveTimesMs) -> {
+        mCpuUidActiveTimeReader.readAbsolute((uid, cpuActiveTimesMs) -> {
             StatsLogEventWrapper e = new StatsLogEventWrapper(tagId, elapsedNanos, wallClockNanos);
             e.writeInt(uid);
             e.writeLong((long) cpuActiveTimesMs);
@@ -1708,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.
      */
@@ -1870,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/StatusBarManagerService.java b/services/core/java/com/android/server/statusbar/StatusBarManagerService.java
index 7c1e619..8d2bab4 100644
--- a/services/core/java/com/android/server/statusbar/StatusBarManagerService.java
+++ b/services/core/java/com/android/server/statusbar/StatusBarManagerService.java
@@ -1219,13 +1219,13 @@
     }
 
     @Override
-    public void onNotificationExpansionChanged(String key, boolean userAction,
-            boolean expanded) throws RemoteException {
+    public void onNotificationExpansionChanged(String key, boolean userAction, boolean expanded,
+            int location) throws RemoteException {
         enforceStatusBarService();
         long identity = Binder.clearCallingIdentity();
         try {
             mNotificationDelegate.onNotificationExpansionChanged(
-                    key, userAction, expanded);
+                    key, userAction, expanded, location);
         } finally {
             Binder.restoreCallingIdentity(identity);
         }
diff --git a/services/core/java/com/android/server/testharness/TestHarnessModeService.java b/services/core/java/com/android/server/testharness/TestHarnessModeService.java
new file mode 100644
index 0000000..23c042a5
--- /dev/null
+++ b/services/core/java/com/android/server/testharness/TestHarnessModeService.java
@@ -0,0 +1,328 @@
+/*
+ * 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.testharness;
+
+import android.annotation.Nullable;
+import android.app.KeyguardManager;
+import android.content.ContentResolver;
+import android.content.Context;
+import android.content.Intent;
+import android.content.pm.UserInfo;
+import android.os.BatteryManager;
+import android.os.Binder;
+import android.os.IBinder;
+import android.os.ResultReceiver;
+import android.os.ShellCallback;
+import android.os.ShellCommand;
+import android.os.SystemProperties;
+import android.os.UserHandle;
+import android.os.UserManager;
+import android.provider.Settings;
+import android.util.Slog;
+
+import com.android.server.LocalServices;
+import com.android.server.PersistentDataBlockManagerInternal;
+import com.android.server.SystemService;
+
+import java.io.ByteArrayInputStream;
+import java.io.ByteArrayOutputStream;
+import java.io.DataInputStream;
+import java.io.DataOutputStream;
+import java.io.FileDescriptor;
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.OutputStream;
+import java.io.PrintWriter;
+import java.nio.file.Files;
+import java.nio.file.Path;
+import java.nio.file.Paths;
+import java.nio.file.attribute.PosixFilePermission;
+import java.util.Set;
+
+/**
+ * Manages the Test Harness Mode service for setting up test harness mode on the device.
+ *
+ * <p>Test Harness Mode is a feature that allows the user to clean their device, retain ADB keys,
+ * and provision the device for Instrumentation testing. This means that all parts of the device
+ * that would otherwise interfere with testing (auto-syncing accounts, package verification,
+ * automatic updates, etc.) are all disabled by default but may be re-enabled by the user.
+ */
+public class TestHarnessModeService extends SystemService {
+    private static final String TAG = TestHarnessModeService.class.getSimpleName();
+    private static final String TEST_HARNESS_MODE_PROPERTY = "persist.sys.test_harness";
+
+    private PersistentDataBlockManagerInternal mPersistentDataBlockManagerInternal;
+
+    public TestHarnessModeService(Context context) {
+        super(context);
+    }
+
+    @Override
+    public void onStart() {
+        publishBinderService("testharness", mService);
+    }
+
+    @Override
+    public void onBootPhase(int phase) {
+        switch (phase) {
+            case PHASE_SYSTEM_SERVICES_READY:
+                setUpTestHarnessMode();
+                break;
+            case PHASE_BOOT_COMPLETED:
+                disableAutoSync();
+                break;
+        }
+        super.onBootPhase(phase);
+    }
+
+    private void setUpTestHarnessMode() {
+        Slog.d(TAG, "Setting up test harness mode");
+        byte[] testHarnessModeData = getPersistentDataBlock().getTestHarnessModeData();
+        if (testHarnessModeData == null || testHarnessModeData.length == 0) {
+            // There's no data to apply, so leave it as-is.
+            return;
+        }
+        PersistentData persistentData = PersistentData.fromBytes(testHarnessModeData);
+
+        SystemProperties.set(TEST_HARNESS_MODE_PROPERTY, persistentData.mEnabled ? "1" : "0");
+        writeAdbKeysFile(persistentData);
+        // Clear out the data block so that we don't revert the ADB keys on every boot.
+        getPersistentDataBlock().clearTestHarnessModeData();
+
+        ContentResolver cr = getContext().getContentResolver();
+        if (Settings.Global.getInt(cr, Settings.Global.ADB_ENABLED, 0) == 0) {
+            // Enable ADB
+            Settings.Global.putInt(cr, Settings.Global.ADB_ENABLED, 1);
+        } else {
+            // ADB is already enabled, we should restart the service so it picks up the new keys
+            android.os.SystemService.restart("adbd");
+        }
+
+        Settings.Global.putInt(cr, Settings.Global.PACKAGE_VERIFIER_ENABLE, 0);
+        Settings.Global.putInt(
+                cr,
+                Settings.Global.STAY_ON_WHILE_PLUGGED_IN,
+                BatteryManager.BATTERY_PLUGGED_ANY);
+        Settings.Global.putInt(cr, Settings.Global.OTA_DISABLE_AUTOMATIC_UPDATE, 1);
+        Settings.Global.putInt(cr, Settings.Global.DEVELOPMENT_SETTINGS_ENABLED, 1);
+
+        setDeviceProvisioned();
+    }
+
+    private void disableAutoSync() {
+        UserInfo primaryUser = UserManager.get(getContext()).getPrimaryUser();
+        ContentResolver
+            .setMasterSyncAutomaticallyAsUser(false, primaryUser.getUserHandle().getIdentifier());
+    }
+
+    private void writeAdbKeysFile(PersistentData persistentData) {
+        Path adbKeys = Paths.get("/data/misc/adb/adb_keys");
+        try {
+            OutputStream fileOutputStream = Files.newOutputStream(adbKeys);
+            fileOutputStream.write(persistentData.mAdbKeys);
+            fileOutputStream.close();
+
+            Set<PosixFilePermission> permissions = Files.getPosixFilePermissions(adbKeys);
+            permissions.add(PosixFilePermission.GROUP_READ);
+            Files.setPosixFilePermissions(adbKeys, permissions);
+        } catch (IOException e) {
+            Slog.e(TAG, "Failed to set up adb keys", e);
+            // Note: if a device enters this block, it will remain UNAUTHORIZED in ADB, but all
+            // other settings will be set up.
+        }
+    }
+
+    // Setting the device as provisioned skips the setup wizard.
+    private void setDeviceProvisioned() {
+        ContentResolver cr = getContext().getContentResolver();
+        Settings.Global.putInt(cr, Settings.Global.DEVICE_PROVISIONED, 1);
+        Settings.Secure.putIntForUser(
+                cr,
+                Settings.Secure.USER_SETUP_COMPLETE,
+                1,
+                UserHandle.USER_CURRENT);
+    }
+
+    @Nullable
+    private PersistentDataBlockManagerInternal getPersistentDataBlock() {
+        if (mPersistentDataBlockManagerInternal == null) {
+            Slog.d(TAG, "Getting PersistentDataBlockManagerInternal from LocalServices");
+            mPersistentDataBlockManagerInternal =
+                    LocalServices.getService(PersistentDataBlockManagerInternal.class);
+        }
+        return mPersistentDataBlockManagerInternal;
+    }
+
+    private final IBinder mService = new Binder() {
+        @Override
+        public void onShellCommand(FileDescriptor in, FileDescriptor out, FileDescriptor err,
+                String[] args, ShellCallback callback, ResultReceiver resultReceiver) {
+            (new TestHarnessModeShellCommand())
+                .exec(this, in, out, err, args, callback, resultReceiver);
+        }
+    };
+
+    private class TestHarnessModeShellCommand extends ShellCommand {
+        @Override
+        public int onCommand(String cmd) {
+            switch (cmd) {
+                case "enable":
+                case "restore":
+                    checkPermissions();
+                    final long originalId = Binder.clearCallingIdentity();
+                    try {
+                        if (isDeviceSecure()) {
+                            getErrPrintWriter().println(
+                                    "Test Harness Mode cannot be enabled if there is a lock "
+                                            + "screen");
+                            return 2;
+                        }
+                        return handleEnable();
+                    } finally {
+                        Binder.restoreCallingIdentity(originalId);
+                    }
+                default:
+                    return handleDefaultCommands(cmd);
+            }
+        }
+
+        private void checkPermissions() {
+            getContext().enforceCallingPermission(
+                    android.Manifest.permission.ENABLE_TEST_HARNESS_MODE,
+                    "You must hold android.permission.ENABLE_TEST_HARNESS_MODE "
+                            + "to enable Test Harness Mode");
+        }
+
+        private boolean isDeviceSecure() {
+            UserInfo primaryUser = UserManager.get(getContext()).getPrimaryUser();
+            KeyguardManager keyguardManager = getContext().getSystemService(KeyguardManager.class);
+            return keyguardManager.isDeviceSecure(primaryUser.id);
+        }
+
+        private int handleEnable() {
+            Path adbKeys = Paths.get("/data/misc/adb/adb_keys");
+            if (!Files.exists(adbKeys)) {
+                // This should only be accessible on eng builds that haven't yet set up ADB keys
+                getErrPrintWriter()
+                    .println("No ADB keys stored; not enabling test harness mode");
+                return 1;
+            }
+
+            try (InputStream inputStream = Files.newInputStream(adbKeys)) {
+                long size = Files.size(adbKeys);
+                byte[] adbKeysBytes = new byte[(int) size];
+                int numBytes = inputStream.read(adbKeysBytes);
+                if (numBytes != size) {
+                    getErrPrintWriter().println("Failed to read all bytes of adb_keys");
+                    return 1;
+                }
+                PersistentData persistentData = new PersistentData(true, adbKeysBytes);
+                getPersistentDataBlock().setTestHarnessModeData(persistentData.toBytes());
+            } catch (IOException e) {
+                Slog.e(TAG, "Failed to store ADB keys.", e);
+                getErrPrintWriter().println("Failed to enable Test Harness Mode");
+                return 1;
+            }
+
+            Intent i = new Intent(Intent.ACTION_FACTORY_RESET);
+            i.setPackage("android");
+            i.addFlags(Intent.FLAG_RECEIVER_FOREGROUND);
+            i.putExtra(Intent.EXTRA_REASON, TAG);
+            i.putExtra(Intent.EXTRA_WIPE_EXTERNAL_STORAGE, true);
+            getContext().sendBroadcastAsUser(i, UserHandle.SYSTEM);
+            return 0;
+        }
+
+        @Override
+        public void onHelp() {
+            PrintWriter pw = getOutPrintWriter();
+            pw.println("About:");
+            pw.println("  Test Harness Mode is a mode that the device can be placed in to prepare");
+            pw.println("  the device for running UI tests. The device is placed into this mode by");
+            pw.println("  first wiping all data from the device, preserving ADB keys.");
+            pw.println();
+            pw.println("  By default, the following settings are configured:");
+            pw.println("    * Package Verifier is disabled");
+            pw.println("    * Stay Awake While Charging is enabled");
+            pw.println("    * OTA Updates are disabled");
+            pw.println("    * Auto-Sync for accounts is disabled");
+            pw.println();
+            pw.println("  Other apps may configure themselves differently in Test Harness Mode by");
+            pw.println("  checking ActivityManager.isRunningInUserTestHarness()");
+            pw.println();
+            pw.println("Test Harness Mode commands:");
+            pw.println("  help");
+            pw.println("    Print this help text.");
+            pw.println();
+            pw.println("  enable|restore");
+            pw.println("    Erase all data from this device and enable Test Harness Mode,");
+            pw.println("    preserving the stored ADB keys currently on the device and toggling");
+            pw.println("    settings in a way that are conducive to Instrumentation testing.");
+        }
+    }
+
+    /**
+     * The object that will serialize/deserialize the Test Harness Mode data to and from the
+     * persistent data block.
+     */
+    public static class PersistentData {
+        static final byte VERSION_1 = 1;
+
+        final int mVersion;
+        final boolean mEnabled;
+        final byte[] mAdbKeys;
+
+        PersistentData(boolean enabled, byte[] adbKeys) {
+            this(VERSION_1, enabled, adbKeys);
+        }
+
+        PersistentData(int version, boolean enabled, byte[] adbKeys) {
+            this.mVersion = version;
+            this.mEnabled = enabled;
+            this.mAdbKeys = adbKeys;
+        }
+
+        static PersistentData fromBytes(byte[] bytes) {
+            try {
+                DataInputStream is = new DataInputStream(new ByteArrayInputStream(bytes));
+                int version = is.readInt();
+                boolean enabled = is.readBoolean();
+                int adbKeysLength = is.readInt();
+                byte[] adbKeys = new byte[adbKeysLength];
+                is.readFully(adbKeys);
+                return new PersistentData(version, enabled, adbKeys);
+            } catch (IOException e) {
+                throw new RuntimeException(e);
+            }
+        }
+
+        byte[] toBytes() {
+            try {
+                ByteArrayOutputStream os = new ByteArrayOutputStream();
+                DataOutputStream dos = new DataOutputStream(os);
+                dos.writeInt(VERSION_1);
+                dos.writeBoolean(mEnabled);
+                dos.writeInt(mAdbKeys.length);
+                dos.write(mAdbKeys);
+                dos.close();
+                return os.toByteArray();
+            } catch (IOException e) {
+                throw new RuntimeException(e);
+            }
+        }
+    }
+}
diff --git a/services/core/java/com/android/server/trust/TrustManagerService.java b/services/core/java/com/android/server/trust/TrustManagerService.java
index ced5935..423ec4c 100644
--- a/services/core/java/com/android/server/trust/TrustManagerService.java
+++ b/services/core/java/com/android/server/trust/TrustManagerService.java
@@ -22,10 +22,8 @@
 import android.app.AlarmManager;
 import android.app.AlarmManager.OnAlarmListener;
 import android.app.admin.DevicePolicyManager;
-import android.hardware.biometrics.BiometricSourceType;
 import android.app.trust.ITrustListener;
 import android.app.trust.ITrustManager;
-import android.app.UserSwitchObserver;
 import android.content.BroadcastReceiver;
 import android.content.ComponentName;
 import android.content.ContentResolver;
@@ -41,6 +39,7 @@
 import android.content.res.XmlResourceParser;
 import android.database.ContentObserver;
 import android.graphics.drawable.Drawable;
+import android.hardware.biometrics.BiometricSourceType;
 import android.net.Uri;
 import android.os.Binder;
 import android.os.Build;
@@ -72,13 +71,15 @@
 import com.android.internal.widget.LockPatternUtils;
 import com.android.server.SystemService;
 
+import org.xmlpull.v1.XmlPullParser;
+import org.xmlpull.v1.XmlPullParserException;
+
 import java.io.FileDescriptor;
 import java.io.IOException;
 import java.io.PrintWriter;
 import java.util.ArrayList;
 import java.util.List;
-import org.xmlpull.v1.XmlPullParser;
-import org.xmlpull.v1.XmlPullParserException;
+
 
 /**
  * Manages trust agents and trust listeners.
@@ -119,7 +120,7 @@
 
     private static final int TRUST_USUALLY_MANAGED_FLUSH_DELAY = 2 * 60 * 1000;
     private static final String TRUST_TIMEOUT_ALARM_TAG = "TrustManagerService.trustTimeoutForUser";
-    private static final long TRUST_TIMEOUT_IN_MILLIS = 20 * 1000; //4 * 60 * 60 * 1000;
+    private static final long TRUST_TIMEOUT_IN_MILLIS = 4 * 60 * 60 * 1000;
 
     private final ArraySet<AgentInfo> mActiveAgents = new ArraySet<>();
     private final ArrayList<ITrustListener> mTrustListeners = new ArrayList<>();
diff --git a/services/core/java/com/android/server/uri/UriGrantsManagerService.java b/services/core/java/com/android/server/uri/UriGrantsManagerService.java
index 9a7e75e..744efab 100644
--- a/services/core/java/com/android/server/uri/UriGrantsManagerService.java
+++ b/services/core/java/com/android/server/uri/UriGrantsManagerService.java
@@ -1129,7 +1129,8 @@
          * In this case, we grant a uri permission, even if the ContentProvider does not normally
          * grant uri permissions.
          */
-        boolean specialCrossUserGrant = UserHandle.getUserId(targetUid) != grantUri.sourceUserId
+        boolean specialCrossUserGrant = targetUid >= 0
+                && UserHandle.getUserId(targetUid) != grantUri.sourceUserId
                 && checkHoldingPermissionsInternal(pm, pi, grantUri, callingUid,
                 modeFlags, false /*without considering the uid permissions*/);
 
diff --git a/services/core/java/com/android/server/vr/VrManagerService.java b/services/core/java/com/android/server/vr/VrManagerService.java
index b3eafa4..45689ce 100644
--- a/services/core/java/com/android/server/vr/VrManagerService.java
+++ b/services/core/java/com/android/server/vr/VrManagerService.java
@@ -65,7 +65,6 @@
 import com.android.server.LocalServices;
 import com.android.server.SystemConfig;
 import com.android.server.SystemService;
-import com.android.server.inputmethod.InputMethodManagerInternal;
 import com.android.server.utils.ManagedApplicationService;
 import com.android.server.utils.ManagedApplicationService.BinderChecker;
 import com.android.server.utils.ManagedApplicationService.LogEvent;
@@ -623,14 +622,6 @@
         }
 
         @Override
-        public void setVrInputMethod(ComponentName componentName) {
-            enforceCallerPermissionAnyOf(Manifest.permission.RESTRICTED_VR_ACCESS);
-            InputMethodManagerInternal imm =
-                    LocalServices.getService(InputMethodManagerInternal.class);
-            imm.startVrInputMethodNoCheck(componentName);
-        }
-
-        @Override
         protected void dump(FileDescriptor fd, PrintWriter pw, String[] args) {
             if (!DumpUtils.checkDumpPermission(mContext, TAG, pw)) return;
 
diff --git a/services/core/java/com/android/server/wm/AccessibilityController.java b/services/core/java/com/android/server/wm/AccessibilityController.java
index caebf15..545b69b 100644
--- a/services/core/java/com/android/server/wm/AccessibilityController.java
+++ b/services/core/java/com/android/server/wm/AccessibilityController.java
@@ -49,6 +49,7 @@
 import android.util.Slog;
 import android.util.SparseArray;
 import android.util.TypedValue;
+import android.view.Display;
 import android.view.MagnificationSpec;
 import android.view.Surface;
 import android.view.Surface.OutOfResourcesException;
@@ -83,23 +84,36 @@
         mService = service;
     }
 
-    private DisplayMagnifier mDisplayMagnifier;
+    private SparseArray<DisplayMagnifier> mDisplayMagnifiers = new SparseArray<>();
 
     private WindowsForAccessibilityObserver mWindowsForAccessibilityObserver;
 
-    public void setMagnificationCallbacksLocked(MagnificationCallbacks callbacks) {
+    public boolean setMagnificationCallbacksLocked(int displayId,
+            MagnificationCallbacks callbacks) {
+        boolean result = false;
         if (callbacks != null) {
-            if (mDisplayMagnifier != null) {
+            if (mDisplayMagnifiers.get(displayId) != null) {
                 throw new IllegalStateException("Magnification callbacks already set!");
             }
-            mDisplayMagnifier = new DisplayMagnifier(mService, callbacks);
+            final DisplayContent dc = mService.mRoot.getDisplayContent(displayId);
+            if (dc != null) {
+                final Display display = dc.getDisplay();
+                if (display != null && display.getType() != Display.TYPE_OVERLAY) {
+                    mDisplayMagnifiers.put(displayId, new DisplayMagnifier(
+                            mService, dc, display, callbacks));
+                    result = true;
+                }
+            }
         } else {
-            if  (mDisplayMagnifier == null) {
+            final DisplayMagnifier displayMagnifier = mDisplayMagnifiers.get(displayId);
+            if  (displayMagnifier == null) {
                 throw new IllegalStateException("Magnification callbacks already cleared!");
             }
-            mDisplayMagnifier.destroyLocked();
-            mDisplayMagnifier = null;
+            displayMagnifier.destroyLocked();
+            mDisplayMagnifiers.remove(displayId);
+            result = true;
         }
+        return result;
     }
 
     public void setWindowsForAccessibilityCallback(WindowsForAccessibilityCallback callback) {
@@ -129,58 +143,72 @@
         }
     }
 
-    public void setMagnificationSpecLocked(MagnificationSpec spec) {
-        if (mDisplayMagnifier != null) {
-            mDisplayMagnifier.setMagnificationSpecLocked(spec);
+    public void setMagnificationSpecLocked(int displayId, MagnificationSpec spec) {
+        final DisplayMagnifier displayMagnifier = mDisplayMagnifiers.get(displayId);
+        if (displayMagnifier != null) {
+            displayMagnifier.setMagnificationSpecLocked(spec);
         }
-        if (mWindowsForAccessibilityObserver != null) {
+        // TODO: support multi-display for windows observer
+        if (mWindowsForAccessibilityObserver != null && displayId == Display.DEFAULT_DISPLAY) {
             mWindowsForAccessibilityObserver.scheduleComputeChangedWindowsLocked();
         }
     }
 
-    public void getMagnificationRegionLocked(Region outMagnificationRegion) {
-        if (mDisplayMagnifier != null) {
-            mDisplayMagnifier.getMagnificationRegionLocked(outMagnificationRegion);
+    public void getMagnificationRegionLocked(int displayId, Region outMagnificationRegion) {
+        final DisplayMagnifier displayMagnifier = mDisplayMagnifiers.get(displayId);
+        if (displayMagnifier != null) {
+            displayMagnifier.getMagnificationRegionLocked(outMagnificationRegion);
         }
     }
 
-    public void onRectangleOnScreenRequestedLocked(Rect rectangle) {
-        if (mDisplayMagnifier != null) {
-            mDisplayMagnifier.onRectangleOnScreenRequestedLocked(rectangle);
+    public void onRectangleOnScreenRequestedLocked(int displayId, Rect rectangle) {
+        final DisplayMagnifier displayMagnifier = mDisplayMagnifiers.get(displayId);
+        if (displayMagnifier != null) {
+            displayMagnifier.onRectangleOnScreenRequestedLocked(rectangle);
         }
         // Not relevant for the window observer.
     }
 
-    public void onWindowLayersChangedLocked() {
-        if (mDisplayMagnifier != null) {
-            mDisplayMagnifier.onWindowLayersChangedLocked();
+    public void onWindowLayersChangedLocked(int displayId) {
+        final DisplayMagnifier displayMagnifier = mDisplayMagnifiers.get(displayId);
+        if (displayMagnifier != null) {
+            displayMagnifier.onWindowLayersChangedLocked();
         }
-        if (mWindowsForAccessibilityObserver != null) {
+        // TODO: support multi-display for windows observer
+        if (mWindowsForAccessibilityObserver != null && displayId == Display.DEFAULT_DISPLAY) {
             mWindowsForAccessibilityObserver.scheduleComputeChangedWindowsLocked();
         }
     }
 
     public void onRotationChangedLocked(DisplayContent displayContent) {
-        if (mDisplayMagnifier != null) {
-            mDisplayMagnifier.onRotationChangedLocked(displayContent);
+        final int displayId = displayContent.getDisplayId();
+        final DisplayMagnifier displayMagnifier = mDisplayMagnifiers.get(displayId);
+        if (displayMagnifier != null) {
+            displayMagnifier.onRotationChangedLocked(displayContent);
         }
-        if (mWindowsForAccessibilityObserver != null) {
+        // TODO: support multi-display for windows observer
+        if (mWindowsForAccessibilityObserver != null && displayId == Display.DEFAULT_DISPLAY) {
             mWindowsForAccessibilityObserver.scheduleComputeChangedWindowsLocked();
         }
     }
 
     public void onAppWindowTransitionLocked(WindowState windowState, int transition) {
-        if (mDisplayMagnifier != null) {
-            mDisplayMagnifier.onAppWindowTransitionLocked(windowState, transition);
+        final int displayId = windowState.getDisplayId();
+        final DisplayMagnifier displayMagnifier = mDisplayMagnifiers.get(displayId);
+        if (displayMagnifier != null) {
+            displayMagnifier.onAppWindowTransitionLocked(windowState, transition);
         }
         // Not relevant for the window observer.
     }
 
     public void onWindowTransitionLocked(WindowState windowState, int transition) {
-        if (mDisplayMagnifier != null) {
-            mDisplayMagnifier.onWindowTransitionLocked(windowState, transition);
+        final int displayId = windowState.getDisplayId();
+        final DisplayMagnifier displayMagnifier = mDisplayMagnifiers.get(displayId);
+        if (displayMagnifier != null) {
+            displayMagnifier.onWindowTransitionLocked(windowState, transition);
         }
-        if (mWindowsForAccessibilityObserver != null) {
+        // TODO: support multi-display for windows observer
+        if (mWindowsForAccessibilityObserver != null && displayId == Display.DEFAULT_DISPLAY) {
             mWindowsForAccessibilityObserver.scheduleComputeChangedWindowsLocked();
         }
     }
@@ -197,7 +225,6 @@
         }
     }
 
-
     public void onSomeWindowResizedOrMovedLocked() {
         // Not relevant for the display magnifier.
 
@@ -207,29 +234,34 @@
     }
 
     /** NOTE: This has to be called within a surface transaction. */
-    public void drawMagnifiedRegionBorderIfNeededLocked() {
-        if (mDisplayMagnifier != null) {
-            mDisplayMagnifier.drawMagnifiedRegionBorderIfNeededLocked();
+    public void drawMagnifiedRegionBorderIfNeededLocked(int displayId) {
+        final DisplayMagnifier displayMagnifier = mDisplayMagnifiers.get(displayId);
+        if (displayMagnifier != null) {
+            displayMagnifier.drawMagnifiedRegionBorderIfNeededLocked();
         }
         // Not relevant for the window observer.
     }
 
     public MagnificationSpec getMagnificationSpecForWindowLocked(WindowState windowState) {
-        if (mDisplayMagnifier != null) {
-            return mDisplayMagnifier.getMagnificationSpecForWindowLocked(windowState);
+        final int displayId = windowState.getDisplayId();
+        final DisplayMagnifier displayMagnifier = mDisplayMagnifiers.get(displayId);
+        if (displayMagnifier != null) {
+            return displayMagnifier.getMagnificationSpecForWindowLocked(windowState);
         }
         return null;
     }
 
     public boolean hasCallbacksLocked() {
-        return (mDisplayMagnifier != null
+        // TODO: support multi-display for windows observer
+        return (mDisplayMagnifiers.size() > 0
                 || mWindowsForAccessibilityObserver != null);
     }
 
-    public void setForceShowMagnifiableBoundsLocked(boolean show) {
-        if (mDisplayMagnifier != null) {
-            mDisplayMagnifier.setForceShowMagnifiableBoundsLocked(show);
-            mDisplayMagnifier.showMagnificationBoundsIfNeeded();
+    public void setForceShowMagnifiableBoundsLocked(int displayId, boolean show) {
+        final DisplayMagnifier displayMagnifier = mDisplayMagnifiers.get(displayId);
+        if (displayMagnifier != null) {
+            displayMagnifier.setForceShowMagnifiableBoundsLocked(show);
+            displayMagnifier.showMagnificationBoundsIfNeeded();
         }
     }
 
@@ -263,6 +295,8 @@
         private final WindowManagerService mService;
         private final MagnifiedViewport mMagnifedViewport;
         private final Handler mHandler;
+        private final DisplayContent mDisplayContent;
+        private final Display mDisplay;
 
         private final MagnificationCallbacks mCallbacks;
 
@@ -271,10 +305,14 @@
         private boolean mForceShowMagnifiableBounds = false;
 
         public DisplayMagnifier(WindowManagerService windowManagerService,
+                DisplayContent displayContent,
+                Display display,
                 MagnificationCallbacks callbacks) {
             mContext = windowManagerService.mContext;
             mService = windowManagerService;
             mCallbacks = callbacks;
+            mDisplayContent = displayContent;
+            mDisplay = display;
             mHandler = new MyHandler(mService.mH.getLooper());
             mMagnifedViewport = new MagnifiedViewport();
             mLongAnimationDuration = mContext.getResources().getInteger(
@@ -285,7 +323,7 @@
             mMagnifedViewport.updateMagnificationSpecLocked(spec);
             mMagnifedViewport.recomputeBoundsLocked();
 
-            mService.applyMagnificationSpec(spec);
+            mService.applyMagnificationSpecLocked(mDisplay.getDisplayId(), spec);
             mService.scheduleAnimationLocked();
         }
 
@@ -482,7 +520,7 @@
 
                 if (mContext.getResources().getConfiguration().isScreenRound()) {
                     mCircularPath = new Path();
-                    mWindowManager.getDefaultDisplay().getRealSize(mTempPoint);
+                    mDisplay.getRealSize(mTempPoint);
                     final int centerXY = mTempPoint.x / 2;
                     mCircularPath.addCircle(centerXY, centerXY, centerXY, Path.Direction.CW);
                 } else {
@@ -512,7 +550,7 @@
             }
 
             public void recomputeBoundsLocked() {
-                mWindowManager.getDefaultDisplay().getRealSize(mTempPoint);
+                mDisplay.getRealSize(mTempPoint);
                 final int screenWidth = mTempPoint.x;
                 final int screenHeight = mTempPoint.y;
 
@@ -671,9 +709,8 @@
             }
 
             private void populateWindowsOnScreenLocked(SparseArray<WindowState> outWindows) {
-                final DisplayContent dc = mService.getDefaultDisplayContentLocked();
                 mTempLayer = 0;
-                dc.forAllWindows((w) -> {
+                mDisplayContent.forAllWindows((w) -> {
                     if (w.isOnScreen() && w.isVisibleLw()
                             && (w.mAttrs.alpha != 0)
                             && !w.mWinAnimator.mEnterAnimationPending) {
@@ -703,8 +740,9 @@
                 public ViewportWindow(Context context) {
                     SurfaceControl surfaceControl = null;
                     try {
-                        mWindowManager.getDefaultDisplay().getRealSize(mTempPoint);
-                        surfaceControl = mService.getDefaultDisplayContentLocked().makeOverlay()
+                        mDisplay.getRealSize(mTempPoint);
+                        surfaceControl = mDisplayContent
+                                .makeOverlay()
                                 .setName(SURFACE_TITLE)
                                 .setBufferSize(mTempPoint.x, mTempPoint.y) // not a typo
                                 .setFormat(PixelFormat.TRANSLUCENT)
diff --git a/services/core/java/com/android/server/wm/ActivityDisplay.java b/services/core/java/com/android/server/wm/ActivityDisplay.java
index e817dd4..65d66f4 100644
--- a/services/core/java/com/android/server/wm/ActivityDisplay.java
+++ b/services/core/java/com/android/server/wm/ActivityDisplay.java
@@ -558,26 +558,22 @@
     }
 
     /**
-     * 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.
+     * Pause all activities in either all of the stacks or just the back stacks.
      * @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 {@code true} if any activity was paused as a result of this call.
+     * @return 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);
-            final ActivityRecord resumedActivity = stack.getResumedActivity();
-            if (resumedActivity != null
-                    && (!stack.shouldBeVisible(resuming) || !stack.isFocusable())) {
+            // TODO(b/111541062): Check if resumed activity on this display instead
+            if (!mRootActivityContainer.isTopDisplayFocusedStack(stack)
+                    && stack.getResumedActivity() != null) {
                 if (DEBUG_STATES) Slog.d(TAG_STATES, "pauseBackStacks: stack=" + stack +
-                        " mResumedActivity=" + resumedActivity);
+                        " mResumedActivity=" + stack.getResumedActivity());
                 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 6213fa0..b8634d8 100644
--- a/services/core/java/com/android/server/wm/ActivityRecord.java
+++ b/services/core/java/com/android/server/wm/ActivityRecord.java
@@ -1946,84 +1946,30 @@
         try {
             mAtmService.getLifecycleManager().scheduleTransaction(app.getThread(), appToken,
                     WindowVisibilityItem.obtain(true /* showWindow */));
-            makeActiveIfNeeded(null /* activeActivity*/);
+            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 */));
+            }
         } catch (Exception e) {
             Slog.w(TAG, "Exception thrown sending visibility update: " + intent.getComponent(), e);
         }
     }
 
-    /**
-     * 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
-     * - 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
+    /** 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
         // 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 a proper
-        // active state.
-        if (!isState(RESUMED, PAUSED, STOPPED, STOPPING)
-                || getActivityStack().mTranslucentActivityWaiting != null) {
-            return false;
-        }
-
-        if (this == activeActivity) {
+        // 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()) {
             return false;
         }
 
@@ -2033,14 +1979,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 resumed now
+            // It's the topmost activity in the task - should become paused 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 making active if activity above wasn't launched for result.
-            // Otherwise it will cause this activity to resume before getting result.
+            // We will only allow pausing 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 3aef8e1f..3a754c4 100644
--- a/services/core/java/com/android/server/wm/ActivityStack.java
+++ b/services/core/java/com/android/server/wm/ActivityStack.java
@@ -357,11 +357,6 @@
      */
     boolean mForceHidden = false;
 
-    /**
-     * Used to keep resumeTopActivityUncheckedLocked() from being entered recursively
-     */
-    boolean mInResumeTopActivity = false;
-
     private boolean mUpdateBoundsDeferred;
     private boolean mUpdateBoundsDeferredCalled;
     private boolean mUpdateDisplayedBoundsDeferredCalled;
@@ -1737,7 +1732,6 @@
             "Activity paused: token=" + token + ", timeout=" + timeout);
 
         final ActivityRecord r = isInStackLocked(token);
-
         if (r != null) {
             mHandler.removeMessages(PAUSE_TIMEOUT_MSG, r);
             if (mPausingActivity == r) {
@@ -2094,7 +2088,8 @@
             boolean aboveTop = top != null;
             final boolean stackShouldBeVisible = shouldBeVisible(starting);
             boolean behindFullscreenActivity = !stackShouldBeVisible;
-            boolean resumeNextActivity = isFocusable() && isInStackLocked(starting) == null;
+            boolean resumeNextActivity = mRootActivityContainer.isTopDisplayFocusedStack(this)
+                    && (isInStackLocked(starting) == null);
             final boolean isTopNotPinnedStack =
                     isAttached() && getDisplay().isTopNotPinnedStack(this);
             for (int taskNdx = mTaskHistory.size() - 1; taskNdx >= 0; --taskNdx) {
@@ -2155,10 +2150,6 @@
                             if (r.handleAlreadyVisible()) {
                                 resumeNextActivity = false;
                             }
-
-                            if (notifyClients) {
-                                r.makeActiveIfNeeded(starting);
-                            }
                         } else {
                             r.makeVisibleIfNeeded(starting, notifyClients);
                         }
@@ -2295,7 +2286,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="
@@ -2336,7 +2327,7 @@
                 r.setVisible(true);
             }
             if (r != starting) {
-                mStackSupervisor.startSpecificActivityLocked(r, andResume, true /* checkConfig */);
+                mStackSupervisor.startSpecificActivityLocked(r, andResume, false);
                 return true;
             }
         }
@@ -2514,7 +2505,7 @@
      */
     @GuardedBy("mService")
     boolean resumeTopActivityUncheckedLocked(ActivityRecord prev, ActivityOptions options) {
-        if (mInResumeTopActivity) {
+        if (mStackSupervisor.inResumeTopActivity) {
             // Don't even start recursing.
             return false;
         }
@@ -2522,7 +2513,7 @@
         boolean result = false;
         try {
             // Protect against recursion.
-            mInResumeTopActivity = true;
+            mStackSupervisor.inResumeTopActivity = true;
             result = resumeTopActivityInnerLocked(prev, options);
 
             // When resuming the top activity, it may be necessary to pause the top activity (for
@@ -2537,7 +2528,7 @@
                 checkReadyForSleep();
             }
         } finally {
-            mInResumeTopActivity = false;
+            mStackSupervisor.inResumeTopActivity = false;
         }
 
         return result;
@@ -2570,7 +2561,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.
-        ActivityRecord next = topRunningActivityLocked(true /* focusableOnly */);
+        final ActivityRecord next = topRunningActivityLocked(true /* focusableOnly */);
 
         final boolean hasRunningActivity = next != null;
 
@@ -2658,12 +2649,6 @@
         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;
         }
 
@@ -2873,9 +2858,7 @@
             // the screen based on the new activity order.
             boolean notUpdated = true;
 
-            // Activity should also be visible if set mLaunchTaskBehind to true (see
-            // ActivityRecord#shouldBeVisibleIgnoringKeyguard()).
-            if (shouldBeVisible(next)) {
+            if (isFocusedStackOnDisplay()) {
                 // 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
@@ -4104,12 +4087,6 @@
         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 a83ef34..3a288ca 100644
--- a/services/core/java/com/android/server/wm/ActivityStackSupervisor.java
+++ b/services/core/java/com/android/server/wm/ActivityStackSupervisor.java
@@ -327,6 +327,9 @@
      */
     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 43c1206..2807094 100644
--- a/services/core/java/com/android/server/wm/ActivityStartController.java
+++ b/services/core/java/com/android/server/wm/ActivityStartController.java
@@ -179,10 +179,7 @@
                 .setActivityOptions(options.toBundle())
                 .execute();
         mLastHomeActivityStartRecord = tmpOutRecord[0];
-        final ActivityDisplay display =
-                mService.mRootActivityContainer.getActivityDisplay(displayId);
-        final ActivityStack homeStack = display != null ? display.getHomeStack() : null;
-        if (homeStack != null && homeStack.mInResumeTopActivity) {
+        if (mSupervisor.inResumeTopActivity) {
             // 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 4e2dffc..38580bc 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,21 +975,11 @@
                 + "; 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);
@@ -1629,7 +1632,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(mStartActivity, 0, !PRESERVE_WINDOWS);
+                mTargetStack.ensureActivitiesVisibleLocked(null, 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 61c4863..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) {
@@ -5313,9 +5334,18 @@
     }
 
     void updateActivityUsageStats(ActivityRecord activity, int event) {
+        ComponentName taskRoot = null;
+        final TaskRecord task = activity.getTaskRecord();
+        if (task != null) {
+            final ActivityRecord rootActivity = task.getRootActivity();
+            if (rootActivity != null) {
+                taskRoot = rootActivity.mActivityComponent;
+            }
+        }
+
         final Message m = PooledLambda.obtainMessage(
                 ActivityManagerInternal::updateActivityUsageStats, mAmInternal,
-                activity.mActivityComponent, activity.mUserId, event, activity.appToken);
+                activity.mActivityComponent, activity.mUserId, event, activity.appToken, taskRoot);
         mH.sendMessage(m);
     }
 
@@ -5623,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
@@ -6213,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
@@ -7035,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 780eda49..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;
@@ -25,7 +26,6 @@
 import static android.content.pm.ActivityInfo.SCREEN_ORIENTATION_BEHIND;
 import static android.content.pm.ActivityInfo.SCREEN_ORIENTATION_UNSET;
 import static android.os.Trace.TRACE_TAG_WINDOW_MANAGER;
-import static android.view.Display.DEFAULT_DISPLAY;
 import static android.view.WindowManager.LayoutParams.FLAG_DISMISS_KEYGUARD;
 import static android.view.WindowManager.LayoutParams.FLAG_SECURE;
 import static android.view.WindowManager.LayoutParams.FLAG_SHOW_WALLPAPER;
@@ -34,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;
 
@@ -98,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;
@@ -118,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;
@@ -262,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;
@@ -273,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;
 
@@ -591,9 +607,7 @@
                     delayed = runningAppAnimation = true;
                 }
                 final WindowState window = findMainWindow();
-                //TODO (multidisplay): Magnification is supported only for the default display.
-                if (window != null && accessibilityController != null
-                        && getDisplayContent().getDisplayId() == DEFAULT_DISPLAY) {
+                if (window != null && accessibilityController != null) {
                     accessibilityController.onAppWindowTransitionLocked(window, transit);
                 }
                 changed = true;
@@ -813,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;
@@ -1531,6 +1546,7 @@
     @Override
     public void onConfigurationChanged(Configuration newParentConfig) {
         final int prevWinMode = getWindowingMode();
+        mTmpPrevBounds.set(getBounds());
         super.onConfigurationChanged(newParentConfig);
         final int winMode = getWindowingMode();
 
@@ -1562,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) {
@@ -1975,7 +2066,7 @@
         } else if (newTask || !processRunning || (taskSwitch && !activityCreated)) {
             return STARTING_WINDOW_TYPE_SPLASH_SCREEN;
         } else if (taskSwitch && allowTaskSnapshot) {
-            if (mWmService.mLowRamTaskSnapshots) {
+            if (mWmService.mLowRamTaskSnapshotsAndRecents) {
                 // For low RAM devices, we use the splash screen starting window instead of the
                 // task snapshot starting window.
                 return STARTING_WINDOW_TYPE_SPLASH_SCREEN;
@@ -2245,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) {
 
@@ -2263,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();
@@ -2297,6 +2416,10 @@
                 if (adapter.getShowWallpaper()) {
                     mDisplayContent.pendingLayoutChanges |= FINISH_LAYOUT_REDO_WALLPAPER;
                 }
+                if (thumbnailAdapter != null) {
+                    mThumbnail.startAnimation(
+                            getPendingTransaction(), thumbnailAdapter, !isVisible());
+                }
             }
         } else {
             cancelAnimation();
@@ -2407,7 +2530,7 @@
     @Override
     protected void reparentSurfaceControl(Transaction t, SurfaceControl newParent) {
         if (!mSurfaceAnimator.hasLeash()) {
-            t.reparent(mSurfaceControl, newParent.getHandle());
+            t.reparent(mSurfaceControl, newParent);
         }
     }
 
@@ -2432,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);
         }
@@ -2453,7 +2587,7 @@
             t.setWindowCrop(mAnimationBoundsLayer, mTmpRect);
 
             // Reparent leash to animation bounds layer.
-            t.reparent(leash, mAnimationBoundsLayer.getHandle());
+            t.reparent(leash, mAnimationBoundsLayer);
         }
     }
 
@@ -2490,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);
@@ -2521,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() {
@@ -2838,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 8026a04..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.
      *
@@ -1466,11 +1467,9 @@
             }
         }
 
-        // TODO (multi-display): Magnification is supported only for the default display.
         // Announce rotation only if we will not animate as we already have the
         // windows in final state. Otherwise, we make this call at the rotation end.
-        if (screenRotationAnimation == null && mWmService.mAccessibilityController != null
-                && isDefaultDisplay) {
+        if (screenRotationAnimation == null && mWmService.mAccessibilityController != null) {
             mWmService.mAccessibilityController.onRotationChangedLocked(this);
         }
     }
@@ -2456,6 +2455,7 @@
             // Clear all transitions & screen frozen states when removing display.
             mOpeningApps.clear();
             mClosingApps.clear();
+            mChangingApps.clear();
             mUnknownAppVisibilityController.clear();
             mAppTransition.removeAppTransitionTimeoutCallbacks();
             handleAnimatingStoppedAndTransition();
@@ -3286,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);
@@ -3294,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, "  ");
@@ -4590,7 +4593,7 @@
      * Reparents the given surface to mOverlayLayer.
      */
     void reparentToOverlay(Transaction transaction, SurfaceControl surface) {
-        transaction.reparent(surface, mOverlayLayer.getHandle());
+        transaction.reparent(surface, mOverlayLayer);
     }
 
     void applyMagnificationSpec(MagnificationSpec spec) {
@@ -4833,11 +4836,11 @@
      * Re-parent the DisplayContent's top surfaces, {@link #mWindowingLayer} and
      * {@link #mOverlayLayer} to the specified surfaceControl.
      *
-     * @param surfaceControlHandle The handle for the new SurfaceControl, where the DisplayContent's
+     * @param surfaceControlHandle The new SurfaceControl, where the DisplayContent's
      *                             surfaces will be re-parented to.
      */
-    void reparentDisplayContent(IBinder surfaceControlHandle) {
-        mPendingTransaction.reparent(mWindowingLayer, surfaceControlHandle)
-                .reparent(mOverlayLayer, surfaceControlHandle);
+    void reparentDisplayContent(SurfaceControl sc) {
+        mPendingTransaction.reparent(mWindowingLayer, sc)
+                .reparent(mOverlayLayer, sc);
     }
 }
diff --git a/services/core/java/com/android/server/wm/DisplayPolicy.java b/services/core/java/com/android/server/wm/DisplayPolicy.java
index 6d3c693..4006332 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);
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/DragState.java b/services/core/java/com/android/server/wm/DragState.java
index 786a306..3f77e1c 100644
--- a/services/core/java/com/android/server/wm/DragState.java
+++ b/services/core/java/com/android/server/wm/DragState.java
@@ -120,6 +120,8 @@
     // A surface used to catch input events for the drag-and-drop operation.
     SurfaceControl mInputSurface;
 
+    private final SurfaceControl.Transaction mTransaction = new SurfaceControl.Transaction();
+
     private final Rect mTmpClipRect = new Rect();
 
     /**
@@ -240,7 +242,7 @@
 
         // Clear the internal variables.
         if (mSurfaceControl != null) {
-            mSurfaceControl.destroy();
+            mTransaction.reparent(mSurfaceControl, null).apply();
             mSurfaceControl = null;
         }
         if (mAnimator != null && !mAnimationCompleted) {
@@ -500,18 +502,13 @@
         mCurrentY = y;
 
         // Move the surface to the given touch
-        if (SHOW_LIGHT_TRANSACTIONS) Slog.i(
-                TAG_WM, ">>> OPEN TRANSACTION notifyMoveLocked");
-        mService.openSurfaceTransaction();
-        try {
-            mSurfaceControl.setPosition(x - mThumbOffsetX, y - mThumbOffsetY);
-            if (SHOW_TRANSACTIONS) Slog.i(TAG_WM, "  DRAG "
-                    + mSurfaceControl + ": pos=(" +
-                    (int)(x - mThumbOffsetX) + "," + (int)(y - mThumbOffsetY) + ")");
-        } finally {
-            mService.closeSurfaceTransaction("notifyMoveLw");
-            if (SHOW_LIGHT_TRANSACTIONS) Slog.i(
-                    TAG_WM, "<<< CLOSE TRANSACTION notifyMoveLocked");
+        if (SHOW_LIGHT_TRANSACTIONS) {
+            Slog.i(TAG_WM, ">>> OPEN TRANSACTION notifyMoveLocked");
+        }
+        mTransaction.setPosition(mSurfaceControl, x - mThumbOffsetX, y - mThumbOffsetY).apply();
+        if (SHOW_TRANSACTIONS) {
+            Slog.i(TAG_WM, "  DRAG " + mSurfaceControl + ": pos=(" + (int) (x - mThumbOffsetX) + ","
+                    + (int) (y - mThumbOffsetY) + ")");
         }
         notifyLocationLocked(x, y);
     }
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 c4a853d..9b72141 100644
--- a/services/core/java/com/android/server/wm/RootActivityContainer.java
+++ b/services/core/java/com/android/server/wm/RootActivityContainer.java
@@ -1108,41 +1108,28 @@
             return false;
         }
 
-        boolean result = false;
         if (targetStack != null && (targetStack.isTopStackOnDisplay()
                 || getTopDisplayFocusedStack() == targetStack)) {
-            result = targetStack.resumeTopActivityUncheckedLocked(target, targetOptions);
+            return 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);
-            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 ActivityStack focusedStack = display.getFocusedStack();
+            if (focusedStack == null) {
+                continue;
             }
-            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);
-                }
+            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);
             }
         }
 
-        return result;
+        return false;
     }
 
     void applySleepTokens(boolean applyToStacks) {
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/SurfaceAnimator.java b/services/core/java/com/android/server/wm/SurfaceAnimator.java
index 9d9b48a..1a8a911 100644
--- a/services/core/java/com/android/server/wm/SurfaceAnimator.java
+++ b/services/core/java/com/android/server/wm/SurfaceAnimator.java
@@ -199,7 +199,7 @@
      * @see #setLayer
      */
     void reparent(Transaction t, SurfaceControl newParent) {
-        t.reparent(mLeash != null ? mLeash : mAnimatable.getSurfaceControl(), newParent.getHandle());
+        t.reparent(mLeash != null ? mLeash : mAnimatable.getSurfaceControl(), newParent);
     }
 
     /**
@@ -228,8 +228,8 @@
 
         // Cancel source animation, but don't let animation runner cancel the animation.
         from.cancelAnimation(t, false /* restarting */, false /* forwardCancel */);
-        t.reparent(surface, mLeash.getHandle());
-        t.reparent(mLeash, parent.getHandle());
+        t.reparent(surface, mLeash);
+        t.reparent(mLeash, parent);
         mAnimatable.onAnimationLeashCreated(t, mLeash);
         mService.mAnimationTransferMap.put(mAnimation, this);
     }
@@ -275,7 +275,7 @@
         final boolean destroy = mLeash != null && surface != null && parent != null;
         if (destroy) {
             if (DEBUG_ANIM) Slog.i(TAG, "Reparenting to original parent");
-            t.reparent(surface, parent.getHandle());
+            t.reparent(surface, parent);
             scheduleAnim = true;
         }
         mService.mAnimationTransferMap.remove(mAnimation);
@@ -308,7 +308,7 @@
         if (!hidden) {
             t.show(leash);
         }
-        t.reparent(surface, leash.getHandle());
+        t.reparent(surface, leash);
         return leash;
     }
 
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 0529ed1..69dcaf4 100644
--- a/services/core/java/com/android/server/wm/TaskRecord.java
+++ b/services/core/java/com/android/server/wm/TaskRecord.java
@@ -698,14 +698,6 @@
             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/TaskSnapshotController.java b/services/core/java/com/android/server/wm/TaskSnapshotController.java
index 01a5622..beb3d82 100644
--- a/services/core/java/com/android/server/wm/TaskSnapshotController.java
+++ b/services/core/java/com/android/server/wm/TaskSnapshotController.java
@@ -17,7 +17,6 @@
 package com.android.server.wm;
 
 import static com.android.server.wm.TaskSnapshotPersister.DISABLE_FULL_SIZED_BITMAPS;
-import static com.android.server.wm.TaskSnapshotPersister.REDUCED_SCALE;
 import static com.android.server.wm.WindowManagerDebugConfig.DEBUG_SCREENSHOT;
 import static com.android.server.wm.WindowManagerDebugConfig.TAG_WITH_CLASS_NAME;
 import static com.android.server.wm.WindowManagerDebugConfig.TAG_WM;
@@ -90,9 +89,8 @@
     private final WindowManagerService mService;
 
     private final TaskSnapshotCache mCache;
-    private final TaskSnapshotPersister mPersister = new TaskSnapshotPersister(
-            Environment::getDataSystemCeDirectory);
-    private final TaskSnapshotLoader mLoader = new TaskSnapshotLoader(mPersister);
+    private final TaskSnapshotPersister mPersister;
+    private final TaskSnapshotLoader mLoader;
     private final ArraySet<Task> mSkipClosingAppSnapshotTasks = new ArraySet<>();
     private final ArraySet<Task> mTmpTasks = new ArraySet<>();
     private final Handler mHandler = new Handler();
@@ -116,6 +114,8 @@
 
     TaskSnapshotController(WindowManagerService service) {
         mService = service;
+        mPersister = new TaskSnapshotPersister(mService, Environment::getDataSystemCeDirectory);
+        mLoader = new TaskSnapshotLoader(mPersister);
         mCache = new TaskSnapshotCache(mService, mLoader);
         mIsRunningOnTv = mService.mContext.getPackageManager().hasSystemFeature(
                 PackageManager.FEATURE_LEANBACK);
@@ -270,7 +270,7 @@
         }
 
         final boolean isLowRamDevice = ActivityManager.isLowRamDeviceStatic();
-        final float scaleFraction = isLowRamDevice ? REDUCED_SCALE : 1f;
+        final float scaleFraction = isLowRamDevice ? mPersister.getReducedScale() : 1f;
         task.getBounds(mTmpRect);
         mTmpRect.offsetTo(0, 0);
 
diff --git a/services/core/java/com/android/server/wm/TaskSnapshotLoader.java b/services/core/java/com/android/server/wm/TaskSnapshotLoader.java
index 0e1570b..d30843b 100644
--- a/services/core/java/com/android/server/wm/TaskSnapshotLoader.java
+++ b/services/core/java/com/android/server/wm/TaskSnapshotLoader.java
@@ -16,7 +16,6 @@
 
 package com.android.server.wm;
 
-import static com.android.server.wm.TaskSnapshotPersister.*;
 import static com.android.server.wm.WindowManagerDebugConfig.TAG_WITH_CLASS_NAME;
 import static com.android.server.wm.WindowManagerDebugConfig.TAG_WM;
 
@@ -92,7 +91,7 @@
                     proto.topActivityComponent);
             return new TaskSnapshot(topActivityComponent, buffer, proto.orientation,
                     new Rect(proto.insetLeft, proto.insetTop, proto.insetRight, proto.insetBottom),
-                    reducedResolution, reducedResolution ? REDUCED_SCALE : 1f,
+                    reducedResolution, reducedResolution ? mPersister.getReducedScale() : 1f,
                     proto.isRealSnapshot, proto.windowingMode, proto.systemUiVisibility,
                     proto.isTranslucent);
         } catch (IOException e) {
diff --git a/services/core/java/com/android/server/wm/TaskSnapshotPersister.java b/services/core/java/com/android/server/wm/TaskSnapshotPersister.java
index 24b5b61..e6d646c 100644
--- a/services/core/java/com/android/server/wm/TaskSnapshotPersister.java
+++ b/services/core/java/com/android/server/wm/TaskSnapshotPersister.java
@@ -52,7 +52,9 @@
     private static final String TAG = TAG_WITH_CLASS_NAME ? "TaskSnapshotPersister" : TAG_WM;
     private static final String SNAPSHOTS_DIRNAME = "snapshots";
     private static final String REDUCED_POSTFIX = "_reduced";
-    static final float REDUCED_SCALE = ActivityManager.isLowRamDeviceStatic() ? 0.6f : 0.5f;
+    private static final float REDUCED_SCALE = .5f;
+    private static final float LOW_RAM_REDUCED_SCALE = .6f;
+    private static final float LOW_RAM_RECENTS_REDUCED_SCALE = .1f;
     static final boolean DISABLE_FULL_SIZED_BITMAPS = ActivityManager.isLowRamDeviceStatic();
     private static final long DELAY_MS = 100;
     private static final int QUALITY = 95;
@@ -71,6 +73,7 @@
     private boolean mStarted;
     private final Object mLock = new Object();
     private final DirectoryResolver mDirectoryResolver;
+    private final float mReducedScale;
 
     /**
      * The list of ids of the tasks that have been persisted since {@link #removeObsoleteFiles} was
@@ -79,8 +82,16 @@
     @GuardedBy("mLock")
     private final ArraySet<Integer> mPersistedTaskIdsSinceLastRemoveObsolete = new ArraySet<>();
 
-    TaskSnapshotPersister(DirectoryResolver resolver) {
+    TaskSnapshotPersister(WindowManagerService service, DirectoryResolver resolver) {
         mDirectoryResolver = resolver;
+        if (service.mLowRamTaskSnapshotsAndRecents) {
+            // Use very low res snapshots if we are using Go version of recents.
+            mReducedScale = LOW_RAM_RECENTS_REDUCED_SCALE;
+        } else {
+            // TODO(122671846) Replace the low RAM value scale with the above when it is fully built
+            mReducedScale = ActivityManager.isLowRamDeviceStatic()
+                    ? LOW_RAM_REDUCED_SCALE : REDUCED_SCALE;
+        }
     }
 
     /**
@@ -144,6 +155,15 @@
         }
     }
 
+    /**
+     * Gets the scaling the persister uses for low resolution task snapshots.
+     *
+     * @return the reduced scale of task snapshots when they are set to be low res
+     */
+    float getReducedScale() {
+        return mReducedScale;
+    }
+
     @TestApi
     void waitForQueueEmpty() {
         while (true) {
@@ -350,8 +370,8 @@
             final Bitmap reduced = mSnapshot.isReducedResolution()
                     ? swBitmap
                     : Bitmap.createScaledBitmap(swBitmap,
-                            (int) (bitmap.getWidth() * REDUCED_SCALE),
-                            (int) (bitmap.getHeight() * REDUCED_SCALE), true /* filter */);
+                            (int) (bitmap.getWidth() * mReducedScale),
+                            (int) (bitmap.getHeight() * mReducedScale), true /* filter */);
             try {
                 FileOutputStream reducedFos = new FileOutputStream(reducedFile);
                 reduced.compress(JPEG, QUALITY, reducedFos);
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/WindowAnimator.java b/services/core/java/com/android/server/wm/WindowAnimator.java
index b8a0739..b8db98b 100644
--- a/services/core/java/com/android/server/wm/WindowAnimator.java
+++ b/services/core/java/com/android/server/wm/WindowAnimator.java
@@ -167,13 +167,11 @@
                             screenRotationAnimation.kill();
                             displayAnimator.mScreenRotationAnimation = null;
 
-                            //TODO (multidisplay): Accessibility supported only for the default
                             // display.
-                            if (accessibilityController != null && dc.isDefaultDisplay) {
+                            if (accessibilityController != null) {
                                 // We just finished rotation animation which means we did not
                                 // announce the rotation and waited for it to end, announce now.
-                                accessibilityController.onRotationChangedLocked(
-                                        mService.getDefaultDisplayContentLocked());
+                                accessibilityController.onRotationChangedLocked(dc);
                             }
                         }
                     }
@@ -197,9 +195,8 @@
                         screenRotationAnimation.updateSurfaces(mTransaction);
                     }
                     orAnimating(dc.getDockedDividerController().animate(mCurrentTime));
-                    //TODO (multidisplay): Magnification is supported only for the default display.
-                    if (accessibilityController != null && dc.isDefaultDisplay) {
-                        accessibilityController.drawMagnifiedRegionBorderIfNeededLocked();
+                    if (accessibilityController != null) {
+                        accessibilityController.drawMagnifiedRegionBorderIfNeededLocked(displayId);
                     }
                 }
 
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 1691dc0..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;
         }
 
@@ -212,34 +210,40 @@
      * and has access to the raw window data while the accessibility layer serves
      * as a controller.
      *
+     * @param displayId The logical display id.
      * @param callbacks The callbacks to invoke.
+     * @return {@code false} if display id is not valid.
      */
-    public abstract void setMagnificationCallbacks(@Nullable MagnificationCallbacks callbacks);
+    public abstract boolean setMagnificationCallbacks(int displayId,
+            @Nullable MagnificationCallbacks callbacks);
 
     /**
      * Set by the accessibility layer to specify the magnification and panning to
      * be applied to all windows that should be magnified.
      *
+     * @param displayId The logical display id.
      * @param spec The MagnficationSpec to set.
      *
-     * @see #setMagnificationCallbacks(MagnificationCallbacks)
+     * @see #setMagnificationCallbacks(int, MagnificationCallbacks)
      */
-    public abstract void setMagnificationSpec(MagnificationSpec spec);
+    public abstract void setMagnificationSpec(int displayId, MagnificationSpec spec);
 
     /**
      * Set by the accessibility framework to indicate whether the magnifiable regions of the display
      * should be shown.
      *
+     * @param displayId The logical display id.
      * @param show {@code true} to show magnifiable region bounds, {@code false} to hide
      */
-    public abstract void setForceShowMagnifiableBounds(boolean show);
+    public abstract void setForceShowMagnifiableBounds(int displayId, boolean show);
 
     /**
      * Obtains the magnification regions.
      *
+     * @param displayId The logical display id.
      * @param magnificationRegion the current magnification region
      */
-    public abstract void getMagnificationRegion(@NonNull Region magnificationRegion);
+    public abstract void getMagnificationRegion(int displayId, @NonNull Region magnificationRegion);
 
     /**
      * Gets the magnification and translation applied to a window given its token.
@@ -251,7 +255,7 @@
      *
      * @return The magnification spec for the window.
      *
-     * @see #setMagnificationCallbacks(MagnificationCallbacks)
+     * @see #setMagnificationCallbacks(int, MagnificationCallbacks)
      */
     public abstract MagnificationSpec getCompatibleMagnificationSpecForWindow(
             IBinder windowToken);
diff --git a/services/core/java/com/android/server/wm/WindowManagerService.java b/services/core/java/com/android/server/wm/WindowManagerService.java
index fda7a85..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;
@@ -433,11 +432,12 @@
     final long mDrawLockTimeoutMillis;
     final boolean mAllowAnimationsInLowPowerMode;
 
+    // TODO(b/122671846) Remove the flag below in favor of isLowRam once feature is stable
     /**
      * Use very low resolution task snapshots. Replaces task snapshot starting windows with
      * splashscreen starting windows. Used on low RAM devices to save memory.
      */
-    final boolean mLowRamTaskSnapshots;
+    final boolean mLowRamTaskSnapshotsAndRecents;
 
     final boolean mAllowBootMessages;
 
@@ -856,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;
@@ -955,7 +955,7 @@
                 com.android.internal.R.bool.config_disableTransitionAnimation);
         mPerDisplayFocusEnabled = context.getResources().getBoolean(
                 com.android.internal.R.bool.config_perDisplayFocusEnabled);
-        mLowRamTaskSnapshots = context.getResources().getBoolean(
+        mLowRamTaskSnapshotsAndRecents = context.getResources().getBoolean(
                 com.android.internal.R.bool.config_lowRamTaskSnapshotsAndRecents);
         mInputManager = inputManager; // Must be before createDisplayContentLocked.
         mDisplayManagerInternal = LocalServices.getService(DisplayManagerInternal.class);
@@ -1845,9 +1845,9 @@
         synchronized (mGlobalLock) {
             if (mAccessibilityController != null) {
                 WindowState window = mWindowMap.get(token);
-                //TODO (multidisplay): Magnification is supported only for the default display.
-                if (window != null && window.getDisplayId() == DEFAULT_DISPLAY) {
-                    mAccessibilityController.onRectangleOnScreenRequestedLocked(rectangle);
+                if (window != null) {
+                    mAccessibilityController.onRectangleOnScreenRequestedLocked(
+                            window.getDisplayId(), rectangle);
                 }
             }
         }
@@ -2236,8 +2236,7 @@
             win.mDestroying = true;
             win.destroySurface(false, stopped);
         }
-        // TODO(multidisplay): Magnification is supported only for the default display.
-        if (mAccessibilityController != null && win.getDisplayId() == DEFAULT_DISPLAY) {
+        if (mAccessibilityController != null) {
             mAccessibilityController.onWindowTransitionLocked(win, transit);
         }
 
@@ -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);
                 }
@@ -6822,10 +6786,10 @@
         }
 
         @Override
-        public void setMagnificationSpec(MagnificationSpec spec) {
+        public void setMagnificationSpec(int displayId, MagnificationSpec spec) {
             synchronized (mGlobalLock) {
                 if (mAccessibilityController != null) {
-                    mAccessibilityController.setMagnificationSpecLocked(spec);
+                    mAccessibilityController.setMagnificationSpecLocked(displayId, spec);
                 } else {
                     throw new IllegalStateException("Magnification callbacks not set!");
                 }
@@ -6836,10 +6800,10 @@
         }
 
         @Override
-        public void setForceShowMagnifiableBounds(boolean show) {
+        public void setForceShowMagnifiableBounds(int displayId, boolean show) {
             synchronized (mGlobalLock) {
                 if (mAccessibilityController != null) {
-                    mAccessibilityController.setForceShowMagnifiableBoundsLocked(show);
+                    mAccessibilityController.setForceShowMagnifiableBoundsLocked(displayId, show);
                 } else {
                     throw new IllegalStateException("Magnification callbacks not set!");
                 }
@@ -6847,10 +6811,11 @@
         }
 
         @Override
-        public void getMagnificationRegion(@NonNull Region magnificationRegion) {
+        public void getMagnificationRegion(int displayId, @NonNull Region magnificationRegion) {
             synchronized (mGlobalLock) {
                 if (mAccessibilityController != null) {
-                    mAccessibilityController.getMagnificationRegionLocked(magnificationRegion);
+                    mAccessibilityController.getMagnificationRegionLocked(displayId,
+                            magnificationRegion);
                 } else {
                     throw new IllegalStateException("Magnification callbacks not set!");
                 }
@@ -6878,16 +6843,19 @@
         }
 
         @Override
-        public void setMagnificationCallbacks(@Nullable MagnificationCallbacks callbacks) {
+        public boolean setMagnificationCallbacks(int displayId,
+                @Nullable MagnificationCallbacks callbacks) {
             synchronized (mGlobalLock) {
                 if (mAccessibilityController == null) {
                     mAccessibilityController = new AccessibilityController(
                             WindowManagerService.this);
                 }
-                mAccessibilityController.setMagnificationCallbacksLocked(callbacks);
+                boolean result = mAccessibilityController.setMagnificationCallbacksLocked(
+                        displayId, callbacks);
                 if (!mAccessibilityController.hasCallbacksLocked()) {
                     mAccessibilityController = null;
                 }
+                return result;
             }
         }
 
@@ -7266,8 +7234,12 @@
         }, false /* traverseTopToBottom */);
     }
 
-    public void applyMagnificationSpec(MagnificationSpec spec) {
-        getDefaultDisplayContentLocked().applyMagnificationSpec(spec);
+    /** Called from Accessibility Controller to apply magnification spec */
+    public void applyMagnificationSpecLocked(int displayId, MagnificationSpec spec) {
+        final DisplayContent displayContent = mRoot.getDisplayContent(displayId);
+        if (displayContent != null) {
+            displayContent.applyMagnificationSpec(spec);
+        }
     }
 
     SurfaceControl.Builder makeSurfaceBuilder(SurfaceSession s) {
@@ -7326,7 +7298,7 @@
     }
 
     @Override
-    public void reparentDisplayContent(int displayId, IBinder surfaceControlHandle) {
+    public void reparentDisplayContent(int displayId, SurfaceControl sc) {
         final Display display = mDisplayManager.getDisplay(displayId);
         if (display == null) {
             throw new IllegalArgumentException(
@@ -7343,7 +7315,7 @@
             long token = Binder.clearCallingIdentity();
             try {
                 DisplayContent displayContent = getDisplayContentOrCreate(displayId, null);
-                displayContent.reparentDisplayContent(surfaceControlHandle);
+                displayContent.reparentDisplayContent(sc);
             } finally {
                 Binder.restoreCallingIdentity(token);
             }
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 8f86c00..4f12010 100644
--- a/services/core/java/com/android/server/wm/WindowState.java
+++ b/services/core/java/com/android/server/wm/WindowState.java
@@ -1607,8 +1607,7 @@
                         mWmService.mAccessibilityController;
                 final int winTransit = TRANSIT_EXIT;
                 mWinAnimator.applyAnimationLocked(winTransit, false /* isEntrance */);
-                //TODO (multidisplay): Magnification is supported only for the default
-                if (accessibilityController != null && getDisplayId() == DEFAULT_DISPLAY) {
+                if (accessibilityController != null) {
                     accessibilityController.onWindowTransitionLocked(this, winTransit);
                 }
             }
@@ -1625,8 +1624,7 @@
 
         if (isVisibleNow()) {
             mWinAnimator.applyAnimationLocked(TRANSIT_EXIT, false);
-            //TODO (multidisplay): Magnification is supported only for the default
-            if (mWmService.mAccessibilityController != null && isDefaultDisplay()) {
+            if (mWmService.mAccessibilityController != null) {
                 mWmService.mAccessibilityController.onWindowTransitionLocked(this, TRANSIT_EXIT);
             }
             changed = true;
@@ -1915,9 +1913,7 @@
                         setDisplayLayoutNeeded();
                         mWmService.requestTraversal();
                     }
-                    //TODO (multidisplay): Magnification is supported only for the default display.
-                    if (mWmService.mAccessibilityController != null
-                            && displayId == DEFAULT_DISPLAY) {
+                    if (mWmService.mAccessibilityController != null) {
                         mWmService.mAccessibilityController.onWindowTransitionLocked(this, transit);
                     }
                 }
@@ -4371,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);
@@ -4384,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/WindowStateAnimator.java b/services/core/java/com/android/server/wm/WindowStateAnimator.java
index fb5c556..6b4d6d2 100644
--- a/services/core/java/com/android/server/wm/WindowStateAnimator.java
+++ b/services/core/java/com/android/server/wm/WindowStateAnimator.java
@@ -16,7 +16,6 @@
 
 package com.android.server.wm;
 
-import static android.view.Display.DEFAULT_DISPLAY;
 import static android.view.WindowManager.LayoutParams.FLAG_HARDWARE_ACCELERATED;
 import static android.view.WindowManager.LayoutParams.FLAG_SCALED;
 import static android.view.WindowManager.LayoutParams.PRIVATE_FLAG_IS_ROUNDED_CORNERS_OVERLAY;
@@ -1308,9 +1307,7 @@
             transit = WindowManagerPolicy.TRANSIT_SHOW;
         }
         applyAnimationLocked(transit, true);
-        //TODO (multidisplay): Magnification is supported only for the default display.
-        if (mService.mAccessibilityController != null
-                && mWin.getDisplayId() == DEFAULT_DISPLAY) {
+        if (mService.mAccessibilityController != null) {
             mService.mAccessibilityController.onWindowTransitionLocked(mWin, transit);
         }
     }
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/Android.bp b/services/core/jni/Android.bp
index fb00aeb..3729eaf 100644
--- a/services/core/jni/Android.bp
+++ b/services/core/jni/Android.bp
@@ -53,6 +53,7 @@
     ],
 
     include_dirs: [
+        "bionic/libc/private",
         "frameworks/base/libs",
         "frameworks/native/services",
         "system/gatekeeper/include",
diff --git a/services/core/jni/com_android_server_SystemServer.cpp b/services/core/jni/com_android_server_SystemServer.cpp
index dc0d53b..159a496 100644
--- a/services/core/jni/com_android_server_SystemServer.cpp
+++ b/services/core/jni/com_android_server_SystemServer.cpp
@@ -24,6 +24,8 @@
 #include <sensorservice/SensorService.h>
 #include <sensorservicehidl/SensorManager.h>
 
+#include <bionic_malloc.h>
+
 #include <cutils/properties.h>
 #include <utils/Log.h>
 #include <utils/misc.h>
@@ -64,6 +66,11 @@
     ALOGE_IF(err != OK, "Cannot register %s: %d", ISchedulingPolicyService::descriptor, err);
 }
 
+static void android_server_SystemServer_initZygoteChildHeapProfiling(JNIEnv* /* env */,
+                                                                     jobject /* clazz */) {
+   android_mallopt(M_INIT_ZYGOTE_CHILD_PROFILING, nullptr, 0);
+}
+
 /*
  * JNI registration.
  */
@@ -71,6 +78,8 @@
     /* name, signature, funcPtr */
     { "startSensorService", "()V", (void*) android_server_SystemServer_startSensorService },
     { "startHidlServices", "()V", (void*) android_server_SystemServer_startHidlServices },
+    { "initZygoteChildHeapProfiling", "()V",
+      (void*) android_server_SystemServer_initZygoteChildHeapProfiling },
 };
 
 int register_android_server_SystemServer(JNIEnv* env)
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 0977323..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);
+            }
         }
     }
 
@@ -4223,7 +4232,7 @@
 
     @Override
     public void setPasswordHistoryLength(ComponentName who, int length, boolean parent) {
-        if (!mHasFeature) {
+        if (!mHasFeature || !mLockPatternUtils.hasSecureLockScreen()) {
             return;
         }
         Preconditions.checkNotNull(who, "ComponentName is null");
@@ -4246,13 +4255,16 @@
 
     @Override
     public int getPasswordHistoryLength(ComponentName who, int userHandle, boolean parent) {
+        if (!mLockPatternUtils.hasSecureLockScreen()) {
+            return 0;
+        }
         return getStrictestPasswordRequirement(who, userHandle, parent,
                 admin -> admin.passwordHistoryLength, PASSWORD_QUALITY_UNSPECIFIED);
     }
 
     @Override
     public void setPasswordExpirationTimeout(ComponentName who, long timeout, boolean parent) {
-        if (!mHasFeature) {
+        if (!mHasFeature || !mLockPatternUtils.hasSecureLockScreen()) {
             return;
         }
         Preconditions.checkNotNull(who, "ComponentName is null");
@@ -4288,7 +4300,7 @@
      */
     @Override
     public long getPasswordExpirationTimeout(ComponentName who, int userHandle, boolean parent) {
-        if (!mHasFeature) {
+        if (!mHasFeature || !mLockPatternUtils.hasSecureLockScreen()) {
             return 0L;
         }
         enforceFullCrossUsersPermission(userHandle);
@@ -4423,7 +4435,7 @@
 
     @Override
     public long getPasswordExpiration(ComponentName who, int userHandle, boolean parent) {
-        if (!mHasFeature) {
+        if (!mHasFeature || !mLockPatternUtils.hasSecureLockScreen()) {
             return 0L;
         }
         enforceFullCrossUsersPermission(userHandle);
@@ -4770,6 +4782,9 @@
 
     @Override
     public int getCurrentFailedPasswordAttempts(int userHandle, boolean parent) {
+        if (!mLockPatternUtils.hasSecureLockScreen()) {
+            return 0;
+        }
         enforceFullCrossUsersPermission(userHandle);
         synchronized (getLockObject()) {
             if (!isCallerWithSystemUid()) {
@@ -4789,7 +4804,7 @@
 
     @Override
     public void setMaximumFailedPasswordsForWipe(ComponentName who, int num, boolean parent) {
-        if (!mHasFeature) {
+        if (!mHasFeature || !mLockPatternUtils.hasSecureLockScreen()) {
             return;
         }
         Preconditions.checkNotNull(who, "ComponentName is null");
@@ -4815,7 +4830,7 @@
 
     @Override
     public int getMaximumFailedPasswordsForWipe(ComponentName who, int userHandle, boolean parent) {
-        if (!mHasFeature) {
+        if (!mHasFeature || !mLockPatternUtils.hasSecureLockScreen()) {
             return 0;
         }
         enforceFullCrossUsersPermission(userHandle);
@@ -4829,7 +4844,7 @@
 
     @Override
     public int getProfileWithMinimumFailedPasswordsForWipe(int userHandle, boolean parent) {
-        if (!mHasFeature) {
+        if (!mHasFeature || !mLockPatternUtils.hasSecureLockScreen()) {
             return UserHandle.USER_NULL;
         }
         enforceFullCrossUsersPermission(userHandle);
@@ -4910,6 +4925,11 @@
     }
     @Override
     public boolean resetPassword(String passwordOrNull, int flags) throws RemoteException {
+        if (!mLockPatternUtils.hasSecureLockScreen()) {
+            Slog.w(LOG_TAG, "Cannot reset password when the device has no lock screen");
+            return false;
+        }
+
         final int callingUid = mInjector.binderGetCallingUid();
         final int userHandle = mInjector.userHandleGetCallingUserId();
 
@@ -5252,7 +5272,7 @@
     @Override
     public void setRequiredStrongAuthTimeout(ComponentName who, long timeoutMs,
             boolean parent) {
-        if (!mHasFeature) {
+        if (!mHasFeature || !mLockPatternUtils.hasSecureLockScreen()) {
             return;
         }
         Preconditions.checkNotNull(who, "ComponentName is null");
@@ -5285,7 +5305,7 @@
      */
     @Override
     public long getRequiredStrongAuthTimeout(ComponentName who, int userId, boolean parent) {
-        if (!mHasFeature) {
+        if (!mHasFeature || !mLockPatternUtils.hasSecureLockScreen()) {
             return DevicePolicyManager.DEFAULT_STRONG_AUTH_TIMEOUT_MS;
         }
         enforceFullCrossUsersPermission(userId);
@@ -6351,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();
@@ -6362,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) {
@@ -6377,6 +6397,7 @@
         if (!mHasFeature) {
             return;
         }
+        Preconditions.checkStringNotEmpty(wipeReasonForUser, "wipeReasonForUser is null or empty");
         enforceFullCrossUsersPermission(mInjector.userHandleGetCallingUserId());
 
         final ActiveAdmin admin;
@@ -6436,7 +6457,7 @@
                         internalReason,
                         /*wipeEuicc=*/ (flags & WIPE_EUICC) != 0);
             } else {
-                forceWipeUser(userId, wipeReasonForUser);
+                forceWipeUser(userId, wipeReasonForUser, (flags & WIPE_SILENTLY) != 0);
             }
         } finally {
             mInjector.binderRestoreCallingIdentity(ident);
@@ -6494,7 +6515,7 @@
      */
     @Override
     public void setActivePasswordState(PasswordMetrics metrics, int userHandle) {
-        if (!mHasFeature) {
+        if (!mHasFeature || !mLockPatternUtils.hasSecureLockScreen()) {
             return;
         }
         enforceFullCrossUsersPermission(userHandle);
@@ -6514,7 +6535,7 @@
 
     @Override
     public void reportPasswordChanged(@UserIdInt int userId) {
-        if (!mHasFeature) {
+        if (!mHasFeature || !mLockPatternUtils.hasSecureLockScreen()) {
             return;
         }
         enforceFullCrossUsersPermission(userId);
@@ -7656,7 +7677,18 @@
             }
 
             // Shutting down backup manager service permanently.
-            toggleBackupServiceActive(UserHandle.USER_SYSTEM, /* makeActive= */ false);
+            long ident = mInjector.binderClearCallingIdentity();
+            try {
+                if (mInjector.getIBackupManager() != null) {
+                    mInjector.getIBackupManager()
+                            .setBackupServiceActive(UserHandle.USER_SYSTEM, false);
+                }
+            } catch (RemoteException e) {
+                throw new IllegalStateException("Failed deactivating backup service.", e);
+            } finally {
+                mInjector.binderRestoreCallingIdentity(ident);
+            }
+
             if (isAdb()) {
                 // Log device owner provisioning was started using adb.
                 MetricsLogger.action(mContext, PROVISIONING_ENTRY_POINT_ADB, LOG_TAG_DEVICE_OWNER);
@@ -7684,7 +7716,7 @@
                 saveUserRestrictionsLocked(userId);
             }
 
-            long ident = mInjector.binderClearCallingIdentity();
+            ident = mInjector.binderClearCallingIdentity();
             try {
                 // TODO Send to system too?
                 sendOwnerChangedBroadcast(DevicePolicyManager.ACTION_DEVICE_OWNER_CHANGED, userId);
@@ -7941,9 +7973,6 @@
                         .write();
             }
 
-            // Shutting down backup manager service permanently.
-            toggleBackupServiceActive(userHandle, /* makeActive= */ false);
-
             mOwners.setProfileOwner(who, ownerName, userHandle);
             mOwners.writeProfileOwner(userHandle);
             Slog.i(LOG_TAG, "Profile owner set: " + who + " on user " + userHandle);
@@ -7967,24 +7996,6 @@
         }
     }
 
-
-    private void toggleBackupServiceActive(int userId, boolean makeActive) {
-        // Shutting down backup manager service permanently.
-        enforceUserUnlocked(userId);
-        long ident = mInjector.binderClearCallingIdentity();
-        try {
-            if (mInjector.getIBackupManager() != null) {
-                mInjector.getIBackupManager()
-                        .setBackupServiceActive(userId, makeActive);
-            }
-        } catch (RemoteException e) {
-            throw new IllegalStateException("Failed deactivating backup service.", e);
-        } finally {
-            mInjector.binderRestoreCallingIdentity(ident);
-        }
-
-    }
-
     @Override
     public void clearProfileOwner(ComponentName who) {
         if (!mHasFeature) {
@@ -8800,7 +8811,7 @@
     @Override
     public void setTrustAgentConfiguration(ComponentName admin, ComponentName agent,
             PersistableBundle args, boolean parent) {
-        if (!mHasFeature) {
+        if (!mHasFeature || !mLockPatternUtils.hasSecureLockScreen()) {
             return;
         }
         Preconditions.checkNotNull(admin, "admin is null");
@@ -8817,7 +8828,7 @@
     @Override
     public List<PersistableBundle> getTrustAgentConfiguration(ComponentName admin,
             ComponentName agent, int userHandle, boolean parent) {
-        if (!mHasFeature) {
+        if (!mHasFeature || !mLockPatternUtils.hasSecureLockScreen()) {
             return null;
         }
         Preconditions.checkNotNull(agent, "agent null");
@@ -12737,9 +12748,22 @@
             return;
         }
         Preconditions.checkNotNull(admin);
-        enforceProfileOrDeviceOwner(admin);
-        int userId = mInjector.userHandleGetCallingUserId();
-        toggleBackupServiceActive(userId, enabled);
+        synchronized (getLockObject()) {
+            getActiveAdminForCallerLocked(admin, DeviceAdminInfo.USES_POLICY_DEVICE_OWNER);
+        }
+
+        final long ident = mInjector.binderClearCallingIdentity();
+        try {
+            IBackupManager ibm = mInjector.getIBackupManager();
+            if (ibm != null) {
+                ibm.setBackupServiceActive(UserHandle.USER_SYSTEM, enabled);
+            }
+        } catch (RemoteException e) {
+            throw new IllegalStateException(
+                "Failed " + (enabled ? "" : "de") + "activating backup service.", e);
+        } finally {
+            mInjector.binderRestoreCallingIdentity(ident);
+        }
     }
 
     @Override
@@ -12748,13 +12772,11 @@
         if (!mHasFeature) {
             return true;
         }
-
-        enforceProfileOrDeviceOwner(admin);
         synchronized (getLockObject()) {
             try {
+                getActiveAdminForCallerLocked(admin, DeviceAdminInfo.USES_POLICY_DEVICE_OWNER);
                 IBackupManager ibm = mInjector.getIBackupManager();
-                return ibm != null && ibm.isBackupServiceActive(
-                    mInjector.userHandleGetCallingUserId());
+                return ibm != null && ibm.isBackupServiceActive(UserHandle.USER_SYSTEM);
             } catch (RemoteException e) {
                 throw new IllegalStateException("Failed requesting backup service state.", e);
             }
@@ -13215,7 +13237,7 @@
 
     @Override
     public boolean setResetPasswordToken(ComponentName admin, byte[] token) {
-        if (!mHasFeature) {
+        if (!mHasFeature || !mLockPatternUtils.hasSecureLockScreen()) {
             return false;
         }
         if (token == null || token.length < 32) {
@@ -13243,7 +13265,7 @@
 
     @Override
     public boolean clearResetPasswordToken(ComponentName admin) {
-        if (!mHasFeature) {
+        if (!mHasFeature || !mLockPatternUtils.hasSecureLockScreen()) {
             return false;
         }
         synchronized (getLockObject()) {
@@ -13269,6 +13291,9 @@
 
     @Override
     public boolean isResetPasswordTokenActive(ComponentName admin) {
+        if (!mHasFeature || !mLockPatternUtils.hasSecureLockScreen()) {
+            return false;
+        }
         synchronized (getLockObject()) {
             final int userHandle = mInjector.userHandleGetCallingUserId();
             getActiveAdminForCallerLocked(admin, DeviceAdminInfo.USES_POLICY_PROFILE_OWNER);
@@ -13290,6 +13315,9 @@
     @Override
     public boolean resetPasswordWithToken(ComponentName admin, String passwordOrNull, byte[] token,
             int flags) {
+        if (!mHasFeature || !mLockPatternUtils.hasSecureLockScreen()) {
+            return false;
+        }
         Preconditions.checkNotNull(token);
         synchronized (getLockObject()) {
             final int userHandle = mInjector.userHandleGetCallingUserId();
@@ -13970,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();
@@ -14028,7 +14028,7 @@
         synchronized (getLockObject()) {
             final ActiveAdmin admin = getActiveAdminForCallerLocked(
                     who, DeviceAdminInfo.USES_POLICY_PROFILE_OWNER);
-            return new ArrayList<String>(admin.mCrossProfileCalendarPackages);
+            return admin.mCrossProfileCalendarPackages;
         }
     }
 
@@ -14044,6 +14044,9 @@
         synchronized (getLockObject()) {
             final ActiveAdmin admin = getProfileOwnerAdminLocked(userHandle);
             if (admin != null) {
+                if (admin.mCrossProfileCalendarPackages == null) {
+                    return true;
+                }
                 return admin.mCrossProfileCalendarPackages.contains(packageName);
             }
         }
@@ -14059,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 238f077..e99dd4f 100644
--- a/services/ipmemorystore/java/com/android/server/net/ipmemorystore/IpMemoryStoreDatabase.java
+++ b/services/ipmemorystore/java/com/android/server/net/ipmemorystore/IpMemoryStoreDatabase.java
@@ -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.
@@ -21,9 +21,12 @@
 import android.content.ContentValues;
 import android.content.Context;
 import android.database.Cursor;
+import android.database.sqlite.SQLiteCursor;
+import android.database.sqlite.SQLiteCursorDriver;
 import android.database.sqlite.SQLiteDatabase;
 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;
@@ -35,6 +38,7 @@
 import java.net.UnknownHostException;
 import java.util.ArrayList;
 import java.util.List;
+import java.util.StringJoiner;
 
 /**
  * Encapsulating class for using the SQLite database backing the memory store.
@@ -46,6 +50,9 @@
  */
 public class IpMemoryStoreDatabase {
     private static final String TAG = IpMemoryStoreDatabase.class.getSimpleName();
+    // A pair of NetworkAttributes objects is group-close if the confidence that they are
+    // the same is above this cutoff. See NetworkAttributes and SameL3NetworkResponse.
+    private static final float GROUPCLOSE_CONFIDENCE = 0.5f;
 
     /**
      * Contract class for the Network Attributes table.
@@ -134,12 +141,14 @@
         }
 
         /** Called when the database is created */
+        @Override
         public void onCreate(@NonNull final SQLiteDatabase db) {
             db.execSQL(NetworkAttributesContract.CREATE_TABLE);
             db.execSQL(PrivateDataContract.CREATE_TABLE);
         }
 
         /** Called when the database is upgraded */
+        @Override
         public void onUpgrade(@NonNull final SQLiteDatabase db, final int oldVersion,
                 final int newVersion) {
             // No upgrade supported yet.
@@ -149,6 +158,7 @@
         }
 
         /** Called when the database is downgraded */
+        @Override
         public void onDowngrade(@NonNull final SQLiteDatabase db, final int oldVersion,
                 final int newVersion) {
             // Downgrades always nuke all data and recreate an empty table.
@@ -184,30 +194,35 @@
         return addresses;
     }
 
+    @NonNull
+    private static ContentValues toContentValues(@Nullable final NetworkAttributes attributes) {
+        final ContentValues values = new ContentValues();
+        if (null == attributes) return values;
+        if (null != attributes.assignedV4Address) {
+            values.put(NetworkAttributesContract.COLNAME_ASSIGNEDV4ADDRESS,
+                    NetworkUtils.inet4AddressToIntHTH(attributes.assignedV4Address));
+        }
+        if (null != attributes.groupHint) {
+            values.put(NetworkAttributesContract.COLNAME_GROUPHINT, attributes.groupHint);
+        }
+        if (null != attributes.dnsAddresses) {
+            values.put(NetworkAttributesContract.COLNAME_DNSADDRESSES,
+                    encodeAddressList(attributes.dnsAddresses));
+        }
+        if (null != attributes.mtu) {
+            values.put(NetworkAttributesContract.COLNAME_MTU, attributes.mtu);
+        }
+        return values;
+    }
+
     // Convert a NetworkAttributes object to content values to store them in a table compliant
     // with the contract defined in NetworkAttributesContract.
     @NonNull
     private static ContentValues toContentValues(@NonNull final String key,
             @Nullable final NetworkAttributes attributes, final long expiry) {
-        final ContentValues values = new ContentValues();
+        final ContentValues values = toContentValues(attributes);
         values.put(NetworkAttributesContract.COLNAME_L2KEY, key);
         values.put(NetworkAttributesContract.COLNAME_EXPIRYDATE, expiry);
-        if (null != attributes) {
-            if (null != attributes.assignedV4Address) {
-                values.put(NetworkAttributesContract.COLNAME_ASSIGNEDV4ADDRESS,
-                        NetworkUtils.inet4AddressToIntHTH(attributes.assignedV4Address));
-            }
-            if (null != attributes.groupHint) {
-                values.put(NetworkAttributesContract.COLNAME_GROUPHINT, attributes.groupHint);
-            }
-            if (null != attributes.dnsAddresses) {
-                values.put(NetworkAttributesContract.COLNAME_DNSADDRESSES,
-                        encodeAddressList(attributes.dnsAddresses));
-            }
-            if (null != attributes.mtu) {
-                values.put(NetworkAttributesContract.COLNAME_MTU, attributes.mtu);
-            }
-        }
         return values;
     }
 
@@ -225,6 +240,32 @@
         return values;
     }
 
+    @Nullable
+    private static NetworkAttributes readNetworkAttributesLine(@NonNull final Cursor cursor) {
+        // Make sure the data hasn't expired
+        final long expiry = getLong(cursor, NetworkAttributesContract.COLNAME_EXPIRYDATE, -1L);
+        if (expiry < System.currentTimeMillis()) return null;
+
+        final NetworkAttributes.Builder builder = new NetworkAttributes.Builder();
+        final int assignedV4AddressInt = getInt(cursor,
+                NetworkAttributesContract.COLNAME_ASSIGNEDV4ADDRESS, 0);
+        final String groupHint = getString(cursor, NetworkAttributesContract.COLNAME_GROUPHINT);
+        final byte[] dnsAddressesBlob =
+                getBlob(cursor, NetworkAttributesContract.COLNAME_DNSADDRESSES);
+        final int mtu = getInt(cursor, NetworkAttributesContract.COLNAME_MTU, -1);
+        if (0 != assignedV4AddressInt) {
+            builder.setAssignedV4Address(NetworkUtils.intToInet4AddressHTH(assignedV4AddressInt));
+        }
+        builder.setGroupHint(groupHint);
+        if (null != dnsAddressesBlob) {
+            builder.setDnsAddresses(decodeAddressList(dnsAddressesBlob));
+        }
+        if (mtu >= 0) {
+            builder.setMtu(mtu);
+        }
+        return builder.build();
+    }
+
     private static final String[] EXPIRY_COLUMN = new String[] {
         NetworkAttributesContract.COLNAME_EXPIRYDATE
     };
@@ -247,7 +288,9 @@
         // result here. 0 results means the key was not found.
         if (cursor.getCount() != 1) return EXPIRY_ERROR;
         cursor.moveToFirst();
-        return cursor.getLong(0); // index in the EXPIRY_COLUMN array
+        final long result = cursor.getLong(0); // index in the EXPIRY_COLUMN array
+        cursor.close();
+        return result;
     }
 
     static final int RELEVANCE_ERROR = -1; // Legal values for relevance are positive
@@ -308,30 +351,9 @@
         // result here. 0 results means the key was not found.
         if (cursor.getCount() != 1) return null;
         cursor.moveToFirst();
-
-        // Make sure the data hasn't expired
-        final long expiry = cursor.getLong(
-                cursor.getColumnIndexOrThrow(NetworkAttributesContract.COLNAME_EXPIRYDATE));
-        if (expiry < System.currentTimeMillis()) return null;
-
-        final NetworkAttributes.Builder builder = new NetworkAttributes.Builder();
-        final int assignedV4AddressInt = getInt(cursor,
-                NetworkAttributesContract.COLNAME_ASSIGNEDV4ADDRESS, 0);
-        final String groupHint = getString(cursor, NetworkAttributesContract.COLNAME_GROUPHINT);
-        final byte[] dnsAddressesBlob =
-                getBlob(cursor, NetworkAttributesContract.COLNAME_DNSADDRESSES);
-        final int mtu = getInt(cursor, NetworkAttributesContract.COLNAME_MTU, -1);
-        if (0 != assignedV4AddressInt) {
-            builder.setAssignedV4Address(NetworkUtils.intToInet4AddressHTH(assignedV4AddressInt));
-        }
-        builder.setGroupHint(groupHint);
-        if (null != dnsAddressesBlob) {
-            builder.setDnsAddresses(decodeAddressList(dnsAddressesBlob));
-        }
-        if (mtu >= 0) {
-            builder.setMtu(mtu);
-        }
-        return builder.build();
+        final NetworkAttributes attributes = readNetworkAttributesLine(cursor);
+        cursor.close();
+        return attributes;
     }
 
     private static final String[] DATA_COLUMN = new String[] {
@@ -353,20 +375,139 @@
         // get more than one result here. 0 results means the key was not found.
         if (cursor.getCount() != 1) return null;
         cursor.moveToFirst();
-        return cursor.getBlob(0); // index in the DATA_COLUMN array
+        final byte[] result = cursor.getBlob(0); // index in the DATA_COLUMN array
+        cursor.close();
+        return result;
+    }
+
+    /**
+     * The following is a horrible hack that is necessary because the Android SQLite API does not
+     * have a way to query a binary blob. This, almost certainly, is an overlook.
+     *
+     * The Android SQLite API has two family of methods : one for query that returns data, and
+     * one for more general SQL statements that can execute any statement but may not return
+     * anything. All the query methods, however, take only String[] for the arguments.
+     *
+     * In principle it is simple to write a function that will encode the binary blob in the
+     * way SQLite expects it. However, because the API forces the argument to be coerced into a
+     * String, the SQLiteQuery object generated by the default query methods will bind all
+     * arguments as Strings and SQL will *sanitize* them. This works okay for numeric types,
+     * but the format for blobs is x'<hex string>'. Note the presence of quotes, which will
+     * be sanitized, changing the contents of the field, and the query will fail to match the
+     * blob.
+     *
+     * As far as I can tell, there are two possible ways around this problem. The first one
+     * is to put the data in the query string and eschew it being an argument. This would
+     * require doing the sanitizing by hand. The other is to call bindBlob directly on the
+     * generated SQLiteQuery object, which not only is a lot less dangerous than rolling out
+     * sanitizing, but also will do the right thing if the underlying format ever changes.
+     *
+     * But none of the methods that take an SQLiteQuery object can return data ; this *must*
+     * be called with SQLiteDatabase#query. This object is not accessible from outside.
+     * However, there is a #query version that accepts a CursorFactory and this is pretty
+     * straightforward to implement as all the arguments are coming in and the SQLiteCursor
+     * class is public API.
+     * With this, it's possible to intercept the SQLiteQuery object, and assuming the args
+     * are available, to bind them directly and work around the API's oblivious coercion into
+     * Strings.
+     *
+     * This is really sad, but I don't see another way of having this work than this or the
+     * hand-rolled sanitizing, and this is the lesser evil.
+     */
+    private static class CustomCursorFactory implements SQLiteDatabase.CursorFactory {
+        @NonNull
+        private final ArrayList<Object> mArgs;
+        CustomCursorFactory(@NonNull final ArrayList<Object> args) {
+            mArgs = args;
+        }
+        @Override
+        public Cursor newCursor(final SQLiteDatabase db, final SQLiteCursorDriver masterQuery,
+                final String editTable,
+                final SQLiteQuery query) {
+            int index = 1; // bind is 1-indexed
+            for (final Object arg : mArgs) {
+                if (arg instanceof String) {
+                    query.bindString(index++, (String) arg);
+                } else if (arg instanceof Long) {
+                    query.bindLong(index++, (Long) arg);
+                } else if (arg instanceof Integer) {
+                    query.bindLong(index++, Long.valueOf((Integer) arg));
+                } else if (arg instanceof byte[]) {
+                    query.bindBlob(index++, (byte[]) arg);
+                } else {
+                    throw new IllegalStateException("Unsupported type CustomCursorFactory "
+                            + arg.getClass().toString());
+                }
+            }
+            return new SQLiteCursor(masterQuery, editTable, query);
+        }
+    }
+
+    // Returns the l2key of the closest match, if and only if it matches
+    // closely enough (as determined by group-closeness).
+    @Nullable
+    static String findClosestAttributes(@NonNull final SQLiteDatabase db,
+            @NonNull final NetworkAttributes attr) {
+        if (attr.isEmpty()) return null;
+        final ContentValues values = toContentValues(attr);
+
+        // Build the selection and args. To cut down on the number of lines to search, limit
+        // the search to those with at least one argument equals to the requested attributes.
+        // This works only because null attributes match only will not result in group-closeness.
+        final StringJoiner sj = new StringJoiner(" OR ");
+        final ArrayList<Object> args = new ArrayList<>();
+        args.add(System.currentTimeMillis());
+        for (final String field : values.keySet()) {
+            sj.add(field + " = ?");
+            args.add(values.get(field));
+        }
+
+        final String selection = NetworkAttributesContract.COLNAME_EXPIRYDATE + " > ? AND ("
+                + sj.toString() + ")";
+        final Cursor cursor = db.queryWithFactory(new CustomCursorFactory(args),
+                false, // distinct
+                NetworkAttributesContract.TABLENAME,
+                null, // columns, null means everything
+                selection, // selection
+                null, // selectionArgs, horrendously passed to the cursor factory instead
+                null, // groupBy
+                null, // having
+                null, // orderBy
+                null); // limit
+        if (cursor.getCount() <= 0) return null;
+        cursor.moveToFirst();
+        String bestKey = null;
+        float bestMatchConfidence = GROUPCLOSE_CONFIDENCE; // Never return a match worse than this.
+        while (!cursor.isAfterLast()) {
+            final NetworkAttributes read = readNetworkAttributesLine(cursor);
+            final float confidence = read.getNetworkGroupSamenessConfidence(attr);
+            if (confidence > bestMatchConfidence) {
+                bestKey = getString(cursor, NetworkAttributesContract.COLNAME_L2KEY);
+                bestMatchConfidence = confidence;
+            }
+            cursor.moveToNext();
+        }
+        cursor.close();
+        return bestKey;
     }
 
     // Helper methods
-    static String getString(final Cursor cursor, final String columnName) {
+    private static String getString(final Cursor cursor, final String columnName) {
         final int columnIndex = cursor.getColumnIndex(columnName);
         return (columnIndex >= 0) ? cursor.getString(columnIndex) : null;
     }
-    static byte[] getBlob(final Cursor cursor, final String columnName) {
+    private static byte[] getBlob(final Cursor cursor, final String columnName) {
         final int columnIndex = cursor.getColumnIndex(columnName);
         return (columnIndex >= 0) ? cursor.getBlob(columnIndex) : null;
     }
-    static int getInt(final Cursor cursor, final String columnName, final int defaultValue) {
+    private static int getInt(final Cursor cursor, final String columnName,
+            final int defaultValue) {
         final int columnIndex = cursor.getColumnIndex(columnName);
         return (columnIndex >= 0) ? cursor.getInt(columnIndex) : defaultValue;
     }
+    private static long getLong(final Cursor cursor, final String columnName,
+            final long defaultValue) {
+        final int columnIndex = cursor.getColumnIndex(columnName);
+        return (columnIndex >= 0) ? cursor.getLong(columnIndex) : defaultValue;
+    }
 }
diff --git a/services/ipmemorystore/java/com/android/server/net/ipmemorystore/IpMemoryStoreService.java b/services/ipmemorystore/java/com/android/server/net/ipmemorystore/IpMemoryStoreService.java
index 444b299..d43dc6a 100644
--- a/services/ipmemorystore/java/com/android/server/net/ipmemorystore/IpMemoryStoreService.java
+++ b/services/ipmemorystore/java/com/android/server/net/ipmemorystore/IpMemoryStoreService.java
@@ -37,6 +37,7 @@
 import android.net.ipmemorystore.IOnStatusListener;
 import android.net.ipmemorystore.NetworkAttributes;
 import android.net.ipmemorystore.NetworkAttributesParcelable;
+import android.net.ipmemorystore.SameL3NetworkResponse;
 import android.net.ipmemorystore.Status;
 import android.net.ipmemorystore.StatusParcelable;
 import android.net.ipmemorystore.Utils;
@@ -249,9 +250,26 @@
      * Through the listener, returns the L2 key if one matched, or null.
      */
     @Override
-    public void findL2Key(@NonNull final NetworkAttributesParcelable attributes,
-            @NonNull final IOnL2KeyResponseListener listener) {
-        // TODO : implement this.
+    public void findL2Key(@Nullable final NetworkAttributesParcelable attributes,
+            @Nullable final IOnL2KeyResponseListener listener) {
+        if (null == listener) return;
+        mExecutor.execute(() -> {
+            try {
+                if (null == attributes) {
+                    listener.onL2KeyResponse(makeStatus(ERROR_ILLEGAL_ARGUMENT), null);
+                    return;
+                }
+                if (null == mDb) {
+                    listener.onL2KeyResponse(makeStatus(ERROR_ILLEGAL_ARGUMENT), null);
+                    return;
+                }
+                final String key = IpMemoryStoreDatabase.findClosestAttributes(mDb,
+                        new NetworkAttributes(attributes));
+                listener.onL2KeyResponse(makeStatus(SUCCESS), key);
+            } catch (final RemoteException e) {
+                // Client at the other end died
+            }
+        });
     }
 
     /**
@@ -264,9 +282,40 @@
      * Through the listener, a SameL3NetworkResponse containing the answer and confidence.
      */
     @Override
-    public void isSameNetwork(@NonNull final String l2Key1, @NonNull final String l2Key2,
-            @NonNull final IOnSameNetworkResponseListener listener) {
-        // TODO : implement this.
+    public void isSameNetwork(@Nullable final String l2Key1, @Nullable final String l2Key2,
+            @Nullable final IOnSameNetworkResponseListener listener) {
+        if (null == listener) return;
+        mExecutor.execute(() -> {
+            try {
+                if (null == l2Key1 || null == l2Key2) {
+                    listener.onSameNetworkResponse(makeStatus(ERROR_ILLEGAL_ARGUMENT), null);
+                    return;
+                }
+                if (null == mDb) {
+                    listener.onSameNetworkResponse(makeStatus(ERROR_ILLEGAL_ARGUMENT), null);
+                    return;
+                }
+                try {
+                    final NetworkAttributes attr1 =
+                            IpMemoryStoreDatabase.retrieveNetworkAttributes(mDb, l2Key1);
+                    final NetworkAttributes attr2 =
+                            IpMemoryStoreDatabase.retrieveNetworkAttributes(mDb, l2Key2);
+                    if (null == attr1 || null == attr2) {
+                        listener.onSameNetworkResponse(makeStatus(SUCCESS),
+                                new SameL3NetworkResponse(l2Key1, l2Key2,
+                                        -1f /* never connected */).toParcelable());
+                        return;
+                    }
+                    final float confidence = attr1.getNetworkGroupSamenessConfidence(attr2);
+                    listener.onSameNetworkResponse(makeStatus(SUCCESS),
+                            new SameL3NetworkResponse(l2Key1, l2Key2, confidence).toParcelable());
+                } catch (Exception e) {
+                    listener.onSameNetworkResponse(makeStatus(ERROR_GENERIC), null);
+                }
+            } catch (final RemoteException e) {
+                // Client at the other end died
+            }
+        });
     }
 
     /**
@@ -285,21 +334,22 @@
         mExecutor.execute(() -> {
             try {
                 if (null == l2Key) {
-                    listener.onL2KeyResponse(makeStatus(ERROR_ILLEGAL_ARGUMENT), l2Key, null);
+                    listener.onNetworkAttributesRetrieved(
+                            makeStatus(ERROR_ILLEGAL_ARGUMENT), l2Key, null);
                     return;
                 }
                 if (null == mDb) {
-                    listener.onL2KeyResponse(makeStatus(ERROR_DATABASE_CANNOT_BE_OPENED), l2Key,
-                            null);
+                    listener.onNetworkAttributesRetrieved(
+                            makeStatus(ERROR_DATABASE_CANNOT_BE_OPENED), l2Key, null);
                     return;
                 }
                 try {
                     final NetworkAttributes attributes =
                             IpMemoryStoreDatabase.retrieveNetworkAttributes(mDb, l2Key);
-                    listener.onL2KeyResponse(makeStatus(SUCCESS), l2Key,
+                    listener.onNetworkAttributesRetrieved(makeStatus(SUCCESS), l2Key,
                             null == attributes ? null : attributes.toParcelable());
                 } catch (final Exception e) {
-                    listener.onL2KeyResponse(makeStatus(ERROR_GENERIC), l2Key, null);
+                    listener.onNetworkAttributesRetrieved(makeStatus(ERROR_GENERIC), l2Key, null);
                 }
             } catch (final RemoteException e) {
                 // Client at the other end died
diff --git a/services/java/com/android/server/SystemServer.java b/services/java/com/android/server/SystemServer.java
index cef47ca..5861368 100644
--- a/services/java/com/android/server/SystemServer.java
+++ b/services/java/com/android/server/SystemServer.java
@@ -136,6 +136,7 @@
 import com.android.server.statusbar.StatusBarManagerService;
 import com.android.server.storage.DeviceStorageMonitorService;
 import com.android.server.telecom.TelecomLoaderService;
+import com.android.server.testharness.TestHarnessModeService;
 import com.android.server.textclassifier.TextClassificationManagerService;
 import com.android.server.textservices.TextServicesManagerService;
 import com.android.server.trust.TrustManagerService;
@@ -327,6 +328,11 @@
     private static native void startHidlServices();
 
     /**
+     * Mark this process' heap as profileable. Only for debug builds.
+     */
+    private static native void initZygoteChildHeapProfiling();
+
+    /**
      * The main entry point from zygote.
      */
     public static void main(String[] args) {
@@ -448,6 +454,11 @@
             // Initialize native services.
             System.loadLibrary("android_servers");
 
+            // Debug builds - allow heap profiling.
+            if (Build.IS_DEBUGGABLE) {
+                initZygoteChildHeapProfiling();
+            }
+
             // Check whether we failed to shut down last time we tried.
             // This call may not return.
             performPendingShutdown();
@@ -862,7 +873,7 @@
                     TimingsTraceLog traceLog = new TimingsTraceLog(
                             SYSTEM_SERVER_TIMING_ASYNC_TAG, Trace.TRACE_TAG_SYSTEM_SERVER);
                     traceLog.traceBegin(SECONDARY_ZYGOTE_PRELOAD);
-                    if (!Process.zygoteProcess.preloadDefault(Build.SUPPORTED_32_BIT_ABIS[0])) {
+                    if (!Process.ZYGOTE_PROCESS.preloadDefault(Build.SUPPORTED_32_BIT_ABIS[0])) {
                         Slog.e(TAG, "Unable to preload default resources");
                     }
                     traceLog.traceEnd();
@@ -1147,6 +1158,10 @@
                 traceBeginAndSlog("StartPersistentDataBlock");
                 mSystemServiceManager.startService(PersistentDataBlockService.class);
                 traceEnd();
+
+                traceBeginAndSlog("StartTestHarnessMode");
+                mSystemServiceManager.startService(TestHarnessModeService.class);
+                traceEnd();
             }
 
             if (hasPdb || OemLockService.isHalPresent()) {
@@ -1546,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
@@ -1996,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/Android.bp b/services/net/Android.bp
index 3b4d6a7..30c7de5 100644
--- a/services/net/Android.bp
+++ b/services/net/Android.bp
@@ -3,18 +3,18 @@
     srcs: ["java/**/*.java"],
 }
 
-// TODO: move to networking module with DhcpClient and remove lib
-java_library {
-    name: "dhcp-packet-lib",
-    srcs: [
-        "java/android/net/dhcp/*Packet.java",
-    ]
-}
-
 filegroup {
     name: "services-networkstack-shared-srcs",
     srcs: [
-        "java/android/net/util/FdEventsReader.java", // TODO: move to NetworkStack with IpClient
+        "java/android/net/ip/InterfaceController.java", // TODO: move to NetworkStack with tethering
+        "java/android/net/util/InterfaceParams.java", // TODO: move to NetworkStack with IpServer
         "java/android/net/shared/*.java",
+    ],
+}
+
+java_library {
+    name: "services-netlink-lib",
+    srcs: [
+        "java/android/net/netlink/*.java",
     ]
 }
diff --git a/services/net/java/android/net/ip/IpClientUtil.java b/services/net/java/android/net/ip/IpClientUtil.java
index 0aec101..2a2a67a 100644
--- a/services/net/java/android/net/ip/IpClientUtil.java
+++ b/services/net/java/android/net/ip/IpClientUtil.java
@@ -16,8 +16,15 @@
 
 package android.net.ip;
 
+import static android.net.shared.IpConfigurationParcelableUtil.fromStableParcelable;
+import static android.net.shared.LinkPropertiesParcelableUtil.fromStableParcelable;
+
 import android.content.Context;
+import android.net.DhcpResultsParcelable;
 import android.net.LinkProperties;
+import android.net.LinkPropertiesParcelable;
+import android.net.NetworkStack;
+import android.net.ip.IIpClientCallbacks;
 import android.os.ConditionVariable;
 
 import java.io.FileDescriptor;
@@ -31,8 +38,8 @@
  * @hide
  */
 public class IpClientUtil {
-    // TODO: remove once IpClient dumps are moved to NetworkStack and callers don't need this arg
-    public static final String DUMP_ARG = IpClient.DUMP_ARG;
+    // TODO: remove with its callers
+    public static final String DUMP_ARG = "ipclient";
 
     /**
      * Subclass of {@link IpClientCallbacks} allowing clients to block until provisioning is
@@ -69,24 +76,129 @@
      *
      * <p>This is a convenience method to allow clients to use {@link IpClientCallbacks} instead of
      * {@link IIpClientCallbacks}.
+     * @see {@link NetworkStack#makeIpClient(String, IIpClientCallbacks)}
      */
     public static void makeIpClient(Context context, String ifName, IpClientCallbacks callback) {
-        // TODO: request IpClient asynchronously from NetworkStack.
-        final IpClient ipClient = new IpClient(context, ifName, callback);
-        callback.onIpClientCreated(ipClient.makeConnector());
+        context.getSystemService(NetworkStack.class)
+                .makeIpClient(ifName, new IpClientCallbacksProxy(callback));
+    }
+
+    /**
+     * Create a new IpClient.
+     *
+     * <p>This is a convenience method to allow clients to use {@link IpClientCallbacksProxy}
+     * instead of {@link IIpClientCallbacks}.
+     * @see {@link NetworkStack#makeIpClient(String, IIpClientCallbacks)}
+     */
+    public static void makeIpClient(
+            Context context, String ifName, IpClientCallbacksProxy callback) {
+        context.getSystemService(NetworkStack.class)
+                .makeIpClient(ifName, callback);
+    }
+
+    /**
+     * Wrapper to relay calls from {@link IIpClientCallbacks} to {@link IpClientCallbacks}.
+     */
+    public static class IpClientCallbacksProxy extends IIpClientCallbacks.Stub {
+        protected final IpClientCallbacks mCb;
+
+        /**
+         * Create a new IpClientCallbacksProxy.
+         */
+        public IpClientCallbacksProxy(IpClientCallbacks cb) {
+            mCb = cb;
+        }
+
+        @Override
+        public void onIpClientCreated(IIpClient ipClient) {
+            mCb.onIpClientCreated(ipClient);
+        }
+
+        @Override
+        public void onPreDhcpAction() {
+            mCb.onPreDhcpAction();
+        }
+
+        @Override
+        public void onPostDhcpAction() {
+            mCb.onPostDhcpAction();
+        }
+
+        // This is purely advisory and not an indication of provisioning
+        // success or failure.  This is only here for callers that want to
+        // expose DHCPv4 results to other APIs (e.g., WifiInfo#setInetAddress).
+        // DHCPv4 or static IPv4 configuration failure or success can be
+        // determined by whether or not the passed-in DhcpResults object is
+        // null or not.
+        @Override
+        public void onNewDhcpResults(DhcpResultsParcelable dhcpResults) {
+            mCb.onNewDhcpResults(fromStableParcelable(dhcpResults));
+        }
+
+        @Override
+        public void onProvisioningSuccess(LinkPropertiesParcelable newLp) {
+            mCb.onProvisioningSuccess(fromStableParcelable(newLp));
+        }
+        @Override
+        public void onProvisioningFailure(LinkPropertiesParcelable newLp) {
+            mCb.onProvisioningFailure(fromStableParcelable(newLp));
+        }
+
+        // Invoked on LinkProperties changes.
+        @Override
+        public void onLinkPropertiesChange(LinkPropertiesParcelable newLp) {
+            mCb.onLinkPropertiesChange(fromStableParcelable(newLp));
+        }
+
+        // Called when the internal IpReachabilityMonitor (if enabled) has
+        // detected the loss of a critical number of required neighbors.
+        @Override
+        public void onReachabilityLost(String logMsg) {
+            mCb.onReachabilityLost(logMsg);
+        }
+
+        // Called when the IpClient state machine terminates.
+        @Override
+        public void onQuit() {
+            mCb.onQuit();
+        }
+
+        // Install an APF program to filter incoming packets.
+        @Override
+        public void installPacketFilter(byte[] filter) {
+            mCb.installPacketFilter(filter);
+        }
+
+        // Asynchronously read back the APF program & data buffer from the wifi driver.
+        // Due to Wifi HAL limitations, the current implementation only supports dumping the entire
+        // buffer. In response to this request, the driver returns the data buffer asynchronously
+        // by sending an IpClient#EVENT_READ_PACKET_FILTER_COMPLETE message.
+        @Override
+        public void startReadPacketFilter() {
+            mCb.startReadPacketFilter();
+        }
+
+        // If multicast filtering cannot be accomplished with APF, this function will be called to
+        // actuate multicast filtering using another means.
+        @Override
+        public void setFallbackMulticastFilter(boolean enabled) {
+            mCb.setFallbackMulticastFilter(enabled);
+        }
+
+        // Enabled/disable Neighbor Discover offload functionality. This is
+        // called, for example, whenever 464xlat is being started or stopped.
+        @Override
+        public void setNeighborDiscoveryOffload(boolean enable) {
+            mCb.setNeighborDiscoveryOffload(enable);
+        }
     }
 
     /**
      * Dump logs for the specified IpClient.
-     * TODO: remove logging from this method once IpClient logs are dumped in NetworkStack dumpsys,
-     * then remove callers and delete.
+     * TODO: remove callers and delete
      */
     public static void dumpIpClient(
             IIpClient connector, FileDescriptor fd, PrintWriter pw, String[] args) {
-        if (!(connector instanceof IpClient.IpClientConnector)) {
-            pw.println("Invalid connector");
-            return;
-        }
-        ((IpClient.IpClientConnector) connector).dumpIpClientLogs(fd, pw, args);
+        pw.println("IpClient logs have moved to dumpsys network_stack");
     }
 }
diff --git a/services/net/java/android/net/ip/IpServer.java b/services/net/java/android/net/ip/IpServer.java
index 7910c9a..f7360f5 100644
--- a/services/net/java/android/net/ip/IpServer.java
+++ b/services/net/java/android/net/ip/IpServer.java
@@ -40,7 +40,7 @@
 import android.net.ip.RouterAdvertisementDaemon.RaParams;
 import android.net.util.InterfaceParams;
 import android.net.util.InterfaceSet;
-import android.net.util.NetdService;
+import android.net.shared.NetdService;
 import android.net.util.SharedLog;
 import android.os.INetworkManagementService;
 import android.os.Looper;
diff --git a/services/net/java/android/net/util/NetdService.java b/services/net/java/android/net/shared/NetdService.java
similarity index 99%
rename from services/net/java/android/net/util/NetdService.java
rename to services/net/java/android/net/shared/NetdService.java
index 80b2c27..be0f5f2 100644
--- a/services/net/java/android/net/util/NetdService.java
+++ b/services/net/java/android/net/shared/NetdService.java
@@ -14,7 +14,7 @@
  * limitations under the License.
  */
 
-package android.net.util;
+package android.net.shared;
 
 import android.net.INetd;
 import android.os.RemoteException;
diff --git a/services/net/java/android/net/shared/NetworkMonitorUtils.java b/services/net/java/android/net/shared/NetworkMonitorUtils.java
index 463cf2a..3d2a2de 100644
--- a/services/net/java/android/net/shared/NetworkMonitorUtils.java
+++ b/services/net/java/android/net/shared/NetworkMonitorUtils.java
@@ -16,6 +16,11 @@
 
 package android.net.shared;
 
+import static android.net.NetworkCapabilities.NET_CAPABILITY_INTERNET;
+import static android.net.NetworkCapabilities.NET_CAPABILITY_NOT_RESTRICTED;
+import static android.net.NetworkCapabilities.NET_CAPABILITY_NOT_VPN;
+import static android.net.NetworkCapabilities.NET_CAPABILITY_TRUSTED;
+
 import android.content.Context;
 import android.net.NetworkCapabilities;
 import android.provider.Settings;
@@ -58,9 +63,12 @@
      * @param dfltNetCap Default requested network capabilities.
      * @param nc Network capabilities of the network to test.
      */
-    public static boolean isValidationRequired(
-            NetworkCapabilities dfltNetCap, NetworkCapabilities nc) {
+    public static boolean isValidationRequired(NetworkCapabilities nc) {
         // TODO: Consider requiring validation for DUN networks.
-        return dfltNetCap.satisfiedByNetworkCapabilities(nc);
+        return nc != null
+                && nc.hasCapability(NET_CAPABILITY_INTERNET)
+                && nc.hasCapability(NET_CAPABILITY_NOT_RESTRICTED)
+                && nc.hasCapability(NET_CAPABILITY_TRUSTED)
+                && nc.hasCapability(NET_CAPABILITY_NOT_VPN);
     }
 }
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/net/java/android/net/util/InterfaceParams.java b/services/net/java/android/net/util/InterfaceParams.java
index 7b060da..f6bb873 100644
--- a/services/net/java/android/net/util/InterfaceParams.java
+++ b/services/net/java/android/net/util/InterfaceParams.java
@@ -16,9 +16,6 @@
 
 package android.net.util;
 
-import static android.net.util.NetworkConstants.ETHER_MTU;
-import static android.net.util.NetworkConstants.IPV6_MIN_MTU;
-
 import static com.android.internal.util.Preconditions.checkArgument;
 
 import android.net.MacAddress;
@@ -44,6 +41,11 @@
     public final MacAddress macAddr;
     public final int defaultMtu;
 
+    // TODO: move the below to NetworkStackConstants when this class is moved to the NetworkStack.
+    private static final int ETHER_MTU = 1500;
+    private static final int IPV6_MIN_MTU = 1280;
+
+
     public static InterfaceParams getByName(String name) {
         final NetworkInterface netif = getNetworkInterfaceByName(name);
         if (netif == null) return null;
diff --git a/services/net/java/android/net/util/NetworkConstants.java b/services/net/java/android/net/util/NetworkConstants.java
index c183b81..ea5ce65 100644
--- a/services/net/java/android/net/util/NetworkConstants.java
+++ b/services/net/java/android/net/util/NetworkConstants.java
@@ -28,28 +28,6 @@
 public final class NetworkConstants {
     private NetworkConstants() { throw new RuntimeException("no instance permitted"); }
 
-    /**
-     * Ethernet constants.
-     *
-     * See also:
-     *     - https://tools.ietf.org/html/rfc894
-     *     - https://tools.ietf.org/html/rfc2464
-     *     - https://tools.ietf.org/html/rfc7042
-     *     - http://www.iana.org/assignments/ethernet-numbers/ethernet-numbers.xhtml
-     *     - http://www.iana.org/assignments/ieee-802-numbers/ieee-802-numbers.xhtml
-     */
-    public static final int ETHER_DST_ADDR_OFFSET = 0;
-    public static final int ETHER_SRC_ADDR_OFFSET = 6;
-    public static final int ETHER_ADDR_LEN = 6;
-
-    public static final int ETHER_TYPE_OFFSET = 12;
-    public static final int ETHER_TYPE_LENGTH = 2;
-    public static final int ETHER_TYPE_ARP  = 0x0806;
-    public static final int ETHER_TYPE_IPV4 = 0x0800;
-    public static final int ETHER_TYPE_IPV6 = 0x86dd;
-
-    public static final int ETHER_HEADER_LEN = 14;
-
     public static final byte FF = asByte(0xff);
     public static final byte[] ETHER_ADDR_BROADCAST = {
         FF, FF, FF, FF, FF, FF
@@ -58,34 +36,12 @@
     public static final int ETHER_MTU = 1500;
 
     /**
-     * ARP constants.
-     *
-     * See also:
-     *     - https://tools.ietf.org/html/rfc826
-     *     - http://www.iana.org/assignments/arp-parameters/arp-parameters.xhtml
-     */
-    public static final int ARP_PAYLOAD_LEN = 28;  // For Ethernet+IPv4.
-    public static final int ARP_REQUEST = 1;
-    public static final int ARP_REPLY   = 2;
-    public static final int ARP_HWTYPE_RESERVED_LO = 0;
-    public static final int ARP_HWTYPE_ETHER       = 1;
-    public static final int ARP_HWTYPE_RESERVED_HI = 0xffff;
-
-    /**
      * IPv4 constants.
      *
      * See also:
      *     - https://tools.ietf.org/html/rfc791
      */
-    public static final int IPV4_HEADER_MIN_LEN = 20;
-    public static final int IPV4_IHL_MASK = 0xf;
-    public static final int IPV4_FLAGS_OFFSET = 6;
-    public static final int IPV4_FRAGMENT_MASK = 0x1fff;
-    public static final int IPV4_PROTOCOL_OFFSET = 9;
-    public static final int IPV4_SRC_ADDR_OFFSET = 12;
-    public static final int IPV4_DST_ADDR_OFFSET = 16;
     public static final int IPV4_ADDR_BITS = 32;
-    public static final int IPV4_ADDR_LEN = 4;
 
     /**
      * IPv6 constants.
@@ -93,15 +49,10 @@
      * See also:
      *     - https://tools.ietf.org/html/rfc2460
      */
-    public static final int IPV6_HEADER_LEN = 40;
-    public static final int IPV6_PROTOCOL_OFFSET = 6;
-    public static final int IPV6_SRC_ADDR_OFFSET = 8;
-    public static final int IPV6_DST_ADDR_OFFSET = 24;
     public static final int IPV6_ADDR_BITS = 128;
     public static final int IPV6_ADDR_LEN = 16;
     public static final int IPV6_MIN_MTU = 1280;
     public static final int RFC7421_PREFIX_LENGTH = 64;
-    public static final int RFC6177_MIN_PREFIX_LENGTH = 48;
 
     /**
      * ICMP common (v4/v6) constants.
@@ -124,45 +75,7 @@
      *     - https://tools.ietf.org/html/rfc792
      */
     public static final int ICMPV4_ECHO_REQUEST_TYPE = 8;
-
-    /**
-     * ICMPv6 constants.
-     *
-     * See also:
-     *     - https://tools.ietf.org/html/rfc4443
-     *     - https://tools.ietf.org/html/rfc4861
-     */
-    public static final int ICMPV6_HEADER_MIN_LEN = 4;
     public static final int ICMPV6_ECHO_REQUEST_TYPE = 128;
-    public static final int ICMPV6_ECHO_REPLY_TYPE = 129;
-    public static final int ICMPV6_ROUTER_SOLICITATION    = 133;
-    public static final int ICMPV6_ROUTER_ADVERTISEMENT   = 134;
-    public static final int ICMPV6_NEIGHBOR_SOLICITATION  = 135;
-    public static final int ICMPV6_NEIGHBOR_ADVERTISEMENT = 136;
-
-    public static final int ICMPV6_ND_OPTION_MIN_LENGTH = 8;
-    public static final int ICMPV6_ND_OPTION_LENGTH_SCALING_FACTOR = 8;
-    public static final int ICMPV6_ND_OPTION_SLLA = 1;
-    public static final int ICMPV6_ND_OPTION_TLLA = 2;
-    public static final int ICMPV6_ND_OPTION_MTU  = 5;
-
-
-    /**
-     * UDP constants.
-     *
-     * See also:
-     *     - https://tools.ietf.org/html/rfc768
-     */
-    public static final int UDP_HEADER_LEN = 8;
-
-    /**
-     * DHCP(v4) constants.
-     *
-     * See also:
-     *     - https://tools.ietf.org/html/rfc2131
-     */
-    public static final int DHCP4_SERVER_PORT = 67;
-    public static final int DHCP4_CLIENT_PORT = 68;
 
     /**
      * DNS constants.
@@ -176,9 +89,4 @@
      * Utility functions.
      */
     public static byte asByte(int i) { return (byte) i; }
-
-    public static String asString(int i) { return Integer.toString(i); }
-
-    public static int asUint(byte b) { return (b & 0xff); }
-    public static int asUint(short s) { return (s & 0xffff); }
 }
diff --git a/services/robotests/backup/src/com/android/server/backup/keyvalue/KeyValueBackupTaskTest.java b/services/robotests/backup/src/com/android/server/backup/keyvalue/KeyValueBackupTaskTest.java
index 4811523..164570a 100644
--- a/services/robotests/backup/src/com/android/server/backup/keyvalue/KeyValueBackupTaskTest.java
+++ b/services/robotests/backup/src/com/android/server/backup/keyvalue/KeyValueBackupTaskTest.java
@@ -116,6 +116,7 @@
 import com.android.server.backup.testing.TransportTestUtils.TransportMock;
 import com.android.server.testing.shadows.FrameworkShadowLooper;
 import com.android.server.testing.shadows.ShadowApplicationPackageManager;
+import com.android.server.testing.shadows.ShadowBackupActivityThread;
 import com.android.server.testing.shadows.ShadowBackupDataInput;
 import com.android.server.testing.shadows.ShadowBackupDataOutput;
 import com.android.server.testing.shadows.ShadowEventLog;
@@ -163,6 +164,7 @@
             ShadowBackupDataOutput.class,
             ShadowEventLog.class,
             ShadowQueuedWork.class,
+            ShadowBackupActivityThread.class,
         })
 @Presubmit
 public class KeyValueBackupTaskTest {
diff --git a/services/robotests/src/com/android/server/testing/shadows/ShadowAppBackupUtils.java b/services/robotests/src/com/android/server/testing/shadows/ShadowAppBackupUtils.java
index aefc871..33b8aa7 100644
--- a/services/robotests/src/com/android/server/testing/shadows/ShadowAppBackupUtils.java
+++ b/services/robotests/src/com/android/server/testing/shadows/ShadowAppBackupUtils.java
@@ -62,7 +62,7 @@
     }
 
     @Implementation
-    protected static boolean appIsEligibleForBackup(ApplicationInfo app, PackageManager pm) {
+    protected static boolean appIsEligibleForBackup(ApplicationInfo app, int userId) {
         return sAppsEligibleForBackup.contains(app.packageName);
     }
 
diff --git a/services/robotests/src/com/android/server/testing/shadows/ShadowBackupActivityThread.java b/services/robotests/src/com/android/server/testing/shadows/ShadowBackupActivityThread.java
new file mode 100644
index 0000000..ca2e3b6
--- /dev/null
+++ b/services/robotests/src/com/android/server/testing/shadows/ShadowBackupActivityThread.java
@@ -0,0 +1,79 @@
+/*
+ * 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.testing.shadows;
+
+import android.app.ActivityThread;
+import android.content.pm.ApplicationInfo;
+import android.content.pm.IPackageManager;
+import android.content.pm.PackageManager;
+import android.os.RemoteException;
+
+import org.robolectric.RuntimeEnvironment;
+import org.robolectric.annotation.Implementation;
+import org.robolectric.annotation.Implements;
+import org.robolectric.shadows.ShadowActivityThread;
+
+import java.lang.reflect.InvocationHandler;
+import java.lang.reflect.Method;
+import java.lang.reflect.Proxy;
+
+import javax.annotation.Nonnull;
+
+/**
+ * Extends the existing {@link ShadowActivityThread} to add support for
+ * {@link PackageManager#getApplicationEnabledSetting(String)} in the shadow {@link PackageManager}
+ * returned  by {@link ShadowBackupActivityThread#getPackageManager()}.
+ */
+@Implements(value = ActivityThread.class, isInAndroidSdk = false, looseSignatures = true)
+public class ShadowBackupActivityThread extends ShadowActivityThread {
+    @Implementation
+    public static Object getPackageManager() {
+        ClassLoader classLoader = ShadowActivityThread.class.getClassLoader();
+        Class<?> iPackageManagerClass;
+        try {
+            iPackageManagerClass = classLoader.loadClass("android.content.pm.IPackageManager");
+        } catch (ClassNotFoundException e) {
+            throw new RuntimeException(e);
+        }
+
+        return Proxy.newProxyInstance(
+                classLoader,
+                new Class[] {iPackageManagerClass},
+                new InvocationHandler() {
+                    @Override
+                    public Object invoke(Object proxy, @Nonnull Method method, Object[] args)
+                            throws Exception {
+                        if (method.getName().equals("getApplicationInfo")) {
+                            String packageName = (String) args[0];
+                            int flags = (Integer) args[1];
+
+                            try {
+                                return RuntimeEnvironment.application
+                                        .getPackageManager()
+                                        .getApplicationInfo(packageName, flags);
+                            } catch (PackageManager.NameNotFoundException e) {
+                                throw new RemoteException(e.getMessage());
+                            }
+                        } else if (method.getName().equals("getApplicationEnabledSetting")) {
+                            return 0;
+                        } else {
+                            return null;
+                        }
+                    }
+                });
+    }
+}
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/mockingservicestests/src/com/android/server/DeviceIdleControllerTest.java b/services/tests/mockingservicestests/src/com/android/server/DeviceIdleControllerTest.java
index 1a16e56..53d72bb 100644
--- a/services/tests/mockingservicestests/src/com/android/server/DeviceIdleControllerTest.java
+++ b/services/tests/mockingservicestests/src/com/android/server/DeviceIdleControllerTest.java
@@ -1376,6 +1376,45 @@
         verifyLightStateConditions(LIGHT_STATE_ACTIVE);
     }
 
+    @Test
+    public void testStepToIdleMode() {
+        float delta = mDeviceIdleController.MIN_PRE_IDLE_FACTOR_CHANGE;
+        for (int mode = PowerManager.PRE_IDLE_TIMEOUT_MODE_NORMAL;
+                mode <= PowerManager.PRE_IDLE_TIMEOUT_MODE_LONG;
+                mode++) {
+            int ret = mDeviceIdleController.setPreIdleTimeoutMode(mode);
+            if (mode == PowerManager.PRE_IDLE_TIMEOUT_MODE_NORMAL) {
+                assertEquals("setPreIdleTimeoutMode: " + mode + " failed.",
+                        mDeviceIdleController.SET_IDLE_FACTOR_RESULT_IGNORED, ret);
+            } else {
+                assertEquals("setPreIdleTimeoutMode: " + mode + " failed.",
+                        mDeviceIdleController.SET_IDLE_FACTOR_RESULT_OK, ret);
+            }
+            //TODO(b/123045185): Mocked Handler of DeviceIdleController to make message loop
+            //workable in this test class
+            mDeviceIdleController.updatePreIdleFactor();
+            float expectedfactor = mDeviceIdleController.getPreIdleTimeoutByMode(mode);
+            float curfactor = mDeviceIdleController.getPreIdleTimeoutFactor();
+            assertEquals("Pre idle time factor of mode [" + mode + "].",
+                    expectedfactor, curfactor, delta);
+            mDeviceIdleController.resetPreIdleTimeoutMode();
+            mDeviceIdleController.updatePreIdleFactor();
+
+            checkNextAlarmTimeWithNewPreIdleFactor(expectedfactor, STATE_INACTIVE);
+            checkNextAlarmTimeWithNewPreIdleFactor(expectedfactor, STATE_IDLE_PENDING);
+
+            checkNextAlarmTimeWithNewPreIdleFactor(expectedfactor, STATE_SENSING);
+            checkNextAlarmTimeWithNewPreIdleFactor(expectedfactor, STATE_LOCATING);
+            checkNextAlarmTimeWithNewPreIdleFactor(expectedfactor, STATE_QUICK_DOZE_DELAY);
+            checkNextAlarmTimeWithNewPreIdleFactor(expectedfactor, STATE_IDLE_MAINTENANCE);
+            checkNextAlarmTimeWithNewPreIdleFactor(expectedfactor, STATE_IDLE);
+            checkMaybeDoAnImmediateMaintenance(expectedfactor);
+        }
+        float curfactor = mDeviceIdleController.getPreIdleTimeoutFactor();
+        assertEquals("Pre idle time factor of mode default.",
+                1.0f, curfactor, delta);
+    }
+
     private void enterDeepState(int state) {
         switch (state) {
             case STATE_ACTIVE:
@@ -1599,4 +1638,84 @@
                 fail("Conditions for " + lightStateToString(expectedLightState) + " unknown.");
         }
     }
+
+    private void checkNextAlarmTimeWithNewPreIdleFactor(float factor, int state) {
+        final long errorTolerance = 1000;
+        enterDeepState(state);
+        long now = SystemClock.elapsedRealtime();
+        long alarm = mDeviceIdleController.getNextAlarmTime();
+        if (state == STATE_INACTIVE || state == STATE_IDLE_PENDING) {
+            int ret = mDeviceIdleController.setPreIdleTimeoutFactor(factor);
+            if (Float.compare(factor, 1.0f) == 0) {
+                assertEquals("setPreIdleTimeoutMode: " + factor + " failed.",
+                        mDeviceIdleController.SET_IDLE_FACTOR_RESULT_IGNORED, ret);
+            } else {
+                assertEquals("setPreIdleTimeoutMode: " + factor + " failed.",
+                        mDeviceIdleController.SET_IDLE_FACTOR_RESULT_OK, ret);
+            }
+            if (ret == mDeviceIdleController.SET_IDLE_FACTOR_RESULT_OK) {
+                mDeviceIdleController.updatePreIdleFactor();
+                long newAlarm = mDeviceIdleController.getNextAlarmTime();
+                long newDelay = (long) ((alarm - now) * factor);
+                assertTrue("setPreIdleTimeoutFactor: " + factor,
+                        Math.abs(newDelay - (newAlarm - now)) <  errorTolerance);
+                mDeviceIdleController.resetPreIdleTimeoutMode();
+                mDeviceIdleController.updatePreIdleFactor();
+                mDeviceIdleController.maybeDoImmediateMaintenance();
+                newAlarm = mDeviceIdleController.getNextAlarmTime();
+                assertTrue("resetPreIdleTimeoutMode from: " + factor,
+                        Math.abs(newAlarm - alarm) < errorTolerance);
+                mDeviceIdleController.setPreIdleTimeoutFactor(factor);
+                now = SystemClock.elapsedRealtime();
+                enterDeepState(state);
+                newAlarm = mDeviceIdleController.getNextAlarmTime();
+                assertTrue("setPreIdleTimeoutFactor: " + factor + " before step to idle",
+                        Math.abs(newDelay - (newAlarm - now)) <  errorTolerance);
+                mDeviceIdleController.resetPreIdleTimeoutMode();
+                mDeviceIdleController.updatePreIdleFactor();
+                mDeviceIdleController.maybeDoImmediateMaintenance();
+            }
+        } else {
+            mDeviceIdleController.setPreIdleTimeoutFactor(factor);
+            mDeviceIdleController.updatePreIdleFactor();
+            long newAlarm = mDeviceIdleController.getNextAlarmTime();
+            assertTrue("setPreIdleTimeoutFactor: " + factor
+                    + " shounld not change next alarm" ,
+                    (newAlarm == alarm));
+            mDeviceIdleController.resetPreIdleTimeoutMode();
+            mDeviceIdleController.updatePreIdleFactor();
+            mDeviceIdleController.maybeDoImmediateMaintenance();
+        }
+    }
+
+    private void checkMaybeDoAnImmediateMaintenance(float factor) {
+        int ret = mDeviceIdleController.setPreIdleTimeoutFactor(factor);
+        final long minuteInMillis = 60 * 1000;
+        if (Float.compare(factor, 1.0f) == 0) {
+            assertEquals("setPreIdleTimeoutMode: " + factor + " failed.",
+                    mDeviceIdleController.SET_IDLE_FACTOR_RESULT_IGNORED, ret);
+        } else {
+            assertEquals("setPreIdleTimeoutMode: " + factor + " failed.",
+                    mDeviceIdleController.SET_IDLE_FACTOR_RESULT_OK, ret);
+        }
+        if (ret == mDeviceIdleController.SET_IDLE_FACTOR_RESULT_OK) {
+            enterDeepState(STATE_IDLE);
+            long now = SystemClock.elapsedRealtime();
+            long alarm = mDeviceIdleController.getNextAlarmTime();
+            mDeviceIdleController.setIdleStartTimeForTest(
+                    now - (long) (mConstants.IDLE_TIMEOUT * 0.6));
+            mDeviceIdleController.maybeDoImmediateMaintenance();
+            long newAlarm = mDeviceIdleController.getNextAlarmTime();
+            assertTrue("maintenance not reschedule IDLE_TIMEOUT * 0.6",
+                    newAlarm == alarm);
+            mDeviceIdleController.setIdleStartTimeForTest(
+                    now - (long) (mConstants.IDLE_TIMEOUT * 1.2));
+            mDeviceIdleController.maybeDoImmediateMaintenance();
+            newAlarm = mDeviceIdleController.getNextAlarmTime();
+            assertTrue("maintenance not reschedule IDLE_TIMEOUT * 1.2",
+                    (newAlarm - now) < minuteInMillis);
+            mDeviceIdleController.resetPreIdleTimeoutMode();
+            mDeviceIdleController.updatePreIdleFactor();
+        }
+    }
 }
diff --git a/services/tests/mockingservicestests/src/com/android/server/job/controllers/QuotaControllerTest.java b/services/tests/mockingservicestests/src/com/android/server/job/controllers/QuotaControllerTest.java
index 57ee6dc..cad71a2 100644
--- a/services/tests/mockingservicestests/src/com/android/server/job/controllers/QuotaControllerTest.java
+++ b/services/tests/mockingservicestests/src/com/android/server/job/controllers/QuotaControllerTest.java
@@ -25,6 +25,7 @@
 import static com.android.dx.mockito.inline.extended.ExtendedMockito.when;
 import static com.android.server.job.JobSchedulerService.ACTIVE_INDEX;
 import static com.android.server.job.JobSchedulerService.FREQUENT_INDEX;
+import static com.android.server.job.JobSchedulerService.NEVER_INDEX;
 import static com.android.server.job.JobSchedulerService.RARE_INDEX;
 import static com.android.server.job.JobSchedulerService.WORKING_INDEX;
 
@@ -370,16 +371,19 @@
         mQuotaController.saveTimingSession(0, "com.android.test.stay", one);
 
         ExecutionStats expectedStats = new ExecutionStats();
-        expectedStats.invalidTimeElapsed = now + 24 * HOUR_IN_MILLIS;
+        expectedStats.expirationTimeElapsed = now + 24 * HOUR_IN_MILLIS;
         expectedStats.windowSizeMs = 24 * HOUR_IN_MILLIS;
 
-        mQuotaController.onAppRemovedLocked("com.android.test.remove", 10001);
+        final int uid = 10001;
+        mQuotaController.onAppRemovedLocked("com.android.test.remove", uid);
         assertNull(mQuotaController.getTimingSessions(0, "com.android.test.remove"));
         assertEquals(expected, mQuotaController.getTimingSessions(0, "com.android.test.stay"));
         assertEquals(expectedStats,
                 mQuotaController.getExecutionStatsLocked(0, "com.android.test.remove", RARE_INDEX));
         assertNotEquals(expectedStats,
                 mQuotaController.getExecutionStatsLocked(0, "com.android.test.stay", RARE_INDEX));
+
+        assertFalse(mQuotaController.getForegroundUids().get(uid));
     }
 
     @Test
@@ -405,7 +409,7 @@
         mQuotaController.saveTimingSession(10, "com.android.test", one);
 
         ExecutionStats expectedStats = new ExecutionStats();
-        expectedStats.invalidTimeElapsed = now + 24 * HOUR_IN_MILLIS;
+        expectedStats.expirationTimeElapsed = now + 24 * HOUR_IN_MILLIS;
         expectedStats.windowSizeMs = 24 * HOUR_IN_MILLIS;
 
         mQuotaController.onUserRemovedLocked(0);
@@ -440,14 +444,14 @@
 
         inputStats.windowSizeMs = expectedStats.windowSizeMs = 12 * HOUR_IN_MILLIS;
         // Invalid time is now +24 hours since there are no sessions at all for the app.
-        expectedStats.invalidTimeElapsed = now + 24 * HOUR_IN_MILLIS;
+        expectedStats.expirationTimeElapsed = now + 24 * HOUR_IN_MILLIS;
         mQuotaController.updateExecutionStatsLocked(0, "com.android.test.not.run", inputStats);
         assertEquals(expectedStats, inputStats);
 
         inputStats.windowSizeMs = expectedStats.windowSizeMs = MINUTE_IN_MILLIS;
         // Invalid time is now +18 hours since there are no sessions in the window but the earliest
         // session is 6 hours ago.
-        expectedStats.invalidTimeElapsed = now + 18 * HOUR_IN_MILLIS;
+        expectedStats.expirationTimeElapsed = now + 18 * HOUR_IN_MILLIS;
         expectedStats.executionTimeInWindowMs = 0;
         expectedStats.bgJobCountInWindow = 0;
         expectedStats.executionTimeInMaxPeriodMs = 22 * MINUTE_IN_MILLIS;
@@ -457,7 +461,7 @@
 
         inputStats.windowSizeMs = expectedStats.windowSizeMs = 3 * MINUTE_IN_MILLIS;
         // Invalid time is now since the session straddles the window cutoff time.
-        expectedStats.invalidTimeElapsed = now;
+        expectedStats.expirationTimeElapsed = now;
         expectedStats.executionTimeInWindowMs = 2 * MINUTE_IN_MILLIS;
         expectedStats.bgJobCountInWindow = 3;
         expectedStats.executionTimeInMaxPeriodMs = 22 * MINUTE_IN_MILLIS;
@@ -468,7 +472,7 @@
         inputStats.windowSizeMs = expectedStats.windowSizeMs = 5 * MINUTE_IN_MILLIS;
         // Invalid time is now since the start of the session is at the very edge of the window
         // cutoff time.
-        expectedStats.invalidTimeElapsed = now;
+        expectedStats.expirationTimeElapsed = now;
         expectedStats.executionTimeInWindowMs = 4 * MINUTE_IN_MILLIS;
         expectedStats.bgJobCountInWindow = 3;
         expectedStats.executionTimeInMaxPeriodMs = 22 * MINUTE_IN_MILLIS;
@@ -479,7 +483,7 @@
         inputStats.windowSizeMs = expectedStats.windowSizeMs = 49 * MINUTE_IN_MILLIS;
         // Invalid time is now +44 minutes since the earliest session in the window is now-5
         // minutes.
-        expectedStats.invalidTimeElapsed = now + 44 * MINUTE_IN_MILLIS;
+        expectedStats.expirationTimeElapsed = now + 44 * MINUTE_IN_MILLIS;
         expectedStats.executionTimeInWindowMs = 4 * MINUTE_IN_MILLIS;
         expectedStats.bgJobCountInWindow = 3;
         expectedStats.executionTimeInMaxPeriodMs = 22 * MINUTE_IN_MILLIS;
@@ -489,7 +493,7 @@
 
         inputStats.windowSizeMs = expectedStats.windowSizeMs = 50 * MINUTE_IN_MILLIS;
         // Invalid time is now since the session is at the very edge of the window cutoff time.
-        expectedStats.invalidTimeElapsed = now;
+        expectedStats.expirationTimeElapsed = now;
         expectedStats.executionTimeInWindowMs = 5 * MINUTE_IN_MILLIS;
         expectedStats.bgJobCountInWindow = 4;
         expectedStats.executionTimeInMaxPeriodMs = 22 * MINUTE_IN_MILLIS;
@@ -500,7 +504,7 @@
         inputStats.windowSizeMs = expectedStats.windowSizeMs = HOUR_IN_MILLIS;
         // Invalid time is now since the start of the session is at the very edge of the window
         // cutoff time.
-        expectedStats.invalidTimeElapsed = now;
+        expectedStats.expirationTimeElapsed = now;
         expectedStats.executionTimeInWindowMs = 6 * MINUTE_IN_MILLIS;
         expectedStats.bgJobCountInWindow = 5;
         expectedStats.executionTimeInMaxPeriodMs = 22 * MINUTE_IN_MILLIS;
@@ -510,7 +514,7 @@
 
         inputStats.windowSizeMs = expectedStats.windowSizeMs = 2 * HOUR_IN_MILLIS;
         // Invalid time is now since the session straddles the window cutoff time.
-        expectedStats.invalidTimeElapsed = now;
+        expectedStats.expirationTimeElapsed = now;
         expectedStats.executionTimeInWindowMs = 11 * MINUTE_IN_MILLIS;
         expectedStats.bgJobCountInWindow = 10;
         expectedStats.executionTimeInMaxPeriodMs = 22 * MINUTE_IN_MILLIS;
@@ -523,7 +527,7 @@
         inputStats.windowSizeMs = expectedStats.windowSizeMs = 3 * HOUR_IN_MILLIS;
         // Invalid time is now +59 minutes since the earliest session in the window is now-121
         // minutes.
-        expectedStats.invalidTimeElapsed = now + 59 * MINUTE_IN_MILLIS;
+        expectedStats.expirationTimeElapsed = now + 59 * MINUTE_IN_MILLIS;
         expectedStats.executionTimeInWindowMs = 12 * MINUTE_IN_MILLIS;
         expectedStats.bgJobCountInWindow = 10;
         expectedStats.executionTimeInMaxPeriodMs = 22 * MINUTE_IN_MILLIS;
@@ -536,7 +540,7 @@
         inputStats.windowSizeMs = expectedStats.windowSizeMs = 6 * HOUR_IN_MILLIS;
         // Invalid time is now since the start of the session is at the very edge of the window
         // cutoff time.
-        expectedStats.invalidTimeElapsed = now;
+        expectedStats.expirationTimeElapsed = now;
         expectedStats.executionTimeInWindowMs = 22 * MINUTE_IN_MILLIS;
         expectedStats.bgJobCountInWindow = 15;
         expectedStats.executionTimeInMaxPeriodMs = 22 * MINUTE_IN_MILLIS;
@@ -546,14 +550,14 @@
         mQuotaController.updateExecutionStatsLocked(0, "com.android.test", inputStats);
         assertEquals(expectedStats, inputStats);
 
-        // Make sure invalidTimeElapsed is set correctly when it's dependent on the max period.
+        // Make sure expirationTimeElapsed is set correctly when it's dependent on the max period.
         mQuotaController.getTimingSessions(0, "com.android.test")
                 .add(0,
                         createTimingSession(now - (23 * HOUR_IN_MILLIS), MINUTE_IN_MILLIS, 3));
         inputStats.windowSizeMs = expectedStats.windowSizeMs = 8 * HOUR_IN_MILLIS;
         // Invalid time is now +1 hour since the earliest session in the max period is 1 hour
         // before the end of the max period cutoff time.
-        expectedStats.invalidTimeElapsed = now + HOUR_IN_MILLIS;
+        expectedStats.expirationTimeElapsed = now + HOUR_IN_MILLIS;
         expectedStats.executionTimeInWindowMs = 22 * MINUTE_IN_MILLIS;
         expectedStats.bgJobCountInWindow = 15;
         expectedStats.executionTimeInMaxPeriodMs = 23 * MINUTE_IN_MILLIS;
@@ -569,7 +573,7 @@
                                 2 * MINUTE_IN_MILLIS, 2));
         inputStats.windowSizeMs = expectedStats.windowSizeMs = 8 * HOUR_IN_MILLIS;
         // Invalid time is now since the earlist session straddles the max period cutoff time.
-        expectedStats.invalidTimeElapsed = now;
+        expectedStats.expirationTimeElapsed = now;
         expectedStats.executionTimeInWindowMs = 22 * MINUTE_IN_MILLIS;
         expectedStats.bgJobCountInWindow = 15;
         expectedStats.executionTimeInMaxPeriodMs = 24 * MINUTE_IN_MILLIS;
@@ -599,7 +603,7 @@
 
         // Active
         expectedStats.windowSizeMs = 10 * MINUTE_IN_MILLIS;
-        expectedStats.invalidTimeElapsed = now + 4 * MINUTE_IN_MILLIS;
+        expectedStats.expirationTimeElapsed = now + 4 * MINUTE_IN_MILLIS;
         expectedStats.executionTimeInWindowMs = 3 * MINUTE_IN_MILLIS;
         expectedStats.bgJobCountInWindow = 5;
         expectedStats.executionTimeInMaxPeriodMs = 33 * MINUTE_IN_MILLIS;
@@ -609,7 +613,7 @@
 
         // Working
         expectedStats.windowSizeMs = 2 * HOUR_IN_MILLIS;
-        expectedStats.invalidTimeElapsed = now;
+        expectedStats.expirationTimeElapsed = now;
         expectedStats.executionTimeInWindowMs = 13 * MINUTE_IN_MILLIS;
         expectedStats.bgJobCountInWindow = 10;
         expectedStats.executionTimeInMaxPeriodMs = 33 * MINUTE_IN_MILLIS;
@@ -621,7 +625,7 @@
 
         // Frequent
         expectedStats.windowSizeMs = 8 * HOUR_IN_MILLIS;
-        expectedStats.invalidTimeElapsed = now + HOUR_IN_MILLIS;
+        expectedStats.expirationTimeElapsed = now + HOUR_IN_MILLIS;
         expectedStats.executionTimeInWindowMs = 23 * MINUTE_IN_MILLIS;
         expectedStats.bgJobCountInWindow = 15;
         expectedStats.executionTimeInMaxPeriodMs = 33 * MINUTE_IN_MILLIS;
@@ -633,7 +637,7 @@
 
         // Rare
         expectedStats.windowSizeMs = 24 * HOUR_IN_MILLIS;
-        expectedStats.invalidTimeElapsed = now + HOUR_IN_MILLIS;
+        expectedStats.expirationTimeElapsed = now + HOUR_IN_MILLIS;
         expectedStats.executionTimeInWindowMs = 33 * MINUTE_IN_MILLIS;
         expectedStats.bgJobCountInWindow = 20;
         expectedStats.executionTimeInMaxPeriodMs = 33 * MINUTE_IN_MILLIS;
@@ -675,7 +679,7 @@
 
         ExecutionStats expectedStats = new ExecutionStats();
         expectedStats.windowSizeMs = originalStatsActive.windowSizeMs;
-        expectedStats.invalidTimeElapsed = originalStatsActive.invalidTimeElapsed;
+        expectedStats.expirationTimeElapsed = originalStatsActive.expirationTimeElapsed;
         expectedStats.executionTimeInWindowMs = originalStatsActive.executionTimeInWindowMs;
         expectedStats.bgJobCountInWindow = originalStatsActive.bgJobCountInWindow;
         expectedStats.executionTimeInMaxPeriodMs = originalStatsActive.executionTimeInMaxPeriodMs;
@@ -688,7 +692,7 @@
         assertEquals(expectedStats, newStatsActive);
 
         expectedStats.windowSizeMs = originalStatsWorking.windowSizeMs;
-        expectedStats.invalidTimeElapsed = originalStatsWorking.invalidTimeElapsed;
+        expectedStats.expirationTimeElapsed = originalStatsWorking.expirationTimeElapsed;
         expectedStats.executionTimeInWindowMs = originalStatsWorking.executionTimeInWindowMs;
         expectedStats.bgJobCountInWindow = originalStatsWorking.bgJobCountInWindow;
         expectedStats.quotaCutoffTimeElapsed = originalStatsWorking.quotaCutoffTimeElapsed;
@@ -698,7 +702,7 @@
         assertNotEquals(expectedStats, newStatsWorking);
 
         expectedStats.windowSizeMs = originalStatsFrequent.windowSizeMs;
-        expectedStats.invalidTimeElapsed = originalStatsFrequent.invalidTimeElapsed;
+        expectedStats.expirationTimeElapsed = originalStatsFrequent.expirationTimeElapsed;
         expectedStats.executionTimeInWindowMs = originalStatsFrequent.executionTimeInWindowMs;
         expectedStats.bgJobCountInWindow = originalStatsFrequent.bgJobCountInWindow;
         expectedStats.quotaCutoffTimeElapsed = originalStatsFrequent.quotaCutoffTimeElapsed;
@@ -708,7 +712,7 @@
         assertNotEquals(expectedStats, newStatsFrequent);
 
         expectedStats.windowSizeMs = originalStatsRare.windowSizeMs;
-        expectedStats.invalidTimeElapsed = originalStatsRare.invalidTimeElapsed;
+        expectedStats.expirationTimeElapsed = originalStatsRare.expirationTimeElapsed;
         expectedStats.executionTimeInWindowMs = originalStatsRare.executionTimeInWindowMs;
         expectedStats.bgJobCountInWindow = originalStatsRare.bgJobCountInWindow;
         expectedStats.quotaCutoffTimeElapsed = originalStatsRare.quotaCutoffTimeElapsed;
@@ -719,6 +723,77 @@
     }
 
     @Test
+    public void testIsWithinQuotaLocked_NeverApp() {
+        assertFalse(mQuotaController.isWithinQuotaLocked(0, "com.android.test.never", NEVER_INDEX));
+    }
+
+    @Test
+    public void testIsWithinQuotaLocked_Charging() {
+        setCharging();
+        assertTrue(mQuotaController.isWithinQuotaLocked(0, "com.android.test", RARE_INDEX));
+    }
+
+    @Test
+    public void testIsWithinQuotaLocked_UnderDuration_UnderJobCount() {
+        setDischarging();
+        final long now = JobSchedulerService.sElapsedRealtimeClock.millis();
+        mQuotaController.saveTimingSession(0, "com.android.test",
+                createTimingSession(now - (HOUR_IN_MILLIS), 3 * MINUTE_IN_MILLIS, 5));
+        mQuotaController.saveTimingSession(0, "com.android.test",
+                createTimingSession(now - (5 * MINUTE_IN_MILLIS), 3 * MINUTE_IN_MILLIS, 5));
+        mQuotaController.incrementJobCount(0, "com.android.test", 5);
+        assertTrue(mQuotaController.isWithinQuotaLocked(0, "com.android.test", WORKING_INDEX));
+    }
+
+    @Test
+    public void testIsWithinQuotaLocked_UnderDuration_OverJobCount() {
+        setDischarging();
+        final long now = JobSchedulerService.sElapsedRealtimeClock.millis();
+        final int jobCount = mConstants.QUOTA_CONTROLLER_MAX_JOB_COUNT_PER_ALLOWED_TIME;
+        mQuotaController.saveTimingSession(0, "com.android.test.spam",
+                createTimingSession(now - (HOUR_IN_MILLIS), 15 * MINUTE_IN_MILLIS, 25));
+        mQuotaController.saveTimingSession(0, "com.android.test.spam",
+                createTimingSession(now - (5 * MINUTE_IN_MILLIS), 3 * MINUTE_IN_MILLIS, jobCount));
+        mQuotaController.incrementJobCount(0, "com.android.test.spam", jobCount);
+        assertFalse(mQuotaController.isWithinQuotaLocked(0, "com.android.test.spam",
+                WORKING_INDEX));
+
+        mQuotaController.saveTimingSession(0, "com.android.test.frequent",
+                createTimingSession(now - (2 * HOUR_IN_MILLIS), 15 * MINUTE_IN_MILLIS, 2000));
+        mQuotaController.saveTimingSession(0, "com.android.test.frequent",
+                createTimingSession(now - (HOUR_IN_MILLIS), 3 * MINUTE_IN_MILLIS, 500));
+        assertFalse(mQuotaController.isWithinQuotaLocked(0, "com.android.test.frequent",
+                FREQUENT_INDEX));
+    }
+
+    @Test
+    public void testIsWithinQuotaLocked_OverDuration_UnderJobCount() {
+        setDischarging();
+        final long now = JobSchedulerService.sElapsedRealtimeClock.millis();
+        mQuotaController.saveTimingSession(0, "com.android.test",
+                createTimingSession(now - (HOUR_IN_MILLIS), 3 * MINUTE_IN_MILLIS, 5));
+        mQuotaController.saveTimingSession(0, "com.android.test",
+                createTimingSession(now - (30 * MINUTE_IN_MILLIS), 3 * MINUTE_IN_MILLIS, 5));
+        mQuotaController.saveTimingSession(0, "com.android.test",
+                createTimingSession(now - (5 * MINUTE_IN_MILLIS), 4 * MINUTE_IN_MILLIS, 5));
+        mQuotaController.incrementJobCount(0, "com.android.test", 5);
+        assertFalse(mQuotaController.isWithinQuotaLocked(0, "com.android.test", WORKING_INDEX));
+    }
+
+    @Test
+    public void testIsWithinQuotaLocked_OverDuration_OverJobCount() {
+        setDischarging();
+        final long now = JobSchedulerService.sElapsedRealtimeClock.millis();
+        final int jobCount = mConstants.QUOTA_CONTROLLER_MAX_JOB_COUNT_PER_ALLOWED_TIME;
+        mQuotaController.saveTimingSession(0, "com.android.test",
+                createTimingSession(now - (HOUR_IN_MILLIS), 15 * MINUTE_IN_MILLIS, 25));
+        mQuotaController.saveTimingSession(0, "com.android.test",
+                createTimingSession(now - (5 * MINUTE_IN_MILLIS), 3 * MINUTE_IN_MILLIS, jobCount));
+        mQuotaController.incrementJobCount(0, "com.android.test", jobCount);
+        assertFalse(mQuotaController.isWithinQuotaLocked(0, "com.android.test", WORKING_INDEX));
+    }
+
+    @Test
     public void testMaybeScheduleCleanupAlarmLocked() {
         // No sessions saved yet.
         mQuotaController.maybeScheduleCleanupAlarmLocked();
@@ -752,6 +827,7 @@
 
         // Active window size is 10 minutes.
         final int standbyBucket = ACTIVE_INDEX;
+        setProcessState(ActivityManager.PROCESS_STATE_IMPORTANT_FOREGROUND);
 
         // No sessions saved yet.
         mQuotaController.maybeScheduleStartAlarmLocked(SOURCE_USER_ID, SOURCE_PACKAGE,
@@ -1016,11 +1092,37 @@
                 .set(anyInt(), eq(expectedWorkingAlarmTime), eq(TAG_QUOTA_CHECK), any(), any());
 
         mQuotaController.maybeScheduleStartAlarmLocked(0, "com.android.test", ACTIVE_INDEX);
-        inOrder.verify(mAlarmManager, never()).set(anyInt(), anyLong(), eq(TAG_QUOTA_CHECK), any(),
-                any());
+        inOrder.verify(mAlarmManager, never())
+                .set(anyInt(), anyLong(), eq(TAG_QUOTA_CHECK), any(), any());
         inOrder.verify(mAlarmManager, times(1)).cancel(any(AlarmManager.OnAlarmListener.class));
     }
 
+    @Test
+    public void testMaybeScheduleStartAlarmLocked_JobCount_AllowedTime() {
+        final long now = JobSchedulerService.sElapsedRealtimeClock.millis();
+        final int standbyBucket = WORKING_INDEX;
+        ExecutionStats stats = mQuotaController.getExecutionStatsLocked(SOURCE_USER_ID,
+                SOURCE_PACKAGE, standbyBucket);
+        stats.jobCountInAllowedTime =
+                mConstants.QUOTA_CONTROLLER_MAX_JOB_COUNT_PER_ALLOWED_TIME + 2;
+
+        // Invalid time in the past, so the count shouldn't be used.
+        stats.jobCountExpirationTimeElapsed =
+                now - mQuotaController.getAllowedTimePerPeriodMs() / 2;
+        mQuotaController.maybeScheduleStartAlarmLocked(
+                SOURCE_USER_ID, SOURCE_PACKAGE, standbyBucket);
+        verify(mAlarmManager, never()).set(anyInt(), anyLong(), eq(TAG_QUOTA_CHECK), any(), any());
+
+        // Invalid time in the future, so the count should be used.
+        stats.jobCountExpirationTimeElapsed =
+                now + mQuotaController.getAllowedTimePerPeriodMs() / 2;
+        final long expectedWorkingAlarmTime =
+                stats.jobCountExpirationTimeElapsed + mQuotaController.getAllowedTimePerPeriodMs();
+        mQuotaController.maybeScheduleStartAlarmLocked(
+                SOURCE_USER_ID, SOURCE_PACKAGE, standbyBucket);
+        verify(mAlarmManager, times(1))
+                .set(anyInt(), eq(expectedWorkingAlarmTime), eq(TAG_QUOTA_CHECK), any(), any());
+    }
 
     /**
      * Tests that the start alarm is properly rescheduled if the earliest session that contributes
@@ -1172,6 +1274,11 @@
         mConstants.QUOTA_CONTROLLER_WINDOW_SIZE_FREQUENT_MS = 45 * MINUTE_IN_MILLIS;
         mConstants.QUOTA_CONTROLLER_WINDOW_SIZE_RARE_MS = 60 * MINUTE_IN_MILLIS;
         mConstants.QUOTA_CONTROLLER_MAX_EXECUTION_TIME_MS = 3 * HOUR_IN_MILLIS;
+        mConstants.QUOTA_CONTROLLER_MAX_JOB_COUNT_ACTIVE = 5000;
+        mConstants.QUOTA_CONTROLLER_MAX_JOB_COUNT_WORKING = 4000;
+        mConstants.QUOTA_CONTROLLER_MAX_JOB_COUNT_FREQUENT = 3000;
+        mConstants.QUOTA_CONTROLLER_MAX_JOB_COUNT_RARE = 2000;
+        mConstants.QUOTA_CONTROLLER_MAX_JOB_COUNT_PER_ALLOWED_TIME = 500;
 
         mQuotaController.onConstantsUpdatedLocked();
 
@@ -1183,11 +1290,16 @@
                 mQuotaController.getBucketWindowSizes()[FREQUENT_INDEX]);
         assertEquals(60 * MINUTE_IN_MILLIS, mQuotaController.getBucketWindowSizes()[RARE_INDEX]);
         assertEquals(3 * HOUR_IN_MILLIS, mQuotaController.getMaxExecutionTimeMs());
+        assertEquals(500, mQuotaController.getMaxJobCountPerAllowedTime());
+        assertEquals(5000, mQuotaController.getBucketMaxJobCounts()[ACTIVE_INDEX]);
+        assertEquals(4000, mQuotaController.getBucketMaxJobCounts()[WORKING_INDEX]);
+        assertEquals(3000, mQuotaController.getBucketMaxJobCounts()[FREQUENT_INDEX]);
+        assertEquals(2000, mQuotaController.getBucketMaxJobCounts()[RARE_INDEX]);
     }
 
     @Test
     public void testConstantsUpdating_InvalidValues() {
-        // Test negatives
+        // Test negatives/too low.
         mConstants.QUOTA_CONTROLLER_ALLOWED_TIME_PER_PERIOD_MS = -MINUTE_IN_MILLIS;
         mConstants.QUOTA_CONTROLLER_IN_QUOTA_BUFFER_MS = -MINUTE_IN_MILLIS;
         mConstants.QUOTA_CONTROLLER_WINDOW_SIZE_ACTIVE_MS = -MINUTE_IN_MILLIS;
@@ -1195,6 +1307,11 @@
         mConstants.QUOTA_CONTROLLER_WINDOW_SIZE_FREQUENT_MS = -MINUTE_IN_MILLIS;
         mConstants.QUOTA_CONTROLLER_WINDOW_SIZE_RARE_MS = -MINUTE_IN_MILLIS;
         mConstants.QUOTA_CONTROLLER_MAX_EXECUTION_TIME_MS = -MINUTE_IN_MILLIS;
+        mConstants.QUOTA_CONTROLLER_MAX_JOB_COUNT_ACTIVE = -1;
+        mConstants.QUOTA_CONTROLLER_MAX_JOB_COUNT_WORKING = 1;
+        mConstants.QUOTA_CONTROLLER_MAX_JOB_COUNT_FREQUENT = 1;
+        mConstants.QUOTA_CONTROLLER_MAX_JOB_COUNT_RARE = 1;
+        mConstants.QUOTA_CONTROLLER_MAX_JOB_COUNT_PER_ALLOWED_TIME = 0;
 
         mQuotaController.onConstantsUpdatedLocked();
 
@@ -1205,6 +1322,11 @@
         assertEquals(MINUTE_IN_MILLIS, mQuotaController.getBucketWindowSizes()[FREQUENT_INDEX]);
         assertEquals(MINUTE_IN_MILLIS, mQuotaController.getBucketWindowSizes()[RARE_INDEX]);
         assertEquals(HOUR_IN_MILLIS, mQuotaController.getMaxExecutionTimeMs());
+        assertEquals(10, mQuotaController.getMaxJobCountPerAllowedTime());
+        assertEquals(100, mQuotaController.getBucketMaxJobCounts()[ACTIVE_INDEX]);
+        assertEquals(100, mQuotaController.getBucketMaxJobCounts()[WORKING_INDEX]);
+        assertEquals(100, mQuotaController.getBucketMaxJobCounts()[FREQUENT_INDEX]);
+        assertEquals(100, mQuotaController.getBucketMaxJobCounts()[RARE_INDEX]);
 
         // Test larger than a day. Controller should cap at one day.
         mConstants.QUOTA_CONTROLLER_ALLOWED_TIME_PER_PERIOD_MS = 25 * HOUR_IN_MILLIS;
@@ -1246,6 +1368,7 @@
     @Test
     public void testTimerTracking_Discharging() {
         setDischarging();
+        setProcessState(ActivityManager.PROCESS_STATE_BACKUP);
 
         JobStatus jobStatus = createJobStatus("testTimerTracking_Discharging", 1);
         mQuotaController.maybeStartTrackingJobLocked(jobStatus, null);
@@ -1293,6 +1416,8 @@
      */
     @Test
     public void testTimerTracking_ChargingAndDischarging() {
+        setProcessState(ActivityManager.PROCESS_STATE_IMPORTANT_BACKGROUND);
+
         JobStatus jobStatus = createJobStatus("testTimerTracking_ChargingAndDischarging", 1);
         mQuotaController.maybeStartTrackingJobLocked(jobStatus, null);
         JobStatus jobStatus2 = createJobStatus("testTimerTracking_ChargingAndDischarging", 2);
@@ -1363,6 +1488,7 @@
     @Test
     public void testTimerTracking_AllBackground() {
         setDischarging();
+        setProcessState(ActivityManager.PROCESS_STATE_RECEIVER);
 
         JobStatus jobStatus = createJobStatus("testTimerTracking_AllBackground", 1);
         mQuotaController.maybeStartTrackingJobLocked(jobStatus, null);
@@ -1503,6 +1629,64 @@
     }
 
     /**
+     * Tests that Timers don't track job counts while in the foreground.
+     */
+    @Test
+    public void testTimerTracking_JobCount_Foreground() {
+        setDischarging();
+
+        final int standbyBucket = ACTIVE_INDEX;
+        JobStatus jobFg1 = createJobStatus("testTimerTracking_JobCount_Foreground", 1);
+        JobStatus jobFg2 = createJobStatus("testTimerTracking_JobCount_Foreground", 2);
+
+        mQuotaController.maybeStartTrackingJobLocked(jobFg1, null);
+        mQuotaController.maybeStartTrackingJobLocked(jobFg2, null);
+        assertNull(mQuotaController.getTimingSessions(SOURCE_USER_ID, SOURCE_PACKAGE));
+        ExecutionStats stats = mQuotaController.getExecutionStatsLocked(SOURCE_USER_ID,
+                SOURCE_PACKAGE, standbyBucket);
+        assertEquals(0, stats.jobCountInAllowedTime);
+
+        setProcessState(ActivityManager.PROCESS_STATE_FOREGROUND_SERVICE);
+        mQuotaController.prepareForExecutionLocked(jobFg1);
+        advanceElapsedClock(10 * SECOND_IN_MILLIS);
+        mQuotaController.prepareForExecutionLocked(jobFg2);
+        advanceElapsedClock(10 * SECOND_IN_MILLIS);
+        mQuotaController.maybeStopTrackingJobLocked(jobFg1, null, false);
+        advanceElapsedClock(10 * SECOND_IN_MILLIS);
+        mQuotaController.maybeStopTrackingJobLocked(jobFg2, null, false);
+        assertNull(mQuotaController.getTimingSessions(SOURCE_USER_ID, SOURCE_PACKAGE));
+
+        assertEquals(0, stats.jobCountInAllowedTime);
+    }
+
+    /**
+     * Tests that Timers properly track job counts while in the background.
+     */
+    @Test
+    public void testTimerTracking_JobCount_Background() {
+        final int standbyBucket = WORKING_INDEX;
+        JobStatus jobBg1 = createJobStatus("testTimerTracking_JobCount_Background", 1);
+        JobStatus jobBg2 = createJobStatus("testTimerTracking_JobCount_Background", 2);
+        mQuotaController.maybeStartTrackingJobLocked(jobBg1, null);
+        mQuotaController.maybeStartTrackingJobLocked(jobBg2, null);
+
+        ExecutionStats stats = mQuotaController.getExecutionStatsLocked(SOURCE_USER_ID,
+                SOURCE_PACKAGE, standbyBucket);
+        assertEquals(0, stats.jobCountInAllowedTime);
+
+        setProcessState(ActivityManager.PROCESS_STATE_TOP_SLEEPING);
+        mQuotaController.prepareForExecutionLocked(jobBg1);
+        advanceElapsedClock(10 * SECOND_IN_MILLIS);
+        mQuotaController.prepareForExecutionLocked(jobBg2);
+        advanceElapsedClock(10 * SECOND_IN_MILLIS);
+        mQuotaController.maybeStopTrackingJobLocked(jobBg1, null, false);
+        advanceElapsedClock(10 * SECOND_IN_MILLIS);
+        mQuotaController.maybeStopTrackingJobLocked(jobBg2, null, false);
+
+        assertEquals(2, stats.jobCountInAllowedTime);
+    }
+
+    /**
      * Tests that Timers properly track overlapping top and background jobs.
      */
     @Test
@@ -1680,6 +1864,7 @@
         JobStatus jobStatus = createJobStatus("testTracking_OutOfQuota", 1);
         mQuotaController.maybeStartTrackingJobLocked(jobStatus, null);
         setStandbyBucket(WORKING_INDEX, jobStatus); // 2 hour window
+        setProcessState(ActivityManager.PROCESS_STATE_HOME);
         // Now the package only has two seconds to run.
         final long remainingTimeMs = 2 * SECOND_IN_MILLIS;
         mQuotaController.saveTimingSession(SOURCE_USER_ID, SOURCE_PACKAGE,
@@ -1707,6 +1892,7 @@
         JobStatus jobStatus = createJobStatus("testTracking_OutOfQuota", 1);
         mQuotaController.maybeStartTrackingJobLocked(jobStatus, null);
         setStandbyBucket(WORKING_INDEX, jobStatus); // 2 hour window
+        setProcessState(ActivityManager.PROCESS_STATE_SERVICE);
         Handler handler = mQuotaController.getHandler();
         spyOn(handler);
 
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/AndroidManifest.xml b/services/tests/servicestests/AndroidManifest.xml
index 1b5ba26..dc31c0f 100644
--- a/services/tests/servicestests/AndroidManifest.xml
+++ b/services/tests/servicestests/AndroidManifest.xml
@@ -156,6 +156,7 @@
         </activity>
 
         <activity android:name="com.android.server.accounts.AccountAuthenticatorDummyActivity" />
+        <activity android:name="com.android.server.adb.AdbDebuggingManagerTestActivity" />
 
         <activity-alias android:name="a.ShortcutEnabled"
             android:targetActivity="com.android.server.pm.ShortcutTestActivity"
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/MagnificationControllerTest.java b/services/tests/servicestests/src/com/android/server/accessibility/MagnificationControllerTest.java
index feffeef..773b877 100644
--- a/services/tests/servicestests/src/com/android/server/accessibility/MagnificationControllerTest.java
+++ b/services/tests/servicestests/src/com/android/server/accessibility/MagnificationControllerTest.java
@@ -20,7 +20,9 @@
 import static org.junit.Assert.assertFalse;
 import static org.junit.Assert.assertThat;
 import static org.junit.Assert.assertTrue;
+import static org.mockito.ArgumentMatchers.any;
 import static org.mockito.ArgumentMatchers.anyFloat;
+import static org.mockito.ArgumentMatchers.anyInt;
 import static org.mockito.Matchers.anyObject;
 import static org.mockito.Matchers.eq;
 import static org.mockito.Mockito.doAnswer;
@@ -36,19 +38,15 @@
 import android.content.BroadcastReceiver;
 import android.content.Context;
 import android.content.IntentFilter;
-import android.content.res.Resources;
 import android.graphics.PointF;
 import android.graphics.Rect;
 import android.graphics.Region;
-import android.os.Handler;
 import android.os.Looper;
-import android.os.Message;
 import android.view.MagnificationSpec;
 
 import androidx.test.InstrumentationRegistry;
 import androidx.test.runner.AndroidJUnit4;
 
-import com.android.internal.R;
 import com.android.server.wm.WindowManagerInternal;
 import com.android.server.wm.WindowManagerInternal.MagnificationCallbacks;
 
@@ -81,48 +79,36 @@
     static final Region OTHER_REGION = new Region(OTHER_MAGNIFICATION_BOUNDS);
     static final int SERVICE_ID_1 = 1;
     static final int SERVICE_ID_2 = 2;
+    static final int DISPLAY_0 = 0;
+    static final int DISPLAY_1 = 1;
+    static final int DISPLAY_COUNT = 2;
+    static final int INVALID_DISPLAY = 2;
 
+    final MagnificationController.ControllerContext mMockControllerCtx =
+            mock(MagnificationController.ControllerContext.class);
     final Context mMockContext = mock(Context.class);
     final AccessibilityManagerService mMockAms = mock(AccessibilityManagerService.class);
     final WindowManagerInternal mMockWindowManager = mock(WindowManagerInternal.class);
-    final MessageCapturingHandler mMessageCapturingHandler =
-            new MessageCapturingHandler(null);
+    final MessageCapturingHandler mMessageCapturingHandler = new MessageCapturingHandler(null);
 
-    final ValueAnimator mMockValueAnimator = mock(ValueAnimator.class);
-    MagnificationController.SettingsBridge mMockSettingsBridge;
+    ValueAnimator mMockValueAnimator;
+    ValueAnimator.AnimatorUpdateListener mTargetAnimationListener;
 
     MagnificationController mMagnificationController;
-    ValueAnimator.AnimatorUpdateListener mTargetAnimationListener;
 
     @Before
     public void setUp() {
         Looper looper = InstrumentationRegistry.getContext().getMainLooper();
         // Pretending ID of the Thread associated with looper as main thread ID in controller
         when(mMockContext.getMainLooper()).thenReturn(looper);
-        Resources mockResources = mock(Resources.class);
-        when(mMockContext.getResources()).thenReturn(mockResources);
-        when(mockResources.getInteger(R.integer.config_longAnimTime))
-                .thenReturn(1000);
-        mMockSettingsBridge = mock(MagnificationController.SettingsBridge.class);
-        mMagnificationController = new MagnificationController(mMockContext, mMockAms, new Object(),
-                mMessageCapturingHandler, mMockWindowManager, mMockValueAnimator,
-                mMockSettingsBridge);
+        when(mMockControllerCtx.getContext()).thenReturn(mMockContext);
+        when(mMockControllerCtx.getAms()).thenReturn(mMockAms);
+        when(mMockControllerCtx.getWindowManager()).thenReturn(mMockWindowManager);
+        when(mMockControllerCtx.getHandler()).thenReturn(mMessageCapturingHandler);
+        when(mMockControllerCtx.getAnimationDuration()).thenReturn(1000L);
+        initMockWindowManager();
 
-        doAnswer(new Answer<Void>() {
-            @Override
-            public Void answer(InvocationOnMock invocationOnMock) throws Throwable {
-                Object[] args = invocationOnMock.getArguments();
-                Region regionArg = (Region) args[0];
-                regionArg.set(INITIAL_MAGNIFICATION_REGION);
-                return null;
-            }
-        }).when(mMockWindowManager).getMagnificationRegion((Region) anyObject());
-
-        ArgumentCaptor<ValueAnimator.AnimatorUpdateListener> listenerArgumentCaptor =
-                ArgumentCaptor.forClass(ValueAnimator.AnimatorUpdateListener.class);
-        verify(mMockValueAnimator).addUpdateListener(listenerArgumentCaptor.capture());
-        mTargetAnimationListener = listenerArgumentCaptor.getValue();
-        Mockito.reset(mMockValueAnimator); // Ignore other initialization
+        mMagnificationController = new MagnificationController(mMockControllerCtx, new Object());
     }
 
     @After
@@ -133,88 +119,132 @@
 
     @Test
     public void testRegister_WindowManagerAndContextRegisterListeners() {
-        mMagnificationController.register();
+        register(DISPLAY_0);
+        register(DISPLAY_1);
+        register(INVALID_DISPLAY);
         verify(mMockContext).registerReceiver(
                 (BroadcastReceiver) anyObject(), (IntentFilter) anyObject());
-        verify(mMockWindowManager).setMagnificationCallbacks((MagnificationCallbacks) anyObject());
-        assertTrue(mMagnificationController.isRegisteredLocked());
+        verify(mMockWindowManager).setMagnificationCallbacks(
+                eq(DISPLAY_0), (MagnificationCallbacks) anyObject());
+        verify(mMockWindowManager).setMagnificationCallbacks(
+                eq(DISPLAY_1), (MagnificationCallbacks) anyObject());
+        verify(mMockWindowManager).setMagnificationCallbacks(
+                eq(INVALID_DISPLAY), (MagnificationCallbacks) anyObject());
+        assertTrue(mMagnificationController.isRegistered(DISPLAY_0));
+        assertTrue(mMagnificationController.isRegistered(DISPLAY_1));
+        assertFalse(mMagnificationController.isRegistered(INVALID_DISPLAY));
     }
 
     @Test
     public void testRegister_WindowManagerAndContextUnregisterListeners() {
-        mMagnificationController.register();
-        mMagnificationController.unregister();
-
+        register(DISPLAY_0);
+        register(DISPLAY_1);
+        mMagnificationController.unregister(DISPLAY_0);
+        verify(mMockContext, times(0)).unregisterReceiver((BroadcastReceiver) anyObject());
+        mMagnificationController.unregister(DISPLAY_1);
         verify(mMockContext).unregisterReceiver((BroadcastReceiver) anyObject());
-        verify(mMockWindowManager).setMagnificationCallbacks(null);
-        assertFalse(mMagnificationController.isRegisteredLocked());
+        verify(mMockWindowManager).setMagnificationCallbacks(eq(DISPLAY_0), eq(null));
+        verify(mMockWindowManager).setMagnificationCallbacks(eq(DISPLAY_1), eq(null));
+        assertFalse(mMagnificationController.isRegistered(DISPLAY_0));
+        assertFalse(mMagnificationController.isRegistered(DISPLAY_1));
     }
 
     @Test
     public void testInitialState_noMagnificationAndMagnificationRegionReadFromWindowManager() {
-        mMagnificationController.register();
+        for (int i = 0; i < DISPLAY_COUNT; i++) {
+            initialState_noMagnificationAndMagnificationRegionReadFromWindowManager(i);
+            resetMockWindowManager();
+        }
+    }
+
+    private void initialState_noMagnificationAndMagnificationRegionReadFromWindowManager(
+            int displayId) {
+        register(displayId);
         MagnificationSpec expectedInitialSpec = getMagnificationSpec(1.0f, 0.0f, 0.0f);
         Region initialMagRegion = new Region();
         Rect initialBounds = new Rect();
 
-        assertEquals(expectedInitialSpec, getCurrentMagnificationSpec());
-        mMagnificationController.getMagnificationRegion(initialMagRegion);
-        mMagnificationController.getMagnificationBounds(initialBounds);
+        assertEquals(expectedInitialSpec, getCurrentMagnificationSpec(displayId));
+        mMagnificationController.getMagnificationRegion(displayId, initialMagRegion);
+        mMagnificationController.getMagnificationBounds(displayId, initialBounds);
         assertEquals(INITIAL_MAGNIFICATION_REGION, initialMagRegion);
         assertEquals(INITIAL_MAGNIFICATION_BOUNDS, initialBounds);
         assertEquals(INITIAL_MAGNIFICATION_BOUNDS.centerX(),
-                mMagnificationController.getCenterX(), 0.0f);
+                mMagnificationController.getCenterX(displayId), 0.0f);
         assertEquals(INITIAL_MAGNIFICATION_BOUNDS.centerY(),
-                mMagnificationController.getCenterY(), 0.0f);
+                mMagnificationController.getCenterY(displayId), 0.0f);
     }
 
     @Test
     public void testNotRegistered_publicMethodsShouldBeBenign() {
-        assertFalse(mMagnificationController.isMagnifying());
-        assertFalse(mMagnificationController.magnificationRegionContains(100, 100));
-        assertFalse(mMagnificationController.reset(true));
-        assertFalse(mMagnificationController.setScale(2, 100, 100, true, 0));
-        assertFalse(mMagnificationController.setCenter(100, 100, false, 1));
-        assertFalse(mMagnificationController.setScaleAndCenter(1.5f, 100, 100, false, 2));
-        assertTrue(mMagnificationController.getIdOfLastServiceToMagnify() < 0);
+        for (int i = 0; i < DISPLAY_COUNT; i++) {
+            notRegistered_publicMethodsShouldBeBenign(i);
+            resetMockWindowManager();
+        }
+    }
 
-        mMagnificationController.getMagnificationRegion(new Region());
-        mMagnificationController.getMagnificationBounds(new Rect());
-        mMagnificationController.getScale();
-        mMagnificationController.getOffsetX();
-        mMagnificationController.getOffsetY();
-        mMagnificationController.getCenterX();
-        mMagnificationController.getCenterY();
-        mMagnificationController.offsetMagnifiedRegion(50, 50, 1);
-        mMagnificationController.unregister();
+    private void notRegistered_publicMethodsShouldBeBenign(int displayId) {
+        assertFalse(mMagnificationController.isMagnifying(displayId));
+        assertFalse(mMagnificationController.magnificationRegionContains(displayId, 100, 100));
+        assertFalse(mMagnificationController.reset(displayId, true));
+        assertFalse(mMagnificationController.setScale(displayId, 2, 100, 100, true, 0));
+        assertFalse(mMagnificationController.setCenter(displayId, 100, 100, false, 1));
+        assertFalse(mMagnificationController.setScaleAndCenter(displayId,
+                1.5f, 100, 100, false, 2));
+        assertTrue(mMagnificationController.getIdOfLastServiceToMagnify(displayId) < 0);
+
+        mMagnificationController.getMagnificationRegion(displayId, new Region());
+        mMagnificationController.getMagnificationBounds(displayId, new Rect());
+        mMagnificationController.getScale(displayId);
+        mMagnificationController.getOffsetX(displayId);
+        mMagnificationController.getOffsetY(displayId);
+        mMagnificationController.getCenterX(displayId);
+        mMagnificationController.getCenterY(displayId);
+        mMagnificationController.offsetMagnifiedRegion(displayId, 50, 50, 1);
+        mMagnificationController.unregister(displayId);
     }
 
     @Test
     public void testSetScale_noAnimation_shouldGoStraightToWindowManagerAndUpdateState() {
-        mMagnificationController.register();
+        for (int i = 0; i < DISPLAY_COUNT; i++) {
+            setScale_noAnimation_shouldGoStraightToWindowManagerAndUpdateState(i);
+            resetMockWindowManager();
+        }
+    }
+
+    private void setScale_noAnimation_shouldGoStraightToWindowManagerAndUpdateState(int displayId) {
+        register(displayId);
         final float scale = 2.0f;
         final PointF center = INITIAL_MAGNIFICATION_BOUNDS_CENTER;
         final PointF offsets = computeOffsets(INITIAL_MAGNIFICATION_BOUNDS, center, scale);
         assertTrue(mMagnificationController
-                .setScale(scale, center.x, center.y, false, SERVICE_ID_1));
+                .setScale(displayId, scale, center.x, center.y, false, SERVICE_ID_1));
         mMessageCapturingHandler.sendAllMessages();
 
         final MagnificationSpec expectedSpec = getMagnificationSpec(scale, offsets);
-        verify(mMockWindowManager).setMagnificationSpec(argThat(closeTo(expectedSpec)));
-        assertThat(getCurrentMagnificationSpec(), closeTo(expectedSpec));
-        assertEquals(center.x, mMagnificationController.getCenterX(), 0.0);
-        assertEquals(center.y, mMagnificationController.getCenterY(), 0.0);
+        verify(mMockWindowManager).setMagnificationSpec(
+                eq(displayId), argThat(closeTo(expectedSpec)));
+        assertThat(getCurrentMagnificationSpec(displayId), closeTo(expectedSpec));
+        assertEquals(center.x, mMagnificationController.getCenterX(displayId), 0.0);
+        assertEquals(center.y, mMagnificationController.getCenterY(displayId), 0.0);
         verify(mMockValueAnimator, times(0)).start();
     }
 
     @Test
     public void testSetScale_withPivotAndAnimation_stateChangesAndAnimationHappens() {
-        mMagnificationController.register();
-        MagnificationSpec startSpec = getCurrentMagnificationSpec();
+        for (int i = 0; i < DISPLAY_COUNT; i++) {
+            setScale_withPivotAndAnimation_stateChangesAndAnimationHappens(i);
+            resetMockWindowManager();
+        }
+    }
+
+    private void setScale_withPivotAndAnimation_stateChangesAndAnimationHappens(int displayId) {
+        register(displayId);
+        MagnificationSpec startSpec = getCurrentMagnificationSpec(displayId);
         float scale = 2.0f;
         PointF pivotPoint = INITIAL_BOUNDS_LOWER_RIGHT_2X_CENTER;
         assertTrue(mMagnificationController
-                .setScale(scale, pivotPoint.x, pivotPoint.y, true, SERVICE_ID_1));
+                .setScale(displayId, scale, pivotPoint.x, pivotPoint.y, true, SERVICE_ID_1));
         mMessageCapturingHandler.sendAllMessages();
 
         // New center should be halfway between original center and pivot
@@ -223,467 +253,645 @@
         PointF offsets = computeOffsets(INITIAL_MAGNIFICATION_BOUNDS, newCenter, scale);
         MagnificationSpec endSpec = getMagnificationSpec(scale, offsets);
 
-        assertEquals(newCenter.x, mMagnificationController.getCenterX(), 0.5);
-        assertEquals(newCenter.y, mMagnificationController.getCenterY(), 0.5);
-        assertThat(getCurrentMagnificationSpec(), closeTo(endSpec));
+        assertEquals(newCenter.x, mMagnificationController.getCenterX(displayId), 0.5);
+        assertEquals(newCenter.y, mMagnificationController.getCenterY(displayId), 0.5);
+        assertThat(getCurrentMagnificationSpec(displayId), closeTo(endSpec));
         verify(mMockValueAnimator).start();
 
         // Initial value
         when(mMockValueAnimator.getAnimatedFraction()).thenReturn(0.0f);
         mTargetAnimationListener.onAnimationUpdate(mMockValueAnimator);
-        verify(mMockWindowManager).setMagnificationSpec(startSpec);
+        verify(mMockWindowManager).setMagnificationSpec(eq(displayId), eq(startSpec));
 
         // Intermediate point
         Mockito.reset(mMockWindowManager);
         float fraction = 0.5f;
         when(mMockValueAnimator.getAnimatedFraction()).thenReturn(fraction);
         mTargetAnimationListener.onAnimationUpdate(mMockValueAnimator);
-        verify(mMockWindowManager).setMagnificationSpec(
+        verify(mMockWindowManager).setMagnificationSpec(eq(displayId),
                 argThat(closeTo(getInterpolatedMagSpec(startSpec, endSpec, fraction))));
 
         // Final value
         Mockito.reset(mMockWindowManager);
         when(mMockValueAnimator.getAnimatedFraction()).thenReturn(1.0f);
         mTargetAnimationListener.onAnimationUpdate(mMockValueAnimator);
-        verify(mMockWindowManager).setMagnificationSpec(argThat(closeTo(endSpec)));
+        verify(mMockWindowManager).setMagnificationSpec(eq(displayId), argThat(closeTo(endSpec)));
     }
 
     @Test
     public void testSetCenter_whileMagnifying_noAnimation_centerMoves() {
-        mMagnificationController.register();
+        for (int i = 0; i < DISPLAY_COUNT; i++) {
+            setCenter_whileMagnifying_noAnimation_centerMoves(i);
+            resetMockWindowManager();
+        }
+    }
+
+    private void setCenter_whileMagnifying_noAnimation_centerMoves(int displayId) {
+        register(displayId);
         // First zoom in
         float scale = 2.0f;
-        assertTrue(mMagnificationController.setScale(scale,
+        assertTrue(mMagnificationController.setScale(displayId, scale,
                 INITIAL_MAGNIFICATION_BOUNDS.centerX(), INITIAL_MAGNIFICATION_BOUNDS.centerY(),
                 false, SERVICE_ID_1));
         Mockito.reset(mMockWindowManager);
 
         PointF newCenter = INITIAL_BOUNDS_LOWER_RIGHT_2X_CENTER;
         assertTrue(mMagnificationController
-                .setCenter(newCenter.x, newCenter.y, false, SERVICE_ID_1));
+                .setCenter(displayId, newCenter.x, newCenter.y, false, SERVICE_ID_1));
         mMessageCapturingHandler.sendAllMessages();
         PointF expectedOffsets = computeOffsets(INITIAL_MAGNIFICATION_BOUNDS, newCenter, scale);
         MagnificationSpec expectedSpec = getMagnificationSpec(scale, expectedOffsets);
 
-        verify(mMockWindowManager).setMagnificationSpec(argThat(closeTo(expectedSpec)));
-        assertEquals(newCenter.x, mMagnificationController.getCenterX(), 0.0);
-        assertEquals(newCenter.y, mMagnificationController.getCenterY(), 0.0);
+        verify(mMockWindowManager).setMagnificationSpec(
+                eq(displayId), argThat(closeTo(expectedSpec)));
+        assertEquals(newCenter.x, mMagnificationController.getCenterX(displayId), 0.0);
+        assertEquals(newCenter.y, mMagnificationController.getCenterY(displayId), 0.0);
         verify(mMockValueAnimator, times(0)).start();
     }
 
     @Test
     public void testSetScaleAndCenter_animated_stateChangesAndAnimationHappens() {
-        mMagnificationController.register();
-        MagnificationSpec startSpec = getCurrentMagnificationSpec();
+        for (int i = 0; i < DISPLAY_COUNT; i++) {
+            setScaleAndCenter_animated_stateChangesAndAnimationHappens(i);
+            resetMockWindowManager();
+        }
+    }
+
+    private void setScaleAndCenter_animated_stateChangesAndAnimationHappens(int displayId) {
+        register(displayId);
+        MagnificationSpec startSpec = getCurrentMagnificationSpec(displayId);
         float scale = 2.5f;
         PointF newCenter = INITIAL_BOUNDS_LOWER_RIGHT_2X_CENTER;
         PointF offsets = computeOffsets(INITIAL_MAGNIFICATION_BOUNDS, newCenter, scale);
         MagnificationSpec endSpec = getMagnificationSpec(scale, offsets);
 
-        assertTrue(mMagnificationController.setScaleAndCenter(scale, newCenter.x, newCenter.y,
-                true, SERVICE_ID_1));
+        assertTrue(mMagnificationController.setScaleAndCenter(displayId, scale, newCenter.x,
+                newCenter.y, true, SERVICE_ID_1));
         mMessageCapturingHandler.sendAllMessages();
 
-        assertEquals(newCenter.x, mMagnificationController.getCenterX(), 0.5);
-        assertEquals(newCenter.y, mMagnificationController.getCenterY(), 0.5);
-        assertThat(getCurrentMagnificationSpec(), closeTo(endSpec));
-        verify(mMockAms).notifyMagnificationChanged(
+        assertEquals(newCenter.x, mMagnificationController.getCenterX(displayId), 0.5);
+        assertEquals(newCenter.y, mMagnificationController.getCenterY(displayId), 0.5);
+        assertThat(getCurrentMagnificationSpec(displayId), closeTo(endSpec));
+        verify(mMockAms).notifyMagnificationChanged(displayId,
                 INITIAL_MAGNIFICATION_REGION, scale, newCenter.x, newCenter.y);
         verify(mMockValueAnimator).start();
 
         // Initial value
         when(mMockValueAnimator.getAnimatedFraction()).thenReturn(0.0f);
         mTargetAnimationListener.onAnimationUpdate(mMockValueAnimator);
-        verify(mMockWindowManager).setMagnificationSpec(startSpec);
+        verify(mMockWindowManager).setMagnificationSpec(eq(displayId), eq(startSpec));
 
         // Intermediate point
         Mockito.reset(mMockWindowManager);
         float fraction = 0.33f;
         when(mMockValueAnimator.getAnimatedFraction()).thenReturn(fraction);
         mTargetAnimationListener.onAnimationUpdate(mMockValueAnimator);
-        verify(mMockWindowManager).setMagnificationSpec(
+        verify(mMockWindowManager).setMagnificationSpec(eq(displayId),
                 argThat(closeTo(getInterpolatedMagSpec(startSpec, endSpec, fraction))));
 
         // Final value
         Mockito.reset(mMockWindowManager);
         when(mMockValueAnimator.getAnimatedFraction()).thenReturn(1.0f);
         mTargetAnimationListener.onAnimationUpdate(mMockValueAnimator);
-        verify(mMockWindowManager).setMagnificationSpec(argThat(closeTo(endSpec)));
+        verify(mMockWindowManager).setMagnificationSpec(eq(displayId), argThat(closeTo(endSpec)));
     }
 
     @Test
     public void testSetScaleAndCenter_scaleOutOfBounds_cappedAtLimits() {
-        mMagnificationController.register();
-        MagnificationSpec startSpec = getCurrentMagnificationSpec();
+        for (int i = 0; i < DISPLAY_COUNT; i++) {
+            setScaleAndCenter_scaleOutOfBounds_cappedAtLimits(i);
+            resetMockWindowManager();
+        }
+    }
+
+    private void setScaleAndCenter_scaleOutOfBounds_cappedAtLimits(int displayId) {
+        register(displayId);
+        MagnificationSpec startSpec = getCurrentMagnificationSpec(displayId);
         PointF newCenter = INITIAL_BOUNDS_LOWER_RIGHT_2X_CENTER;
         PointF offsets = computeOffsets(INITIAL_MAGNIFICATION_BOUNDS, newCenter,
                 MagnificationController.MAX_SCALE);
         MagnificationSpec endSpec = getMagnificationSpec(
                 MagnificationController.MAX_SCALE, offsets);
 
-        assertTrue(mMagnificationController.setScaleAndCenter(
+        assertTrue(mMagnificationController.setScaleAndCenter(displayId,
                 MagnificationController.MAX_SCALE + 1.0f,
                 newCenter.x, newCenter.y, false, SERVICE_ID_1));
         mMessageCapturingHandler.sendAllMessages();
 
-        assertEquals(newCenter.x, mMagnificationController.getCenterX(), 0.5);
-        assertEquals(newCenter.y, mMagnificationController.getCenterY(), 0.5);
-        verify(mMockWindowManager).setMagnificationSpec(argThat(closeTo(endSpec)));
+        assertEquals(newCenter.x, mMagnificationController.getCenterX(displayId), 0.5);
+        assertEquals(newCenter.y, mMagnificationController.getCenterY(displayId), 0.5);
+        verify(mMockWindowManager).setMagnificationSpec(eq(displayId), argThat(closeTo(endSpec)));
         Mockito.reset(mMockWindowManager);
 
         // Verify that we can't zoom below 1x
-        assertTrue(mMagnificationController.setScaleAndCenter(0.5f,
+        assertTrue(mMagnificationController.setScaleAndCenter(displayId, 0.5f,
                 INITIAL_MAGNIFICATION_BOUNDS_CENTER.x, INITIAL_MAGNIFICATION_BOUNDS_CENTER.y,
                 false, SERVICE_ID_1));
         mMessageCapturingHandler.sendAllMessages();
 
         assertEquals(INITIAL_MAGNIFICATION_BOUNDS_CENTER.x,
-                mMagnificationController.getCenterX(), 0.5);
+                mMagnificationController.getCenterX(displayId), 0.5);
         assertEquals(INITIAL_MAGNIFICATION_BOUNDS_CENTER.y,
-                mMagnificationController.getCenterY(), 0.5);
-        verify(mMockWindowManager).setMagnificationSpec(argThat(closeTo(startSpec)));
+                mMagnificationController.getCenterY(displayId), 0.5);
+        verify(mMockWindowManager).setMagnificationSpec(eq(displayId), argThat(closeTo(startSpec)));
     }
 
     @Test
     public void testSetScaleAndCenter_centerOutOfBounds_cappedAtLimits() {
-        mMagnificationController.register();
+        for (int i = 0; i < DISPLAY_COUNT; i++) {
+            setScaleAndCenter_centerOutOfBounds_cappedAtLimits(i);
+            resetMockWindowManager();
+        }
+    }
+
+    private void setScaleAndCenter_centerOutOfBounds_cappedAtLimits(int displayId) {
+        register(displayId);
         float scale = 2.0f;
 
         // Off the edge to the top and left
-        assertTrue(mMagnificationController.setScaleAndCenter(
+        assertTrue(mMagnificationController.setScaleAndCenter(displayId,
                 scale, -100f, -200f, false, SERVICE_ID_1));
         mMessageCapturingHandler.sendAllMessages();
 
         PointF newCenter = INITIAL_BOUNDS_UPPER_LEFT_2X_CENTER;
         PointF newOffsets = computeOffsets(INITIAL_MAGNIFICATION_BOUNDS, newCenter, scale);
-        assertEquals(newCenter.x, mMagnificationController.getCenterX(), 0.5);
-        assertEquals(newCenter.y, mMagnificationController.getCenterY(), 0.5);
-        verify(mMockWindowManager).setMagnificationSpec(
+        assertEquals(newCenter.x, mMagnificationController.getCenterX(displayId), 0.5);
+        assertEquals(newCenter.y, mMagnificationController.getCenterY(displayId), 0.5);
+        verify(mMockWindowManager).setMagnificationSpec(eq(displayId),
                 argThat(closeTo(getMagnificationSpec(scale, newOffsets))));
         Mockito.reset(mMockWindowManager);
 
         // Off the edge to the bottom and right
-        assertTrue(mMagnificationController.setScaleAndCenter(scale,
+        assertTrue(mMagnificationController.setScaleAndCenter(displayId, scale,
                 INITIAL_MAGNIFICATION_BOUNDS.right + 1, INITIAL_MAGNIFICATION_BOUNDS.bottom + 1,
                 false, SERVICE_ID_1));
         mMessageCapturingHandler.sendAllMessages();
         newCenter = INITIAL_BOUNDS_LOWER_RIGHT_2X_CENTER;
         newOffsets = computeOffsets(INITIAL_MAGNIFICATION_BOUNDS, newCenter, scale);
-        assertEquals(newCenter.x, mMagnificationController.getCenterX(), 0.5);
-        assertEquals(newCenter.y, mMagnificationController.getCenterY(), 0.5);
-        verify(mMockWindowManager).setMagnificationSpec(
+        assertEquals(newCenter.x, mMagnificationController.getCenterX(displayId), 0.5);
+        assertEquals(newCenter.y, mMagnificationController.getCenterY(displayId), 0.5);
+        verify(mMockWindowManager).setMagnificationSpec(eq(displayId),
                 argThat(closeTo(getMagnificationSpec(scale, newOffsets))));
     }
 
     @Test
     public void testMagnificationRegionChanged_serviceNotified() {
-        mMagnificationController.register();
-        MagnificationCallbacks callbacks = getMagnificationCallbacks();
+        for (int i = 0; i < DISPLAY_COUNT; i++) {
+            magnificationRegionChanged_serviceNotified(i);
+            resetMockWindowManager();
+        }
+    }
+
+    private void magnificationRegionChanged_serviceNotified(int displayId) {
+        register(displayId);
+        MagnificationCallbacks callbacks = getMagnificationCallbacks(displayId);
         callbacks.onMagnificationRegionChanged(OTHER_REGION);
         mMessageCapturingHandler.sendAllMessages();
-        verify(mMockAms).notifyMagnificationChanged(OTHER_REGION, 1.0f,
+        verify(mMockAms).notifyMagnificationChanged(displayId, OTHER_REGION, 1.0f,
                 OTHER_MAGNIFICATION_BOUNDS.centerX(), OTHER_MAGNIFICATION_BOUNDS.centerY());
     }
 
     @Test
     public void testOffsetMagnifiedRegion_whileMagnifying_offsetsMove() {
-        mMagnificationController.register();
+        for (int i = 0; i < DISPLAY_COUNT; i++) {
+            offsetMagnifiedRegion_whileMagnifying_offsetsMove(i);
+            resetMockWindowManager();
+        }
+    }
+
+    private void offsetMagnifiedRegion_whileMagnifying_offsetsMove(int displayId) {
+        register(displayId);
         PointF startCenter = INITIAL_MAGNIFICATION_BOUNDS_CENTER;
         float scale = 2.0f;
         PointF startOffsets = computeOffsets(INITIAL_MAGNIFICATION_BOUNDS, startCenter, scale);
         // First zoom in
         assertTrue(mMagnificationController
-                .setScaleAndCenter(scale, startCenter.x, startCenter.y, false, SERVICE_ID_1));
+                .setScaleAndCenter(displayId, scale, startCenter.x, startCenter.y, false,
+                        SERVICE_ID_1));
         mMessageCapturingHandler.sendAllMessages();
         Mockito.reset(mMockWindowManager);
 
         PointF newCenter = INITIAL_BOUNDS_LOWER_RIGHT_2X_CENTER;
         PointF newOffsets = computeOffsets(INITIAL_MAGNIFICATION_BOUNDS, newCenter, scale);
-        mMagnificationController.offsetMagnifiedRegion(
-                startOffsets.x - newOffsets.x, startOffsets.y - newOffsets.y, SERVICE_ID_1);
+        mMagnificationController.offsetMagnifiedRegion(displayId,
+                startOffsets.x - newOffsets.x, startOffsets.y - newOffsets.y,
+                SERVICE_ID_1);
         mMessageCapturingHandler.sendAllMessages();
 
         MagnificationSpec expectedSpec = getMagnificationSpec(scale, newOffsets);
-        verify(mMockWindowManager).setMagnificationSpec(argThat(closeTo(expectedSpec)));
-        assertEquals(newCenter.x, mMagnificationController.getCenterX(), 0.0);
-        assertEquals(newCenter.y, mMagnificationController.getCenterY(), 0.0);
+        verify(mMockWindowManager).setMagnificationSpec(eq(displayId),
+                argThat(closeTo(expectedSpec)));
+        assertEquals(newCenter.x, mMagnificationController.getCenterX(displayId), 0.0);
+        assertEquals(newCenter.y, mMagnificationController.getCenterY(displayId), 0.0);
         verify(mMockValueAnimator, times(0)).start();
     }
 
     @Test
     public void testOffsetMagnifiedRegion_whileNotMagnifying_hasNoEffect() {
-        mMagnificationController.register();
+        for (int i = 0; i < DISPLAY_COUNT; i++) {
+            offsetMagnifiedRegion_whileNotMagnifying_hasNoEffect(i);
+            resetMockWindowManager();
+        }
+    }
+
+    private void offsetMagnifiedRegion_whileNotMagnifying_hasNoEffect(int displayId) {
+        register(displayId);
         Mockito.reset(mMockWindowManager);
-        MagnificationSpec startSpec = getCurrentMagnificationSpec();
-        mMagnificationController.offsetMagnifiedRegion(10, 10, SERVICE_ID_1);
-        assertThat(getCurrentMagnificationSpec(), closeTo(startSpec));
-        mMagnificationController.offsetMagnifiedRegion(-10, -10, SERVICE_ID_1);
-        assertThat(getCurrentMagnificationSpec(), closeTo(startSpec));
+        MagnificationSpec startSpec = getCurrentMagnificationSpec(displayId);
+        mMagnificationController.offsetMagnifiedRegion(displayId, 10, 10, SERVICE_ID_1);
+        assertThat(getCurrentMagnificationSpec(displayId), closeTo(startSpec));
+        mMagnificationController.offsetMagnifiedRegion(displayId, -10, -10, SERVICE_ID_1);
+        assertThat(getCurrentMagnificationSpec(displayId), closeTo(startSpec));
         verifyNoMoreInteractions(mMockWindowManager);
     }
 
     @Test
     public void testOffsetMagnifiedRegion_whileMagnifyingButAtEdge_hasNoEffect() {
-        mMagnificationController.register();
+        for (int i = 0; i < DISPLAY_COUNT; i++) {
+            offsetMagnifiedRegion_whileMagnifyingButAtEdge_hasNoEffect(i);
+            resetMockWindowManager();
+        }
+    }
+
+    private void offsetMagnifiedRegion_whileMagnifyingButAtEdge_hasNoEffect(int displayId) {
+        register(displayId);
         float scale = 2.0f;
 
         // Upper left edges
         PointF ulCenter = INITIAL_BOUNDS_UPPER_LEFT_2X_CENTER;
         assertTrue(mMagnificationController
-                .setScaleAndCenter(scale, ulCenter.x, ulCenter.y, false, SERVICE_ID_1));
+                .setScaleAndCenter(displayId, scale, ulCenter.x, ulCenter.y, false,
+                        SERVICE_ID_1));
         Mockito.reset(mMockWindowManager);
-        MagnificationSpec ulSpec = getCurrentMagnificationSpec();
-        mMagnificationController.offsetMagnifiedRegion(-10, -10, SERVICE_ID_1);
-        assertThat(getCurrentMagnificationSpec(), closeTo(ulSpec));
+        MagnificationSpec ulSpec = getCurrentMagnificationSpec(displayId);
+        mMagnificationController.offsetMagnifiedRegion(displayId, -10, -10,
+                SERVICE_ID_1);
+        assertThat(getCurrentMagnificationSpec(displayId), closeTo(ulSpec));
         verifyNoMoreInteractions(mMockWindowManager);
 
         // Lower right edges
         PointF lrCenter = INITIAL_BOUNDS_LOWER_RIGHT_2X_CENTER;
         assertTrue(mMagnificationController
-                .setScaleAndCenter(scale, lrCenter.x, lrCenter.y, false, SERVICE_ID_1));
+                .setScaleAndCenter(displayId, scale, lrCenter.x, lrCenter.y, false,
+                        SERVICE_ID_1));
         Mockito.reset(mMockWindowManager);
-        MagnificationSpec lrSpec = getCurrentMagnificationSpec();
-        mMagnificationController.offsetMagnifiedRegion(10, 10, SERVICE_ID_1);
-        assertThat(getCurrentMagnificationSpec(), closeTo(lrSpec));
+        MagnificationSpec lrSpec = getCurrentMagnificationSpec(displayId);
+        mMagnificationController.offsetMagnifiedRegion(displayId, 10, 10,
+                SERVICE_ID_1);
+        assertThat(getCurrentMagnificationSpec(displayId), closeTo(lrSpec));
         verifyNoMoreInteractions(mMockWindowManager);
     }
 
     @Test
     public void testGetIdOfLastServiceToChange_returnsCorrectValue() {
-        mMagnificationController.register();
+        for (int i = 0; i < DISPLAY_COUNT; i++) {
+            getIdOfLastServiceToChange_returnsCorrectValue(i);
+            resetMockWindowManager();
+        }
+    }
+
+    private void getIdOfLastServiceToChange_returnsCorrectValue(int displayId) {
+        register(displayId);
         PointF startCenter = INITIAL_MAGNIFICATION_BOUNDS_CENTER;
         assertTrue(mMagnificationController
-                .setScale(2.0f, startCenter.x, startCenter.y, false, SERVICE_ID_1));
-        assertEquals(SERVICE_ID_1, mMagnificationController.getIdOfLastServiceToMagnify());
+                .setScale(displayId, 2.0f, startCenter.x, startCenter.y, false,
+                        SERVICE_ID_1));
+        assertEquals(SERVICE_ID_1, mMagnificationController.getIdOfLastServiceToMagnify(displayId));
         assertTrue(mMagnificationController
-                .setScale(1.5f, startCenter.x, startCenter.y, false, SERVICE_ID_2));
-        assertEquals(SERVICE_ID_2, mMagnificationController.getIdOfLastServiceToMagnify());
+                .setScale(displayId, 1.5f, startCenter.x, startCenter.y, false,
+                        SERVICE_ID_2));
+        assertEquals(SERVICE_ID_2, mMagnificationController.getIdOfLastServiceToMagnify(displayId));
     }
 
     @Test
     public void testResetIfNeeded_resetsOnlyIfLastMagnifyingServiceIsDisabled() {
-        mMagnificationController.register();
+        for (int i = 0; i < DISPLAY_COUNT; i++) {
+            resetIfNeeded_resetsOnlyIfLastMagnifyingServiceIsDisabled(i);
+            resetMockWindowManager();
+        }
+    }
+
+    private void resetIfNeeded_resetsOnlyIfLastMagnifyingServiceIsDisabled(int displayId) {
+        register(displayId);
         PointF startCenter = INITIAL_MAGNIFICATION_BOUNDS_CENTER;
         mMagnificationController
-                .setScale(2.0f, startCenter.x, startCenter.y, false, SERVICE_ID_1);
+                .setScale(displayId, 2.0f, startCenter.x, startCenter.y, false,
+                        SERVICE_ID_1);
         mMagnificationController
-                .setScale(1.5f, startCenter.x, startCenter.y, false, SERVICE_ID_2);
-        assertFalse(mMagnificationController.resetIfNeeded(SERVICE_ID_1));
-        assertTrue(mMagnificationController.isMagnifying());
-        assertTrue(mMagnificationController.resetIfNeeded(SERVICE_ID_2));
-        assertFalse(mMagnificationController.isMagnifying());
+                .setScale(displayId, 1.5f, startCenter.x, startCenter.y, false,
+                        SERVICE_ID_2);
+        assertFalse(mMagnificationController.resetIfNeeded(displayId, SERVICE_ID_1));
+        assertTrue(mMagnificationController.isMagnifying(displayId));
+        assertTrue(mMagnificationController.resetIfNeeded(displayId, SERVICE_ID_2));
+        assertFalse(mMagnificationController.isMagnifying(displayId));
     }
 
     @Test
     public void testSetUserId_resetsOnlyIfIdChanges() {
+        for (int i = 0; i < DISPLAY_COUNT; i++) {
+            testSetUserId_resetsOnlyIfIdChanges(i);
+            resetMockWindowManager();
+        }
+    }
+
+    private void testSetUserId_resetsOnlyIfIdChanges(int displayId) {
         final int userId1 = 1;
         final int userId2 = 2;
 
-        mMagnificationController.register();
+        register(displayId);
         mMagnificationController.setUserId(userId1);
         PointF startCenter = INITIAL_MAGNIFICATION_BOUNDS_CENTER;
         float scale = 2.0f;
-        mMagnificationController.setScale(scale, startCenter.x, startCenter.y, false, SERVICE_ID_1);
+        mMagnificationController.setScale(displayId, scale, startCenter.x, startCenter.y, false,
+                SERVICE_ID_1);
 
         mMagnificationController.setUserId(userId1);
-        assertTrue(mMagnificationController.isMagnifying());
+        assertTrue(mMagnificationController.isMagnifying(displayId));
         mMagnificationController.setUserId(userId2);
-        assertFalse(mMagnificationController.isMagnifying());
+        assertFalse(mMagnificationController.isMagnifying(displayId));
     }
 
     @Test
     public void testResetIfNeeded_doesWhatItSays() {
-        mMagnificationController.register();
-        zoomIn2xToMiddle();
+        for (int i = 0; i < DISPLAY_COUNT; i++) {
+            testResetIfNeeded_doesWhatItSays(i);
+            resetMockWindowManager();
+        }
+    }
+
+    private void testResetIfNeeded_doesWhatItSays(int displayId) {
+        register(displayId);
+        zoomIn2xToMiddle(displayId);
         mMessageCapturingHandler.sendAllMessages();
         reset(mMockAms);
-        assertTrue(mMagnificationController.resetIfNeeded(false));
-        verify(mMockAms).notifyMagnificationChanged(
+        assertTrue(mMagnificationController.resetIfNeeded(displayId, false));
+        verify(mMockAms).notifyMagnificationChanged(eq(displayId),
                 eq(INITIAL_MAGNIFICATION_REGION), eq(1.0f), anyFloat(), anyFloat());
-        assertFalse(mMagnificationController.isMagnifying());
-        assertFalse(mMagnificationController.resetIfNeeded(false));
+        assertFalse(mMagnificationController.isMagnifying(displayId));
+        assertFalse(mMagnificationController.resetIfNeeded(displayId, false));
     }
 
     @Test
     public void testTurnScreenOff_resetsMagnification() {
-        mMagnificationController.register();
+        register(DISPLAY_0);
+        register(DISPLAY_1);
         ArgumentCaptor<BroadcastReceiver> broadcastReceiverCaptor =
                 ArgumentCaptor.forClass(BroadcastReceiver.class);
         verify(mMockContext).registerReceiver(
                 broadcastReceiverCaptor.capture(), (IntentFilter) anyObject());
         BroadcastReceiver br = broadcastReceiverCaptor.getValue();
-        zoomIn2xToMiddle();
+        zoomIn2xToMiddle(DISPLAY_0);
+        zoomIn2xToMiddle(DISPLAY_1);
         mMessageCapturingHandler.sendAllMessages();
         br.onReceive(mMockContext, null);
         mMessageCapturingHandler.sendAllMessages();
-        assertFalse(mMagnificationController.isMagnifying());
+        assertFalse(mMagnificationController.isMagnifying(DISPLAY_0));
+        assertFalse(mMagnificationController.isMagnifying(DISPLAY_1));
     }
 
     @Test
     public void testUserContextChange_resetsMagnification() {
-        mMagnificationController.register();
-        MagnificationCallbacks callbacks = getMagnificationCallbacks();
-        zoomIn2xToMiddle();
+        for (int i = 0; i < DISPLAY_COUNT; i++) {
+            contextChange_resetsMagnification(i);
+            resetMockWindowManager();
+        }
+    }
+
+    private void contextChange_resetsMagnification(int displayId) {
+        register(displayId);
+        MagnificationCallbacks callbacks = getMagnificationCallbacks(displayId);
+        zoomIn2xToMiddle(displayId);
         mMessageCapturingHandler.sendAllMessages();
         callbacks.onUserContextChanged();
         mMessageCapturingHandler.sendAllMessages();
-        assertFalse(mMagnificationController.isMagnifying());
+        assertFalse(mMagnificationController.isMagnifying(displayId));
     }
 
     @Test
     public void testRotation_resetsMagnification() {
-        mMagnificationController.register();
-        MagnificationCallbacks callbacks = getMagnificationCallbacks();
-        zoomIn2xToMiddle();
+        for (int i = 0; i < DISPLAY_COUNT; i++) {
+            rotation_resetsMagnification(i);
+            resetMockWindowManager();
+        }
+    }
+
+    private void rotation_resetsMagnification(int displayId) {
+        register(displayId);
+        MagnificationCallbacks callbacks = getMagnificationCallbacks(displayId);
+        zoomIn2xToMiddle(displayId);
         mMessageCapturingHandler.sendAllMessages();
-        assertTrue(mMagnificationController.isMagnifying());
+        assertTrue(mMagnificationController.isMagnifying(displayId));
         callbacks.onRotationChanged(0);
         mMessageCapturingHandler.sendAllMessages();
-        assertFalse(mMagnificationController.isMagnifying());
+        assertFalse(mMagnificationController.isMagnifying(displayId));
     }
 
     @Test
     public void testBoundsChange_whileMagnifyingWithCompatibleSpec_noSpecChange() {
+        for (int i = 0; i < DISPLAY_COUNT; i++) {
+            boundsChange_whileMagnifyingWithCompatibleSpec_noSpecChange(i);
+            resetMockWindowManager();
+        }
+    }
+
+    private void boundsChange_whileMagnifyingWithCompatibleSpec_noSpecChange(int displayId) {
         // Going from a small region to a large one leads to no issues
-        mMagnificationController.register();
-        zoomIn2xToMiddle();
+        register(displayId);
+        zoomIn2xToMiddle(displayId);
         mMessageCapturingHandler.sendAllMessages();
-        MagnificationSpec startSpec = getCurrentMagnificationSpec();
-        MagnificationCallbacks callbacks = getMagnificationCallbacks();
+        MagnificationSpec startSpec = getCurrentMagnificationSpec(displayId);
+        MagnificationCallbacks callbacks = getMagnificationCallbacks(displayId);
         Mockito.reset(mMockWindowManager);
         callbacks.onMagnificationRegionChanged(OTHER_REGION_COMPAT);
         mMessageCapturingHandler.sendAllMessages();
-        assertThat(getCurrentMagnificationSpec(), closeTo(startSpec));
+        assertThat(getCurrentMagnificationSpec(displayId), closeTo(startSpec));
         verifyNoMoreInteractions(mMockWindowManager);
     }
 
     @Test
     public void testBoundsChange_whileZoomingWithCompatibleSpec_noSpecChange() {
-        mMagnificationController.register();
+        for (int i = 0; i < DISPLAY_COUNT; i++) {
+            boundsChange_whileZoomingWithCompatibleSpec_noSpecChange(i);
+            resetMockWindowManager();
+        }
+    }
+
+    private void boundsChange_whileZoomingWithCompatibleSpec_noSpecChange(int displayId) {
+        register(displayId);
         PointF startCenter = INITIAL_MAGNIFICATION_BOUNDS_CENTER;
         float scale = 2.0f;
         // setting animate parameter to true is differ from zoomIn2xToMiddle()
-        mMagnificationController.setScale(scale, startCenter.x, startCenter.y, true, SERVICE_ID_1);
-        MagnificationSpec startSpec = getCurrentMagnificationSpec();
-        MagnificationCallbacks callbacks = getMagnificationCallbacks();
+        mMagnificationController.setScale(displayId, scale, startCenter.x, startCenter.y, true,
+                SERVICE_ID_1);
+        MagnificationSpec startSpec = getCurrentMagnificationSpec(displayId);
+        MagnificationCallbacks callbacks = getMagnificationCallbacks(displayId);
         Mockito.reset(mMockWindowManager);
         callbacks.onMagnificationRegionChanged(OTHER_REGION_COMPAT);
         mMessageCapturingHandler.sendAllMessages();
-        assertThat(getCurrentMagnificationSpec(), closeTo(startSpec));
+        assertThat(getCurrentMagnificationSpec(displayId), closeTo(startSpec));
         verifyNoMoreInteractions(mMockWindowManager);
     }
 
     @Test
     public void testBoundsChange_whileMagnifyingWithIncompatibleSpec_offsetsConstrained() {
+        for (int i = 0; i < DISPLAY_COUNT; i++) {
+            boundsChange_whileMagnifyingWithIncompatibleSpec_offsetsConstrained(i);
+            resetMockWindowManager();
+        }
+    }
+
+    private void boundsChange_whileMagnifyingWithIncompatibleSpec_offsetsConstrained(
+            int displayId) {
         // In a large region, pan to the farthest point possible
-        mMagnificationController.register();
-        MagnificationCallbacks callbacks = getMagnificationCallbacks();
+        register(displayId);
+        MagnificationCallbacks callbacks = getMagnificationCallbacks(displayId);
         callbacks.onMagnificationRegionChanged(OTHER_REGION);
         mMessageCapturingHandler.sendAllMessages();
         PointF startCenter = OTHER_BOUNDS_LOWER_RIGHT_2X_CENTER;
         float scale = 2.0f;
-        mMagnificationController.setScale(scale, startCenter.x, startCenter.y, false, SERVICE_ID_1);
+        mMagnificationController.setScale(displayId, scale, startCenter.x, startCenter.y, false,
+                SERVICE_ID_1);
         mMessageCapturingHandler.sendAllMessages();
-        MagnificationSpec startSpec = getCurrentMagnificationSpec();
-        verify(mMockWindowManager).setMagnificationSpec(argThat(closeTo(startSpec)));
+        MagnificationSpec startSpec = getCurrentMagnificationSpec(displayId);
+        verify(mMockWindowManager).setMagnificationSpec(eq(displayId), argThat(closeTo(startSpec)));
         Mockito.reset(mMockWindowManager);
 
         callbacks.onMagnificationRegionChanged(INITIAL_MAGNIFICATION_REGION);
         mMessageCapturingHandler.sendAllMessages();
 
-        MagnificationSpec endSpec = getCurrentMagnificationSpec();
+        MagnificationSpec endSpec = getCurrentMagnificationSpec(displayId);
         assertThat(endSpec, CoreMatchers.not(closeTo(startSpec)));
         PointF expectedOffsets = computeOffsets(INITIAL_MAGNIFICATION_BOUNDS,
                 INITIAL_BOUNDS_LOWER_RIGHT_2X_CENTER, scale);
         assertThat(endSpec, closeTo(getMagnificationSpec(scale, expectedOffsets)));
-        verify(mMockWindowManager).setMagnificationSpec(argThat(closeTo(endSpec)));
+        verify(mMockWindowManager).setMagnificationSpec(eq(displayId), argThat(closeTo(endSpec)));
     }
 
     @Test
     public void testBoundsChange_whileZoomingWithIncompatibleSpec_jumpsToCompatibleSpec() {
-        mMagnificationController.register();
-        MagnificationCallbacks callbacks = getMagnificationCallbacks();
+        for (int i = 0; i < DISPLAY_COUNT; i++) {
+            boundsChange_whileZoomingWithIncompatibleSpec_jumpsToCompatibleSpec(i);
+            resetMockWindowManager();
+        }
+    }
+
+    private void boundsChange_whileZoomingWithIncompatibleSpec_jumpsToCompatibleSpec(
+            int displayId) {
+        register(displayId);
+        MagnificationCallbacks callbacks = getMagnificationCallbacks(displayId);
         callbacks.onMagnificationRegionChanged(OTHER_REGION);
         mMessageCapturingHandler.sendAllMessages();
         PointF startCenter = OTHER_BOUNDS_LOWER_RIGHT_2X_CENTER;
         float scale = 2.0f;
-        mMagnificationController.setScale(scale, startCenter.x, startCenter.y, true, SERVICE_ID_1);
+        mMagnificationController.setScale(displayId, scale, startCenter.x, startCenter.y, true,
+                SERVICE_ID_1);
         mMessageCapturingHandler.sendAllMessages();
-        MagnificationSpec startSpec = getCurrentMagnificationSpec();
+        MagnificationSpec startSpec = getCurrentMagnificationSpec(displayId);
         when(mMockValueAnimator.isRunning()).thenReturn(true);
 
         callbacks.onMagnificationRegionChanged(INITIAL_MAGNIFICATION_REGION);
         mMessageCapturingHandler.sendAllMessages();
         verify(mMockValueAnimator).cancel();
 
-        MagnificationSpec endSpec = getCurrentMagnificationSpec();
+        MagnificationSpec endSpec = getCurrentMagnificationSpec(displayId);
         assertThat(endSpec, CoreMatchers.not(closeTo(startSpec)));
         PointF expectedOffsets = computeOffsets(INITIAL_MAGNIFICATION_BOUNDS,
                 INITIAL_BOUNDS_LOWER_RIGHT_2X_CENTER, scale);
         assertThat(endSpec, closeTo(getMagnificationSpec(scale, expectedOffsets)));
-        verify(mMockWindowManager).setMagnificationSpec(argThat(closeTo(endSpec)));
+        verify(mMockWindowManager).setMagnificationSpec(eq(displayId), argThat(closeTo(endSpec)));
     }
 
     @Test
     public void testRequestRectOnScreen_rectAlreadyOnScreen_doesNothing() {
-        mMagnificationController.register();
-        zoomIn2xToMiddle();
+        for (int i = 0; i < DISPLAY_COUNT; i++) {
+            requestRectOnScreen_rectAlreadyOnScreen_doesNothing(i);
+            resetMockWindowManager();
+        }
+    }
+
+    private void requestRectOnScreen_rectAlreadyOnScreen_doesNothing(int displayId) {
+        register(displayId);
+        zoomIn2xToMiddle(displayId);
         mMessageCapturingHandler.sendAllMessages();
-        MagnificationSpec startSpec = getCurrentMagnificationSpec();
-        MagnificationCallbacks callbacks = getMagnificationCallbacks();
+        MagnificationSpec startSpec = getCurrentMagnificationSpec(displayId);
+        MagnificationCallbacks callbacks = getMagnificationCallbacks(displayId);
         Mockito.reset(mMockWindowManager);
         int centerX = (int) INITIAL_MAGNIFICATION_BOUNDS_CENTER.x;
         int centerY = (int) INITIAL_MAGNIFICATION_BOUNDS_CENTER.y;
         callbacks.onRectangleOnScreenRequested(centerX - 1, centerY - 1, centerX + 1, centerY - 1);
         mMessageCapturingHandler.sendAllMessages();
-        assertThat(getCurrentMagnificationSpec(), closeTo(startSpec));
+        assertThat(getCurrentMagnificationSpec(displayId), closeTo(startSpec));
         verifyNoMoreInteractions(mMockWindowManager);
     }
 
     @Test
     public void testRequestRectOnScreen_rectCanFitOnScreen_pansToGetRectOnScreen() {
-        mMagnificationController.register();
-        zoomIn2xToMiddle();
+        for (int i = 0; i < DISPLAY_COUNT; i++) {
+            requestRectOnScreen_rectCanFitOnScreen_pansToGetRectOnScreen(i);
+            resetMockWindowManager();
+        }
+    }
+
+    private void requestRectOnScreen_rectCanFitOnScreen_pansToGetRectOnScreen(int displayId) {
+        register(displayId);
+        zoomIn2xToMiddle(displayId);
         mMessageCapturingHandler.sendAllMessages();
-        MagnificationCallbacks callbacks = getMagnificationCallbacks();
+        MagnificationCallbacks callbacks = getMagnificationCallbacks(displayId);
         Mockito.reset(mMockWindowManager);
         callbacks.onRectangleOnScreenRequested(0, 0, 1, 1);
         mMessageCapturingHandler.sendAllMessages();
         MagnificationSpec expectedEndSpec = getMagnificationSpec(2.0f, 0, 0);
-        assertThat(getCurrentMagnificationSpec(), closeTo(expectedEndSpec));
-        verify(mMockWindowManager).setMagnificationSpec(argThat(closeTo(expectedEndSpec)));
+        assertThat(getCurrentMagnificationSpec(displayId), closeTo(expectedEndSpec));
+        verify(mMockWindowManager).setMagnificationSpec(eq(displayId),
+                argThat(closeTo(expectedEndSpec)));
     }
 
     @Test
     public void testRequestRectOnScreen_garbageInput_doesNothing() {
-        mMagnificationController.register();
-        zoomIn2xToMiddle();
+        for (int i = 0; i < DISPLAY_COUNT; i++) {
+            requestRectOnScreen_garbageInput_doesNothing(i);
+            resetMockWindowManager();
+        }
+    }
+
+    private void requestRectOnScreen_garbageInput_doesNothing(int displayId) {
+        register(displayId);
+        zoomIn2xToMiddle(displayId);
         mMessageCapturingHandler.sendAllMessages();
-        MagnificationSpec startSpec = getCurrentMagnificationSpec();
-        MagnificationCallbacks callbacks = getMagnificationCallbacks();
+        MagnificationSpec startSpec = getCurrentMagnificationSpec(displayId);
+        MagnificationCallbacks callbacks = getMagnificationCallbacks(displayId);
         Mockito.reset(mMockWindowManager);
         callbacks.onRectangleOnScreenRequested(0, 0, -50, -50);
         mMessageCapturingHandler.sendAllMessages();
-        assertThat(getCurrentMagnificationSpec(), closeTo(startSpec));
+        assertThat(getCurrentMagnificationSpec(displayId), closeTo(startSpec));
         verifyNoMoreInteractions(mMockWindowManager);
     }
 
     @Test
     public void testRequestRectOnScreen_rectTooWide_pansToGetStartOnScreenBasedOnLocale() {
+        for (int i = 0; i < DISPLAY_COUNT; i++) {
+            requestRectOnScreen_rectTooWide_pansToGetStartOnScreenBasedOnLocale(i);
+            resetMockWindowManager();
+        }
+    }
+
+    private void requestRectOnScreen_rectTooWide_pansToGetStartOnScreenBasedOnLocale(
+            int displayId) {
         Locale.setDefault(new Locale("en", "us"));
-        mMagnificationController.register();
-        zoomIn2xToMiddle();
+        register(displayId);
+        zoomIn2xToMiddle(displayId);
         mMessageCapturingHandler.sendAllMessages();
-        MagnificationCallbacks callbacks = getMagnificationCallbacks();
-        MagnificationSpec startSpec = getCurrentMagnificationSpec();
+        MagnificationCallbacks callbacks = getMagnificationCallbacks(displayId);
+        MagnificationSpec startSpec = getCurrentMagnificationSpec(displayId);
         Mockito.reset(mMockWindowManager);
         Rect wideRect = new Rect(0, 50, 100, 51);
         callbacks.onRectangleOnScreenRequested(
                 wideRect.left, wideRect.top, wideRect.right, wideRect.bottom);
         mMessageCapturingHandler.sendAllMessages();
         MagnificationSpec expectedEndSpec = getMagnificationSpec(2.0f, 0, startSpec.offsetY);
-        assertThat(getCurrentMagnificationSpec(), closeTo(expectedEndSpec));
-        verify(mMockWindowManager).setMagnificationSpec(argThat(closeTo(expectedEndSpec)));
+        assertThat(getCurrentMagnificationSpec(displayId), closeTo(expectedEndSpec));
+        verify(mMockWindowManager).setMagnificationSpec(eq(displayId),
+                argThat(closeTo(expectedEndSpec)));
         Mockito.reset(mMockWindowManager);
 
         // Repeat with RTL
@@ -692,50 +900,66 @@
                 wideRect.left, wideRect.top, wideRect.right, wideRect.bottom);
         mMessageCapturingHandler.sendAllMessages();
         expectedEndSpec = getMagnificationSpec(2.0f, -100, startSpec.offsetY);
-        assertThat(getCurrentMagnificationSpec(), closeTo(expectedEndSpec));
-        verify(mMockWindowManager).setMagnificationSpec(argThat(closeTo(expectedEndSpec)));
+        assertThat(getCurrentMagnificationSpec(displayId), closeTo(expectedEndSpec));
+        verify(mMockWindowManager).setMagnificationSpec(eq(displayId),
+                argThat(closeTo(expectedEndSpec)));
     }
 
     @Test
     public void testRequestRectOnScreen_rectTooTall_pansMinimumToGetTopOnScreen() {
-        mMagnificationController.register();
-        zoomIn2xToMiddle();
+        for (int i = 0; i < DISPLAY_COUNT; i++) {
+            requestRectOnScreen_rectTooTall_pansMinimumToGetTopOnScreen(i);
+            resetMockWindowManager();
+        }
+    }
+
+    private void requestRectOnScreen_rectTooTall_pansMinimumToGetTopOnScreen(int displayId) {
+        register(displayId);
+        zoomIn2xToMiddle(displayId);
         mMessageCapturingHandler.sendAllMessages();
-        MagnificationCallbacks callbacks = getMagnificationCallbacks();
-        MagnificationSpec startSpec = getCurrentMagnificationSpec();
+        MagnificationCallbacks callbacks = getMagnificationCallbacks(displayId);
+        MagnificationSpec startSpec = getCurrentMagnificationSpec(displayId);
         Mockito.reset(mMockWindowManager);
         Rect tallRect = new Rect(50, 0, 51, 100);
         callbacks.onRectangleOnScreenRequested(
                 tallRect.left, tallRect.top, tallRect.right, tallRect.bottom);
         mMessageCapturingHandler.sendAllMessages();
         MagnificationSpec expectedEndSpec = getMagnificationSpec(2.0f, startSpec.offsetX, 0);
-        assertThat(getCurrentMagnificationSpec(), closeTo(expectedEndSpec));
-        verify(mMockWindowManager).setMagnificationSpec(argThat(closeTo(expectedEndSpec)));
+        assertThat(getCurrentMagnificationSpec(displayId), closeTo(expectedEndSpec));
+        verify(mMockWindowManager).setMagnificationSpec(eq(displayId),
+                argThat(closeTo(expectedEndSpec)));
     }
 
     @Test
     public void testChangeMagnification_duringAnimation_animatesToNewValue() {
-        mMagnificationController.register();
-        MagnificationSpec startSpec = getCurrentMagnificationSpec();
+        for (int i = 0; i < DISPLAY_COUNT; i++) {
+            changeMagnification_duringAnimation_animatesToNewValue(i);
+            resetMockWindowManager();
+        }
+    }
+
+    private void changeMagnification_duringAnimation_animatesToNewValue(int displayId) {
+        register(displayId);
+        MagnificationSpec startSpec = getCurrentMagnificationSpec(displayId);
         float scale = 2.5f;
         PointF firstCenter = INITIAL_BOUNDS_LOWER_RIGHT_2X_CENTER;
         MagnificationSpec firstEndSpec = getMagnificationSpec(
                 scale, computeOffsets(INITIAL_MAGNIFICATION_BOUNDS, firstCenter, scale));
 
-        assertTrue(mMagnificationController.setScaleAndCenter(scale, firstCenter.x, firstCenter.y,
-                true, SERVICE_ID_1));
+        assertTrue(mMagnificationController.setScaleAndCenter(displayId,
+                scale, firstCenter.x, firstCenter.y, true, SERVICE_ID_1));
         mMessageCapturingHandler.sendAllMessages();
 
-        assertEquals(firstCenter.x, mMagnificationController.getCenterX(), 0.5);
-        assertEquals(firstCenter.y, mMagnificationController.getCenterY(), 0.5);
-        assertThat(getCurrentMagnificationSpec(), closeTo(firstEndSpec));
+        assertEquals(firstCenter.x, mMagnificationController.getCenterX(displayId), 0.5);
+        assertEquals(firstCenter.y, mMagnificationController.getCenterY(displayId), 0.5);
+        assertThat(getCurrentMagnificationSpec(displayId), closeTo(firstEndSpec));
         verify(mMockValueAnimator, times(1)).start();
 
         // Initial value
         when(mMockValueAnimator.getAnimatedFraction()).thenReturn(0.0f);
         mTargetAnimationListener.onAnimationUpdate(mMockValueAnimator);
-        verify(mMockWindowManager).setMagnificationSpec(startSpec);
-        verify(mMockAms).notifyMagnificationChanged(
+        verify(mMockWindowManager).setMagnificationSpec(eq(displayId), eq(startSpec));
+        verify(mMockAms).notifyMagnificationChanged(displayId,
                 INITIAL_MAGNIFICATION_REGION, scale, firstCenter.x, firstCenter.y);
         Mockito.reset(mMockWindowManager);
 
@@ -745,32 +969,34 @@
         mTargetAnimationListener.onAnimationUpdate(mMockValueAnimator);
         MagnificationSpec intermediateSpec1 =
                 getInterpolatedMagSpec(startSpec, firstEndSpec, fraction);
-        verify(mMockWindowManager).setMagnificationSpec(argThat(closeTo(intermediateSpec1)));
+        verify(mMockWindowManager).setMagnificationSpec(eq(displayId),
+                argThat(closeTo(intermediateSpec1)));
         Mockito.reset(mMockWindowManager);
 
         PointF newCenter = INITIAL_BOUNDS_UPPER_LEFT_2X_CENTER;
         MagnificationSpec newEndSpec = getMagnificationSpec(
                 scale, computeOffsets(INITIAL_MAGNIFICATION_BOUNDS, newCenter, scale));
-        assertTrue(mMagnificationController.setCenter(
+        assertTrue(mMagnificationController.setCenter(displayId,
                 newCenter.x, newCenter.y, true, SERVICE_ID_1));
         mMessageCapturingHandler.sendAllMessages();
 
         // Animation should have been restarted
         verify(mMockValueAnimator, times(2)).start();
-        verify(mMockAms).notifyMagnificationChanged(
+        verify(mMockAms).notifyMagnificationChanged(displayId,
                 INITIAL_MAGNIFICATION_REGION, scale, newCenter.x, newCenter.y);
 
         // New starting point should be where we left off
         when(mMockValueAnimator.getAnimatedFraction()).thenReturn(0.0f);
         mTargetAnimationListener.onAnimationUpdate(mMockValueAnimator);
-        verify(mMockWindowManager).setMagnificationSpec(argThat(closeTo(intermediateSpec1)));
+        verify(mMockWindowManager).setMagnificationSpec(eq(displayId),
+                argThat(closeTo(intermediateSpec1)));
         Mockito.reset(mMockWindowManager);
 
         // Second intermediate point
         fraction = 0.5f;
         when(mMockValueAnimator.getAnimatedFraction()).thenReturn(fraction);
         mTargetAnimationListener.onAnimationUpdate(mMockValueAnimator);
-        verify(mMockWindowManager).setMagnificationSpec(
+        verify(mMockWindowManager).setMagnificationSpec(eq(displayId),
                 argThat(closeTo(getInterpolatedMagSpec(intermediateSpec1, newEndSpec, fraction))));
         Mockito.reset(mMockWindowManager);
 
@@ -778,21 +1004,54 @@
         Mockito.reset(mMockWindowManager);
         when(mMockValueAnimator.getAnimatedFraction()).thenReturn(1.0f);
         mTargetAnimationListener.onAnimationUpdate(mMockValueAnimator);
-        verify(mMockWindowManager).setMagnificationSpec(argThat(closeTo(newEndSpec)));
+        verify(mMockWindowManager).setMagnificationSpec(eq(displayId),
+                argThat(closeTo(newEndSpec)));
     }
 
-    private void zoomIn2xToMiddle() {
+    private void initMockWindowManager() {
+        for (int i = 0; i < DISPLAY_COUNT; i++) {
+            when(mMockWindowManager.setMagnificationCallbacks(eq(i), any())).thenReturn(true);
+        }
+        doAnswer(new Answer<Void>() {
+            @Override
+            public Void answer(InvocationOnMock invocationOnMock) throws Throwable {
+                Object[] args = invocationOnMock.getArguments();
+                Region regionArg = (Region) args[1];
+                regionArg.set(INITIAL_MAGNIFICATION_REGION);
+                return null;
+            }
+        }).when(mMockWindowManager).getMagnificationRegion(anyInt(), (Region) anyObject());
+    }
+
+    private void resetMockWindowManager() {
+        Mockito.reset(mMockWindowManager);
+        initMockWindowManager();
+    }
+
+    private void register(int displayId) {
+        mMockValueAnimator = mock(ValueAnimator.class);
+        when(mMockControllerCtx.newValueAnimator()).thenReturn(mMockValueAnimator);
+        mMagnificationController.register(displayId);
+        ArgumentCaptor<ValueAnimator.AnimatorUpdateListener> listenerArgumentCaptor =
+                ArgumentCaptor.forClass(ValueAnimator.AnimatorUpdateListener.class);
+        verify(mMockValueAnimator).addUpdateListener(listenerArgumentCaptor.capture());
+        mTargetAnimationListener = listenerArgumentCaptor.getValue();
+        Mockito.reset(mMockValueAnimator); // Ignore other initialization
+    }
+
+    private void zoomIn2xToMiddle(int displayId) {
         PointF startCenter = INITIAL_MAGNIFICATION_BOUNDS_CENTER;
         float scale = 2.0f;
-        mMagnificationController.setScale(scale, startCenter.x, startCenter.y, false, SERVICE_ID_1);
-        assertTrue(mMagnificationController.isMagnifying());
+        mMagnificationController.setScale(displayId, scale, startCenter.x, startCenter.y, false,
+                SERVICE_ID_1);
+        assertTrue(mMagnificationController.isMagnifying(displayId));
     }
 
-    private MagnificationCallbacks getMagnificationCallbacks() {
+    private MagnificationCallbacks getMagnificationCallbacks(int displayId) {
         ArgumentCaptor<MagnificationCallbacks> magnificationCallbacksCaptor =
                 ArgumentCaptor.forClass(MagnificationCallbacks.class);
         verify(mMockWindowManager)
-                .setMagnificationCallbacks(magnificationCallbacksCaptor.capture());
+                .setMagnificationCallbacks(eq(displayId), magnificationCallbacksCaptor.capture());
         return magnificationCallbacksCaptor.getValue();
     }
 
@@ -823,9 +1082,10 @@
         return spec;
     }
 
-    private MagnificationSpec getCurrentMagnificationSpec() {
-        return getMagnificationSpec(mMagnificationController.getScale(),
-                mMagnificationController.getOffsetX(), mMagnificationController.getOffsetY());
+    private MagnificationSpec getCurrentMagnificationSpec(int displayId) {
+        return getMagnificationSpec(mMagnificationController.getScale(displayId),
+                mMagnificationController.getOffsetX(displayId),
+                mMagnificationController.getOffsetY(displayId));
     }
 
     private MagSpecMatcher closeTo(MagnificationSpec spec) {
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 032074a..de7d77d 100644
--- a/services/tests/servicestests/src/com/android/server/accessibility/MagnificationGestureHandlerTest.java
+++ b/services/tests/servicestests/src/com/android/server/accessibility/MagnificationGestureHandlerTest.java
@@ -26,16 +26,19 @@
 import static org.junit.Assert.assertFalse;
 import static org.junit.Assert.assertTrue;
 import static org.junit.Assert.fail;
+import static org.mockito.ArgumentMatchers.eq;
 import static org.mockito.Matchers.any;
 import static org.mockito.Matchers.anyInt;
 import static org.mockito.Mockito.doNothing;
 import static org.mockito.Mockito.mock;
 import static org.mockito.Mockito.times;
 import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.when;
 
 import android.animation.ValueAnimator;
 import android.annotation.NonNull;
 import android.content.Context;
+import android.os.Handler;
 import android.os.Message;
 import android.util.DebugUtils;
 import android.view.InputDevice;
@@ -104,14 +107,9 @@
     public static final float DEFAULT_X = 301;
     public static final float DEFAULT_Y = 299;
 
+    private static final int DISPLAY_0 = 0;
+
     private Context mContext;
-    final AccessibilityManagerService mMockAms = mock(AccessibilityManagerService.class);
-    final WindowManagerInternal mMockWindowManager = mock(WindowManagerInternal.class);
-    final MessageCapturingHandler mMessageCapturingHandler =
-            new MessageCapturingHandler(null);
-    final ValueAnimator mMockValueAnimator = mock(ValueAnimator.class);
-    MagnificationController.SettingsBridge mMockSettingsBridge =
-            mock(MagnificationController.SettingsBridge.class);
     MagnificationController mMagnificationController;
 
     private OffsettableClock mClock;
@@ -123,18 +121,26 @@
     @Before
     public void setUp() {
         mContext = InstrumentationRegistry.getContext();
-        mMagnificationController = new MagnificationController(mContext, mMockAms, new Object(),
-                mMessageCapturingHandler, mMockWindowManager, mMockValueAnimator,
-                mMockSettingsBridge) {
+        final MagnificationController.ControllerContext mockController =
+                mock(MagnificationController.ControllerContext.class);
+        final WindowManagerInternal mockWindowManager = mock(WindowManagerInternal.class);
+        when(mockController.getContext()).thenReturn(mContext);
+        when(mockController.getAms()).thenReturn(mock(AccessibilityManagerService.class));
+        when(mockController.getWindowManager()).thenReturn(mockWindowManager);
+        when(mockController.getHandler()).thenReturn(new Handler(mContext.getMainLooper()));
+        when(mockController.newValueAnimator()).thenReturn(new ValueAnimator());
+        when(mockController.getAnimationDuration()).thenReturn(1000L);
+        when(mockWindowManager.setMagnificationCallbacks(eq(DISPLAY_0), any())).thenReturn(true);
+        mMagnificationController = new MagnificationController(mockController, new Object()) {
             @Override
-            public boolean magnificationRegionContains(float x, float y) {
+            public boolean magnificationRegionContains(int displayId, float x, float y) {
                 return true;
             }
 
             @Override
-            void setForceShowMagnifiableBounds(boolean show) {}
+            void setForceShowMagnifiableBounds(int displayId, boolean show) {}
         };
-        mMagnificationController.register();
+        mMagnificationController.register(DISPLAY_0);
         mClock = new OffsettableClock.Stopped();
 
         boolean detectTripleTap = true;
@@ -144,7 +150,7 @@
 
     @After
     public void tearDown() {
-        mMagnificationController.unregister();
+        mMagnificationController.unregister(DISPLAY_0);
     }
 
     @NonNull
@@ -152,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) {
@@ -302,6 +308,24 @@
         assertZoomsImmediatelyOnSwipeFrom(STATE_SHORTCUT_TRIGGERED);
     }
 
+    @Test
+    public void testMultiTap_outOfDistanceSlop_shouldInIdle() {
+        // All delay motion events should be sent, if multi-tap with out of distance slop.
+        // STATE_IDLE will check if tapCount() < 2.
+        allowEventDelegation();
+        assertStaysIn(STATE_IDLE, () -> {
+            tap();
+            tap(DEFAULT_X * 2, DEFAULT_Y * 2);
+        });
+        assertStaysIn(STATE_IDLE, () -> {
+            tap();
+            tap(DEFAULT_X * 2, DEFAULT_Y * 2);
+            tap();
+            tap(DEFAULT_X * 2, DEFAULT_Y * 2);
+            tap();
+        });
+    }
+
     private void assertZoomsImmediatelyOnSwipeFrom(int state) {
         goFromStateIdleTo(state);
         swipeAndHold();
@@ -509,7 +533,7 @@
     }
 
     private boolean isZoomed() {
-        return mMgh.mMagnificationController.isMagnifying();
+        return mMgh.mMagnificationController.isMagnifying(DISPLAY_0);
     }
 
     private int tapCount() {
@@ -525,6 +549,11 @@
         send(upEvent());
     }
 
+    private void tap(float x, float y) {
+        send(downEvent(x, y));
+        send(upEvent(x, y));
+    }
+
     private void swipe() {
         swipeAndHold();
         send(upEvent());
@@ -566,18 +595,26 @@
     }
 
     private MotionEvent downEvent() {
+        return downEvent(DEFAULT_X, DEFAULT_Y);
+    }
+
+    private MotionEvent downEvent(float x, float y) {
         mLastDownTime = mClock.now();
         return fromTouchscreen(MotionEvent.obtain(mLastDownTime, mLastDownTime,
-                ACTION_DOWN, DEFAULT_X, DEFAULT_Y, 0));
+                ACTION_DOWN, x, y, 0));
     }
 
     private MotionEvent upEvent() {
-        return upEvent(mLastDownTime);
+        return upEvent(DEFAULT_X, DEFAULT_Y, mLastDownTime);
     }
 
-    private MotionEvent upEvent(long downTime) {
+    private MotionEvent upEvent(float x, float y) {
+        return upEvent(x, y, mLastDownTime);
+    }
+
+    private MotionEvent upEvent(float x, float y, long downTime) {
         return fromTouchscreen(MotionEvent.obtain(downTime, mClock.now(),
-                MotionEvent.ACTION_UP, DEFAULT_X, DEFAULT_Y, 0));
+                MotionEvent.ACTION_UP, x, y, 0));
     }
 
     private MotionEvent pointerEvent(int action, float x, float y) {
diff --git a/services/tests/servicestests/src/com/android/server/adb/AdbDebuggingManagerTest.java b/services/tests/servicestests/src/com/android/server/adb/AdbDebuggingManagerTest.java
new file mode 100644
index 0000000..65af677
--- /dev/null
+++ b/services/tests/servicestests/src/com/android/server/adb/AdbDebuggingManagerTest.java
@@ -0,0 +1,497 @@
+/*
+ * 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.adb;
+
+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.assertTrue;
+import static org.junit.Assert.fail;
+
+import android.content.Context;
+import android.provider.Settings;
+import android.util.Log;
+
+import androidx.test.InstrumentationRegistry;
+
+import com.android.server.FgThread;
+
+import org.junit.After;
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.JUnit4;
+
+import java.io.File;
+import java.util.concurrent.ArrayBlockingQueue;
+import java.util.concurrent.BlockingQueue;
+import java.util.concurrent.CountDownLatch;
+import java.util.concurrent.TimeUnit;
+
+@RunWith(JUnit4.class)
+public final class AdbDebuggingManagerTest {
+
+    private static final String TAG = "AdbDebuggingManagerTest";
+
+    // This component is passed to the AdbDebuggingManager to act as the activity that can confirm
+    // unknown adb keys.  An overlay package was first attempted to override the
+    // config_customAdbPublicKeyConfirmationComponent config, but the value from that package was
+    // not being read.
+    private static final String ADB_CONFIRM_COMPONENT =
+            "com.android.frameworks.servicestests/"
+                    + "com.android.server.adb.AdbDebuggingManagerTestActivity";
+
+    // The base64 encoding of the values 'test key 1' and 'test key 2'.
+    private static final String TEST_KEY_1 = "dGVzdCBrZXkgMQo=";
+    private static final String TEST_KEY_2 = "dGVzdCBrZXkgMgo=";
+
+    // This response is received from the AdbDebuggingHandler when the key is allowed to connect
+    private static final String RESPONSE_KEY_ALLOWED = "OK";
+    // This response is received from the AdbDebuggingHandler when the key is not allowed to connect
+    private static final String RESPONSE_KEY_DENIED = "NO";
+
+    // wait up to 5 seconds for any blocking queries
+    private static final long TIMEOUT = 5000;
+    private static final TimeUnit TIMEOUT_TIME_UNIT = TimeUnit.MILLISECONDS;
+
+    private Context mContext;
+    private AdbDebuggingManager mManager;
+    private AdbDebuggingManager.AdbDebuggingThread mThread;
+    private AdbDebuggingManager.AdbDebuggingHandler mHandler;
+    private AdbDebuggingManager.AdbKeyStore mKeyStore;
+    private BlockingQueue<TestResult> mBlockingQueue;
+    private long mOriginalAllowedConnectionTime;
+    private File mKeyFile;
+
+    @Before
+    public void setUp() throws Exception {
+        mContext = InstrumentationRegistry.getContext();
+        mManager = new AdbDebuggingManager(mContext, ADB_CONFIRM_COMPONENT);
+        mKeyFile = new File(mContext.getFilesDir(), "test_adb_keys.xml");
+        if (mKeyFile.exists()) {
+            mKeyFile.delete();
+        }
+        mThread = new AdbDebuggingThreadTest();
+        mKeyStore = mManager.new AdbKeyStore(mKeyFile);
+        mHandler = mManager.new AdbDebuggingHandler(FgThread.get().getLooper(), mThread, mKeyStore);
+        mOriginalAllowedConnectionTime = mKeyStore.getAllowedConnectionTime();
+        mBlockingQueue = new ArrayBlockingQueue<>(1);
+
+    }
+
+    @After
+    public void tearDown() throws Exception {
+        mKeyStore.deleteKeyStore();
+        setAllowedConnectionTime(mOriginalAllowedConnectionTime);
+    }
+
+    /**
+     * Sets the allowed connection time within which a subsequent connection from a key for which
+     * the user selected the 'Always allow' option will be allowed without user interaction.
+     */
+    private void setAllowedConnectionTime(long connectionTime) {
+        Settings.Global.putLong(mContext.getContentResolver(),
+                Settings.Global.ADB_ALLOWED_CONNECTION_TIME, connectionTime);
+    };
+
+    @Test
+    public void testAllowNewKeyOnce() throws Exception {
+        // Verifies the behavior when a new key first attempts to connect to a device. During the
+        // first connection the ADB confirmation activity should be launched to prompt the user to
+        // allow the connection with an option to always allow connections from this key.
+
+        // Verify if the user allows the key but does not select the option to 'always
+        // allow' that the connection is allowed but the key is not stored.
+        runAdbTest(TEST_KEY_1, true, false, false);
+    }
+
+    @Test
+    public void testDenyNewKey() throws Exception {
+        // Verifies if the user does not allow the key then the connection is not allowed and the
+        // key is not stored.
+        runAdbTest(TEST_KEY_1, false, false, false);
+    }
+
+    @Test
+    public void testDisconnectAlwaysAllowKey() throws Exception {
+        // When a key is disconnected from a device ADB should send a disconnect message; this
+        // message should trigger an update of the last connection time for the currently connected
+        // key.
+
+        // Allow a connection from a new key with the 'Always allow' option selected.
+        runAdbTest(TEST_KEY_1, true, true, false);
+
+        // Get the last connection time for the currently connected key to verify that it is updated
+        // after the disconnect.
+        long lastConnectionTime = mKeyStore.getLastConnectionTime(TEST_KEY_1);
+
+        // Sleep for a small amount of time to ensure a difference can be observed in the last
+        // connection time after a disconnect.
+        Thread.sleep(10);
+
+        // Send the disconnect message for the currently connected key to trigger an update of the
+        // last connection time.
+        mHandler.obtainMessage(
+                AdbDebuggingManager.AdbDebuggingHandler.MESSAGE_ADB_DISCONNECT).sendToTarget();
+
+        // Use a latch to ensure the test does not exit untill the Runnable has been processed.
+        CountDownLatch latch = new CountDownLatch(1);
+
+        // Post a new Runnable to the handler to ensure it runs after the disconnect message is
+        // processed.
+        mHandler.post(() -> {
+            assertNotEquals(
+                    "The last connection time was not updated after the disconnect",
+                    lastConnectionTime,
+                    mKeyStore.getLastConnectionTime(TEST_KEY_1));
+            latch.countDown();
+        });
+        if (!latch.await(TIMEOUT, TIMEOUT_TIME_UNIT)) {
+            fail("The Runnable to verify the last connection time was updated did not complete "
+                    + "within the timeout period");
+        }
+    }
+
+    @Test
+    public void testDisconnectAllowedOnceKey() throws Exception {
+        // When a key is disconnected ADB should send a disconnect message; this message should
+        // essentially result in a noop for keys that the user only allows once since the last
+        // connection time is not maintained for these keys.
+
+        // Allow a connection from a new key with the 'Always allow' option set to false
+        runAdbTest(TEST_KEY_1, true, false, false);
+
+        // Send the disconnect message for the currently connected key.
+        mHandler.obtainMessage(
+                AdbDebuggingManager.AdbDebuggingHandler.MESSAGE_ADB_DISCONNECT).sendToTarget();
+
+        // Verify that the disconnected key is not automatically allowed on a subsequent connection.
+        runAdbTest(TEST_KEY_1, true, false, false);
+    }
+
+    @Test
+    public void testAlwaysAllowConnectionFromKey() throws Exception {
+        // Verifies when the user selects the 'Always allow' option for the current key that
+        // subsequent connection attempts from that key are allowed.
+
+        // Allow a connection from a new key with the 'Always allow' option selected.
+        runAdbTest(TEST_KEY_1, true, true, false);
+
+        // Next attempt another connection with the same key and verify that the activity to prompt
+        // the user to accept the key is not launched.
+        runAdbTest(TEST_KEY_1, true, true, true);
+
+        // Verify that a different key is not automatically allowed.
+        runAdbTest(TEST_KEY_2, false, false, false);
+    }
+
+    @Test
+    public void testOriginalAlwaysAllowBehavior() throws Exception {
+        // If the Settings.Global.ADB_ALLOWED_CONNECTION_TIME setting is set to 0 then the original
+        // behavior of 'Always allow' should be restored.
+
+        // Accept the test key with the 'Always allow' option selected.
+        runAdbTest(TEST_KEY_1, true, true, false);
+
+        // Set the connection time to 0 to restore the original behavior.
+        setAllowedConnectionTime(0);
+
+        // Set the last connection time to the test key to a very small value to ensure it would
+        // fail the new test but would be allowed with the original behavior.
+        mKeyStore.setLastConnectionTime(TEST_KEY_1, 1);
+
+        // Run the test with the key and verify that the connection is automatically allowed.
+        runAdbTest(TEST_KEY_1, true, true, true);
+    }
+
+    @Test
+    public void testLastConnectionTimeUpdatedByScheduledJob() throws Exception {
+        // If a development device is left connected to a system beyond the allowed connection time
+        // a reboot of the device while connected could make it appear as though the last connection
+        // time is beyond the allowed window. A scheduled job runs daily while a key is connected
+        // to update the last connection time to the current time; this ensures if the device is
+        // rebooted while connected to a system the last connection time should be within 24 hours.
+
+        // Allow the key to connect with the 'Always allow' option selected
+        runAdbTest(TEST_KEY_1, true, true, false);
+
+        // Get the current last connection time for comparison after the scheduled job is run
+        long lastConnectionTime = mKeyStore.getLastConnectionTime(TEST_KEY_1);
+
+        // Sleep a small amount of time to ensure that the updated connection time changes
+        Thread.sleep(10);
+
+        // Send a message to the handler to update the last connection time for the active key
+        mHandler.obtainMessage(
+                AdbDebuggingManager.AdbDebuggingHandler.MESSAGE_ADB_UPDATE_KEY_CONNECTION_TIME)
+                .sendToTarget();
+
+        // Post a Runnable to the handler to ensure it runs after the update key connection time
+        // message is processed.
+        CountDownLatch latch = new CountDownLatch(1);
+        mHandler.post(() -> {
+            assertNotEquals(
+                    "The last connection time of the key was not updated after the update key "
+                            + "connection time message",
+                    lastConnectionTime, mKeyStore.getLastConnectionTime(TEST_KEY_1));
+            latch.countDown();
+        });
+        if (!latch.await(TIMEOUT, TIMEOUT_TIME_UNIT)) {
+            fail("The Runnable to verify the last connection time was updated did not complete "
+                    + "within the timeout period");
+        }
+    }
+
+    @Test
+    public void testKeystorePersisted() throws Exception {
+        // After any updates are made to the key store a message should be sent to persist the
+        // key store. This test verifies that a key that is always allowed is persisted in the key
+        // store along with its last connection time.
+
+        // Allow the key to connect with the 'Always allow' option selected
+        runAdbTest(TEST_KEY_1, true, true, false);
+
+        // Send a message to the handler to persist the updated keystore.
+        mHandler.obtainMessage(
+                AdbDebuggingManager.AdbDebuggingHandler.MESSAGE_ADB_PERSIST_KEY_STORE)
+                .sendToTarget();
+
+        // Post a Runnable to the handler to ensure the persist key store message has been processed
+        // using a new AdbKeyStore backed by the key file.
+        mHandler.post(() -> assertTrue(
+                "The key with the 'Always allow' option selected was not persisted in the keystore",
+                mManager.new AdbKeyStore(mKeyFile).isKeyAuthorized(TEST_KEY_1)));
+
+        // Get the current last connection time to ensure it is updated in the persisted keystore.
+        long lastConnectionTime = mKeyStore.getLastConnectionTime(TEST_KEY_1);
+
+        // Sleep a small amount of time to ensure the last connection time is updated.
+        Thread.sleep(10);
+
+        // Send a message to the handler to update the last connection time for the active key.
+        mHandler.obtainMessage(
+                AdbDebuggingManager.AdbDebuggingHandler.MESSAGE_ADB_UPDATE_KEY_CONNECTION_TIME)
+                .sendToTarget();
+
+        // Persist the updated last connection time.
+        mHandler.obtainMessage(
+                AdbDebuggingManager.AdbDebuggingHandler.MESSAGE_ADB_PERSIST_KEY_STORE)
+                .sendToTarget();
+
+        // Post a Runnable with a new key store backed by the key file to verify that the last
+        // connection time obtained above is different from the persisted updated value.
+        CountDownLatch latch = new CountDownLatch(1);
+        mHandler.post(() -> {
+            assertNotEquals(
+                    "The last connection time in the key file was not updated after the update "
+                            + "connection time message", lastConnectionTime,
+                    mManager.new AdbKeyStore(mKeyFile).getLastConnectionTime(TEST_KEY_1));
+            latch.countDown();
+        });
+        if (!latch.await(TIMEOUT, TIMEOUT_TIME_UNIT)) {
+            fail("The Runnable to verify the last connection time was updated did not complete "
+                    + "within the timeout period");
+        }
+    }
+
+    @Test
+    public void testAdbClearRemovesActiveKey() throws Exception {
+        // If the user selects the option to 'Revoke USB debugging authorizations' while an 'Always
+        // allow' key is connected that key should be deleted as well.
+
+        // Allow the key to connect with the 'Always allow' option selected
+        runAdbTest(TEST_KEY_1, true, true, false);
+
+        // Send a message to the handler to clear the adb authorizations.
+        mHandler.obtainMessage(
+                AdbDebuggingManager.AdbDebuggingHandler.MESSAGE_ADB_CLEAR).sendToTarget();
+
+        // Send a message to disconnect the currently connected key
+        mHandler.obtainMessage(
+                AdbDebuggingManager.AdbDebuggingHandler.MESSAGE_ADB_DISCONNECT).sendToTarget();
+
+        // Post a Runnable to ensure the disconnect has completed to verify the 'Always allow' key
+        // that was connected when the clear was sent requires authorization.
+        CountDownLatch latch = new CountDownLatch(1);
+        mHandler.post(() -> {
+            assertFalse(
+                    "The currently connected 'always allow' key should not be authorized after an"
+                            + " adb"
+                            + " clear message.",
+                    mKeyStore.isKeyAuthorized(TEST_KEY_1));
+            latch.countDown();
+        });
+        if (!latch.await(TIMEOUT, TIMEOUT_TIME_UNIT)) {
+            fail("The Runnable to verify the key is not authorized did not complete within the "
+                    + "timeout period");
+        }
+    }
+
+    @Test
+    public void testAdbGrantRevokedIfLastConnectionBeyondAllowedTime() throws Exception {
+        // If the user selects the 'Always allow' option then subsequent connections from the key
+        // will be allowed as long as the connection is within the allowed window. Once the last
+        // connection time is beyond this window the user should be prompted to allow the key again.
+
+        // Allow the key to connect with the 'Always allow' option selected
+        runAdbTest(TEST_KEY_1, true, true, false);
+
+        // Set the allowed window to a small value to ensure the time is beyond the allowed window.
+        setAllowedConnectionTime(1);
+
+        // Sleep for a small amount of time to exceed the allowed window.
+        Thread.sleep(10);
+
+        // A new connection from this key should prompt the user again.
+        runAdbTest(TEST_KEY_1, true, true, false);
+    }
+
+    @Test
+    public void testLastConnectionTimeCannotBeSetBack() throws Exception {
+        // When a device is first booted there is a possibility that the system time will be set to
+        // the build time of the system image. If a device is connected to a system during a reboot
+        // this could cause the connection time to be set in the past; if the device time is not
+        // corrected before the device is disconnected then a subsequent connection with the time
+        // corrected would appear as though the last connection time was beyond the allowed window,
+        // and the user would be required to authorize the connection again. This test verifies that
+        // the AdbKeyStore does not update the last connection time if it is less than the
+        // previously written connection time.
+
+        // Allow the key to connect with the 'Always allow' option selected
+        runAdbTest(TEST_KEY_1, true, true, false);
+
+        // Get the last connection time that was written to the key store.
+        long lastConnectionTime = mKeyStore.getLastConnectionTime(TEST_KEY_1);
+
+        // Attempt to set the last connection time to 1970
+        mKeyStore.setLastConnectionTime(TEST_KEY_1, 0);
+        assertEquals(
+                "The last connection time in the adb key store should not be set to a value less "
+                        + "than the previous connection time",
+                lastConnectionTime, mKeyStore.getLastConnectionTime(TEST_KEY_1));
+
+        // Attempt to set the last connection time just beyond the allowed window.
+        mKeyStore.setLastConnectionTime(TEST_KEY_1,
+                Math.max(0, lastConnectionTime - (mKeyStore.getAllowedConnectionTime() + 1)));
+        assertEquals(
+                "The last connection time in the adb key store should not be set to a value less "
+                        + "than the previous connection time",
+                lastConnectionTime, mKeyStore.getLastConnectionTime(TEST_KEY_1));
+    }
+
+    /**
+     * Runs an adb test with the provided configuration.
+     *
+     * @param key The base64 encoding of the key to be used during the test.
+     * @param allowKey boolean indicating whether the key should be allowed to connect.
+     * @param alwaysAllow boolean indicating whether the 'Always allow' option should be selected.
+     * @param autoAllowExpected boolean indicating whether the key is expected to be automatically
+     *                          allowed without user interaction.
+     */
+    private void runAdbTest(String key, boolean allowKey, boolean alwaysAllow,
+            boolean autoAllowExpected) throws Exception {
+        // if the key should not be automatically allowed then set up the activity
+        if (!autoAllowExpected) {
+            new AdbDebuggingManagerTestActivity.Configurator()
+                    .setExpectedKey(key)
+                    .setAllowKey(allowKey)
+                    .setAlwaysAllow(alwaysAllow)
+                    .setHandler(mHandler)
+                    .setBlockingQueue(mBlockingQueue);
+        }
+        // send the message indicating a new key is attempting to connect
+        mHandler.obtainMessage(AdbDebuggingManager.AdbDebuggingHandler.MESSAGE_ADB_CONFIRM,
+                key).sendToTarget();
+        // if the key should not be automatically allowed then the ADB public key confirmation
+        // activity should be launched
+        if (!autoAllowExpected) {
+            TestResult activityResult = mBlockingQueue.poll(TIMEOUT, TIMEOUT_TIME_UNIT);
+            assertNotNull(
+                    "The ADB public key confirmation activity did not complete within the timeout"
+                            + " period", activityResult);
+            assertEquals("The ADB public key activity failed with result: " + activityResult,
+                    TestResult.RESULT_ACTIVITY_LAUNCHED, activityResult.mReturnCode);
+        }
+        // If the activity was launched it should send a response back to the manager that would
+        // trigger a response to the thread, or if the key is a known valid key then a response
+        // should be sent back without requiring interaction with the activity.
+        TestResult threadResult = mBlockingQueue.poll(TIMEOUT, TIMEOUT_TIME_UNIT);
+        assertNotNull("A response was not sent to the thread within the timeout period",
+                threadResult);
+        // verify that the result is an expected message from the thread
+        assertEquals("An unexpected result was received: " + threadResult,
+                TestResult.RESULT_RESPONSE_RECEIVED, threadResult.mReturnCode);
+        assertEquals("The manager did not send the proper response for allowKey = " + allowKey,
+                allowKey ? RESPONSE_KEY_ALLOWED : RESPONSE_KEY_DENIED, threadResult.mMessage);
+        // if the key is not allowed or not always allowed verify it is not in the key store
+        if (!allowKey || !alwaysAllow) {
+            assertFalse(
+                    "The key should not be allowed automatically on subsequent connection attempts",
+                    mKeyStore.isKeyAuthorized(key));
+        }
+    }
+
+    /**
+     * Helper class that extends AdbDebuggingThread to receive the response from AdbDebuggingManager
+     * indicating whether the key should be allowed to  connect.
+     */
+    class AdbDebuggingThreadTest extends AdbDebuggingManager.AdbDebuggingThread {
+        AdbDebuggingThreadTest() {
+            mManager.super();
+        }
+
+        @Override
+        public void sendResponse(String msg) {
+            TestResult result = new TestResult(TestResult.RESULT_RESPONSE_RECEIVED, msg);
+            try {
+                mBlockingQueue.put(result);
+            } catch (InterruptedException e) {
+                Log.e(TAG,
+                        "Caught an InterruptedException putting the result in the queue: " + result,
+                        e);
+            }
+        }
+    }
+
+    /**
+     * Contains the result for the current portion of the test along with any corresponding
+     * messages.
+     */
+    public static class TestResult {
+        public int mReturnCode;
+        public String mMessage;
+
+        public static final int RESULT_ACTIVITY_LAUNCHED = 1;
+        public static final int RESULT_UNEXPECTED_KEY = 2;
+        public static final int RESULT_RESPONSE_RECEIVED = 3;
+
+        public TestResult(int returnCode) {
+            this(returnCode, null);
+        }
+
+        public TestResult(int returnCode, String message) {
+            mReturnCode = returnCode;
+            mMessage = message;
+        }
+
+        @Override
+        public String toString() {
+            return "{mReturnCode = " + mReturnCode + ", mMessage = " + mMessage + "}";
+        }
+    }
+}
diff --git a/services/tests/servicestests/src/com/android/server/adb/AdbDebuggingManagerTestActivity.java b/services/tests/servicestests/src/com/android/server/adb/AdbDebuggingManagerTestActivity.java
new file mode 100644
index 0000000..1a9c180
--- /dev/null
+++ b/services/tests/servicestests/src/com/android/server/adb/AdbDebuggingManagerTestActivity.java
@@ -0,0 +1,142 @@
+/*
+ * 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.adb;
+
+import static com.android.server.adb.AdbDebuggingManagerTest.TestResult;
+
+import android.app.Activity;
+import android.content.Intent;
+import android.os.Bundle;
+import android.os.Message;
+import android.util.Log;
+
+import java.util.concurrent.BlockingQueue;
+
+/**
+ * Helper Activity used to test the AdbDebuggingManager's prompt to allow an adb key.
+ */
+public class AdbDebuggingManagerTestActivity extends Activity {
+
+    private static final String TAG = "AdbDebuggingManagerTestActivity";
+
+    /*
+     * Static values that must be set before each test to modify the behavior of the Activity.
+     */
+    private static AdbDebuggingManager.AdbDebuggingHandler sHandler;
+    private static boolean sAllowKey;
+    private static boolean sAlwaysAllow;
+    private static String sExpectedKey;
+    private static BlockingQueue sBlockingQueue;
+
+    /**
+     * Receives the Intent sent from the AdbDebuggingManager and sends the preconfigured response.
+     */
+    @Override
+    public void onCreate(Bundle bundle) {
+        super.onCreate(bundle);
+        Intent intent = getIntent();
+        String key = intent.getStringExtra("key");
+        if (!key.equals(sExpectedKey)) {
+            TestResult result = new TestResult(TestResult.RESULT_UNEXPECTED_KEY, key);
+            postResult(result);
+            finish();
+            return;
+        }
+        // Post the result that the activity was successfully launched as expected and a response
+        // is being sent to let the test method know that it should move on to waiting for the next
+        // expected response from the AdbDebuggingManager.
+        TestResult result = new TestResult(
+                AdbDebuggingManagerTest.TestResult.RESULT_ACTIVITY_LAUNCHED);
+        postResult(result);
+
+        // Initialize the message based on the preconfigured values. If the key is accepted the
+        // AdbDebuggingManager expects the key to be in the obj field of the message, and if the
+        // user selects the 'Always allow' option the manager expects the arg1 field to be set to 1.
+        int messageType;
+        if (sAllowKey) {
+            messageType = AdbDebuggingManager.AdbDebuggingHandler.MESSAGE_ADB_ALLOW;
+        } else {
+            messageType = AdbDebuggingManager.AdbDebuggingHandler.MESSAGE_ADB_DENY;
+        }
+        Message message = sHandler.obtainMessage(messageType);
+        message.obj = key;
+        if (sAlwaysAllow) {
+            message.arg1 = 1;
+        }
+        finish();
+        sHandler.sendMessage(message);
+    }
+
+    /**
+     * Posts the result of the activity to the test method.
+     */
+    private void postResult(TestResult result) {
+        try {
+            sBlockingQueue.put(result);
+        } catch (InterruptedException e) {
+            Log.e(TAG, "Caught an InterruptedException posting the result " + result, e);
+        }
+    }
+
+    /**
+     * Allows test methods to specify the behavior of the Activity before it is invoked by the
+     * AdbDebuggingManager.
+     */
+    public static class Configurator {
+
+        /**
+         * Sets the test handler to be used by this activity to send the configured response.
+         */
+        public Configurator setHandler(AdbDebuggingManager.AdbDebuggingHandler handler) {
+            sHandler = handler;
+            return this;
+        }
+
+        /**
+         * Sets whether the key should be allowed for this test.
+         */
+        public Configurator setAllowKey(boolean allow) {
+            sAllowKey = allow;
+            return this;
+        }
+
+        /**
+         * Sets whether the 'Always allow' option should be selected for this test.
+         */
+        public Configurator setAlwaysAllow(boolean alwaysAllow) {
+            sAlwaysAllow = alwaysAllow;
+            return this;
+        }
+
+        /**
+         * Sets the key that should be expected from the AdbDebuggingManager for this test.
+         */
+        public Configurator setExpectedKey(String expectedKey) {
+            sExpectedKey = expectedKey;
+            return this;
+        }
+
+        /**
+         * Sets the BlockingQueue that should be used to post the result of the Activity back to the
+         * test method.
+         */
+        public Configurator setBlockingQueue(BlockingQueue blockingQueue) {
+            sBlockingQueue = blockingQueue;
+            return this;
+        }
+    }
+}
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/backup/testutils/IPackageManagerStub.java b/services/tests/servicestests/src/com/android/server/backup/testutils/IPackageManagerStub.java
new file mode 100644
index 0000000..095a1de
--- /dev/null
+++ b/services/tests/servicestests/src/com/android/server/backup/testutils/IPackageManagerStub.java
@@ -0,0 +1,1172 @@
+/*
+ * 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.backup.testutils;
+
+import android.content.ComponentName;
+import android.content.Intent;
+import android.content.IntentFilter;
+import android.content.IntentSender;
+import android.content.pm.ActivityInfo;
+import android.content.pm.ApplicationInfo;
+import android.content.pm.ChangedPackages;
+import android.content.pm.IDexModuleRegisterCallback;
+import android.content.pm.IOnPermissionsChangeListener;
+import android.content.pm.IPackageDataObserver;
+import android.content.pm.IPackageDeleteObserver;
+import android.content.pm.IPackageDeleteObserver2;
+import android.content.pm.IPackageInstaller;
+import android.content.pm.IPackageManager;
+import android.content.pm.IPackageMoveObserver;
+import android.content.pm.IPackageStatsObserver;
+import android.content.pm.InstrumentationInfo;
+import android.content.pm.KeySet;
+import android.content.pm.ModuleInfo;
+import android.content.pm.PackageInfo;
+import android.content.pm.PackageManager;
+import android.content.pm.ParceledListSlice;
+import android.content.pm.PermissionGroupInfo;
+import android.content.pm.PermissionInfo;
+import android.content.pm.ProviderInfo;
+import android.content.pm.ResolveInfo;
+import android.content.pm.ServiceInfo;
+import android.content.pm.SuspendDialogInfo;
+import android.content.pm.VerifierDeviceIdentity;
+import android.content.pm.VersionedPackage;
+import android.content.pm.dex.IArtManager;
+import android.graphics.Bitmap;
+import android.os.IBinder;
+import android.os.PersistableBundle;
+import android.os.RemoteException;
+import java.util.List;
+
+/**
+ * Stub for IPackageManager to use in tests.
+ */
+public class IPackageManagerStub implements IPackageManager {
+    public static PackageInfo sPackageInfo;
+    public static int sApplicationEnabledSetting = PackageManager.COMPONENT_ENABLED_STATE_DEFAULT;
+
+    @Override
+    public PackageInfo getPackageInfo(String packageName, int flags, int userId)
+        throws RemoteException {
+        return sPackageInfo;
+    }
+
+    @Override
+    public int getApplicationEnabledSetting(String packageName, int userId) throws RemoteException {
+        return sApplicationEnabledSetting;
+    }
+
+    @Override
+    public void checkPackageStartable(String packageName, int userId) throws RemoteException {
+
+    }
+
+    @Override
+    public boolean isPackageAvailable(String packageName, int userId) throws RemoteException {
+        return false;
+    }
+
+    @Override
+    public PackageInfo getPackageInfoVersioned(VersionedPackage versionedPackage, int flags,
+        int userId) throws RemoteException {
+        return null;
+    }
+
+    @Override
+    public int getPackageUid(String packageName, int flags, int userId) throws RemoteException {
+        return 0;
+    }
+
+    @Override
+    public int[] getPackageGids(String packageName, int flags, int userId) throws RemoteException {
+        return new int[0];
+    }
+
+    @Override
+    public String[] currentToCanonicalPackageNames(String[] names) throws RemoteException {
+        return new String[0];
+    }
+
+    @Override
+    public String[] canonicalToCurrentPackageNames(String[] names) throws RemoteException {
+        return new String[0];
+    }
+
+    @Override
+    public PermissionInfo getPermissionInfo(String name, String packageName, int flags)
+        throws RemoteException {
+        return null;
+    }
+
+    @Override
+    public ParceledListSlice queryPermissionsByGroup(String group, int flags)
+        throws RemoteException {
+        return null;
+    }
+
+    @Override
+    public PermissionGroupInfo getPermissionGroupInfo(String name, int flags)
+        throws RemoteException {
+        return null;
+    }
+
+    @Override
+    public ParceledListSlice getAllPermissionGroups(int flags) throws RemoteException {
+        return null;
+    }
+
+    @Override
+    public ApplicationInfo getApplicationInfo(String packageName, int flags, int userId)
+        throws RemoteException {
+        return null;
+    }
+
+    @Override
+    public ActivityInfo getActivityInfo(ComponentName className, int flags, int userId)
+        throws RemoteException {
+        return null;
+    }
+
+    @Override
+    public boolean activitySupportsIntent(ComponentName className, Intent intent,
+        String resolvedType)
+        throws RemoteException {
+        return false;
+    }
+
+    @Override
+    public ActivityInfo getReceiverInfo(ComponentName className, int flags, int userId)
+        throws RemoteException {
+        return null;
+    }
+
+    @Override
+    public ServiceInfo getServiceInfo(ComponentName className, int flags, int userId)
+        throws RemoteException {
+        return null;
+    }
+
+    @Override
+    public ProviderInfo getProviderInfo(ComponentName className, int flags, int userId)
+        throws RemoteException {
+        return null;
+    }
+
+    @Override
+    public int checkPermission(String permName, String pkgName, int userId) throws RemoteException {
+        return 0;
+    }
+
+    @Override
+    public int checkUidPermission(String permName, int uid) throws RemoteException {
+        return 0;
+    }
+
+    @Override
+    public boolean addPermission(PermissionInfo info) throws RemoteException {
+        return false;
+    }
+
+    @Override
+    public void removePermission(String name) throws RemoteException {
+
+    }
+
+    @Override
+    public void grantRuntimePermission(String packageName, String permissionName, int userId)
+        throws RemoteException {
+
+    }
+
+    @Override
+    public void revokeRuntimePermission(String packageName, String permissionName, int userId)
+        throws RemoteException {
+
+    }
+
+    @Override
+    public void resetRuntimePermissions() throws RemoteException {
+
+    }
+
+    @Override
+    public int getPermissionFlags(String permissionName, String packageName, int userId)
+        throws RemoteException {
+        return 0;
+    }
+
+    @Override
+    public void updatePermissionFlags(String permissionName, String packageName, int flagMask,
+        int flagValues, int userId) throws RemoteException {
+
+    }
+
+    @Override
+    public void updatePermissionFlagsForAllApps(int flagMask, int flagValues, int userId)
+        throws RemoteException {
+
+    }
+
+    @Override
+    public boolean shouldShowRequestPermissionRationale(String permissionName, String packageName,
+        int userId) throws RemoteException {
+        return false;
+    }
+
+    @Override
+    public boolean isProtectedBroadcast(String actionName) throws RemoteException {
+        return false;
+    }
+
+    @Override
+    public int checkSignatures(String pkg1, String pkg2) throws RemoteException {
+        return 0;
+    }
+
+    @Override
+    public int checkUidSignatures(int uid1, int uid2) throws RemoteException {
+        return 0;
+    }
+
+    @Override
+    public List<String> getAllPackages() throws RemoteException {
+        return null;
+    }
+
+    @Override
+    public String[] getPackagesForUid(int uid) throws RemoteException {
+        return new String[0];
+    }
+
+    @Override
+    public String getNameForUid(int uid) throws RemoteException {
+        return null;
+    }
+
+    @Override
+    public String[] getNamesForUids(int[] uids) throws RemoteException {
+        return new String[0];
+    }
+
+    @Override
+    public int getUidForSharedUser(String sharedUserName) throws RemoteException {
+        return 0;
+    }
+
+    @Override
+    public int getFlagsForUid(int uid) throws RemoteException {
+        return 0;
+    }
+
+    @Override
+    public int getPrivateFlagsForUid(int uid) throws RemoteException {
+        return 0;
+    }
+
+    @Override
+    public boolean isUidPrivileged(int uid) throws RemoteException {
+        return false;
+    }
+
+    @Override
+    public String[] getAppOpPermissionPackages(String permissionName) throws RemoteException {
+        return new String[0];
+    }
+
+    @Override
+    public ResolveInfo resolveIntent(Intent intent, String resolvedType, int flags, int userId)
+        throws RemoteException {
+        return null;
+    }
+
+    @Override
+    public ResolveInfo findPersistentPreferredActivity(Intent intent, int userId)
+        throws RemoteException {
+        return null;
+    }
+
+    @Override
+    public boolean canForwardTo(Intent intent, String resolvedType, int sourceUserId,
+        int targetUserId) throws RemoteException {
+        return false;
+    }
+
+    @Override
+    public ParceledListSlice queryIntentActivities(Intent intent, String resolvedType, int flags,
+        int userId) throws RemoteException {
+        return null;
+    }
+
+    @Override
+    public ParceledListSlice queryIntentActivityOptions(ComponentName caller, Intent[] specifics,
+        String[] specificTypes, Intent intent, String resolvedType, int flags, int userId)
+        throws RemoteException {
+        return null;
+    }
+
+    @Override
+    public ParceledListSlice queryIntentReceivers(Intent intent, String resolvedType, int flags,
+        int userId) throws RemoteException {
+        return null;
+    }
+
+    @Override
+    public ResolveInfo resolveService(Intent intent, String resolvedType, int flags, int userId)
+        throws RemoteException {
+        return null;
+    }
+
+    @Override
+    public ParceledListSlice queryIntentServices(Intent intent, String resolvedType, int flags,
+        int userId) throws RemoteException {
+        return null;
+    }
+
+    @Override
+    public ParceledListSlice queryIntentContentProviders(Intent intent, String resolvedType,
+        int flags, int userId) throws RemoteException {
+        return null;
+    }
+
+    @Override
+    public ParceledListSlice getInstalledPackages(int flags, int userId) throws RemoteException {
+        return null;
+    }
+
+    @Override
+    public ParceledListSlice getPackagesHoldingPermissions(String[] permissions, int flags,
+        int userId) throws RemoteException {
+        return null;
+    }
+
+    @Override
+    public ParceledListSlice getInstalledApplications(int flags, int userId)
+        throws RemoteException {
+        return null;
+    }
+
+    @Override
+    public ParceledListSlice getPersistentApplications(int flags) throws RemoteException {
+        return null;
+    }
+
+    @Override
+    public ProviderInfo resolveContentProvider(String name, int flags, int userId)
+        throws RemoteException {
+        return null;
+    }
+
+    @Override
+    public void querySyncProviders(List<String> outNames, List<ProviderInfo> outInfo)
+        throws RemoteException {
+
+    }
+
+    @Override
+    public ParceledListSlice queryContentProviders(String processName, int uid, int flags,
+        String metaDataKey) throws RemoteException {
+        return null;
+    }
+
+    @Override
+    public InstrumentationInfo getInstrumentationInfo(ComponentName className, int flags)
+        throws RemoteException {
+        return null;
+    }
+
+    @Override
+    public ParceledListSlice queryInstrumentation(String targetPackage, int flags)
+        throws RemoteException {
+        return null;
+    }
+
+    @Override
+    public void finishPackageInstall(int token, boolean didLaunch) throws RemoteException {
+
+    }
+
+    @Override
+    public void setInstallerPackageName(String targetPackage, String installerPackageName)
+        throws RemoteException {
+
+    }
+
+    @Override
+    public void setApplicationCategoryHint(String packageName, int categoryHint,
+        String callerPackageName) throws RemoteException {
+
+    }
+
+    @Override
+    public void deletePackageAsUser(String packageName, int versionCode,
+        IPackageDeleteObserver observer, int userId, int flags) throws RemoteException {
+
+    }
+
+    @Override
+    public void deletePackageVersioned(VersionedPackage versionedPackage,
+        IPackageDeleteObserver2 observer, int userId, int flags) throws RemoteException {
+
+    }
+
+    @Override
+    public String getInstallerPackageName(String packageName) throws RemoteException {
+        return null;
+    }
+
+    @Override
+    public void resetApplicationPreferences(int userId) throws RemoteException {
+
+    }
+
+    @Override
+    public ResolveInfo getLastChosenActivity(Intent intent, String resolvedType, int flags)
+        throws RemoteException {
+        return null;
+    }
+
+    @Override
+    public void setLastChosenActivity(Intent intent, String resolvedType, int flags,
+        IntentFilter filter, int match, ComponentName activity) throws RemoteException {
+
+    }
+
+    @Override
+    public void addPreferredActivity(IntentFilter filter, int match, ComponentName[] set,
+        ComponentName activity, int userId) throws RemoteException {
+
+    }
+
+    @Override
+    public void replacePreferredActivity(IntentFilter filter, int match, ComponentName[] set,
+        ComponentName activity, int userId) throws RemoteException {
+
+    }
+
+    @Override
+    public void clearPackagePreferredActivities(String packageName) throws RemoteException {
+
+    }
+
+    @Override
+    public int getPreferredActivities(List<IntentFilter> outFilters,
+        List<ComponentName> outActivities, String packageName) throws RemoteException {
+        return 0;
+    }
+
+    @Override
+    public void addPersistentPreferredActivity(IntentFilter filter, ComponentName activity,
+        int userId) throws RemoteException {
+
+    }
+
+    @Override
+    public void clearPackagePersistentPreferredActivities(String packageName, int userId)
+        throws RemoteException {
+
+    }
+
+    @Override
+    public void addCrossProfileIntentFilter(IntentFilter intentFilter, String ownerPackage,
+        int sourceUserId, int targetUserId, int flags) throws RemoteException {
+
+    }
+
+    @Override
+    public void clearCrossProfileIntentFilters(int sourceUserId, String ownerPackage)
+        throws RemoteException {
+
+    }
+
+    @Override
+    public String[] setDistractingPackageRestrictionsAsUser(String[] packageNames,
+        int restrictionFlags, int userId) throws RemoteException {
+        return new String[0];
+    }
+
+    @Override
+    public String[] setPackagesSuspendedAsUser(String[] packageNames, boolean suspended,
+        PersistableBundle appExtras, PersistableBundle launcherExtras, SuspendDialogInfo dialogInfo,
+        String callingPackage, int userId) throws RemoteException {
+        return new String[0];
+    }
+
+    @Override
+    public String[] getUnsuspendablePackagesForUser(String[] packageNames, int userId)
+        throws RemoteException {
+        return new String[0];
+    }
+
+    @Override
+    public boolean isPackageSuspendedForUser(String packageName, int userId)
+        throws RemoteException {
+        return false;
+    }
+
+    @Override
+    public PersistableBundle getSuspendedPackageAppExtras(String packageName, int userId)
+        throws RemoteException {
+        return null;
+    }
+
+    @Override
+    public byte[] getPreferredActivityBackup(int userId) throws RemoteException {
+        return new byte[0];
+    }
+
+    @Override
+    public void restorePreferredActivities(byte[] backup, int userId) throws RemoteException {
+
+    }
+
+    @Override
+    public byte[] getDefaultAppsBackup(int userId) throws RemoteException {
+        return new byte[0];
+    }
+
+    @Override
+    public void restoreDefaultApps(byte[] backup, int userId) throws RemoteException {
+
+    }
+
+    @Override
+    public byte[] getIntentFilterVerificationBackup(int userId) throws RemoteException {
+        return new byte[0];
+    }
+
+    @Override
+    public void restoreIntentFilterVerification(byte[] backup, int userId) throws RemoteException {
+
+    }
+
+    @Override
+    public byte[] getPermissionGrantBackup(int userId) throws RemoteException {
+        return new byte[0];
+    }
+
+    @Override
+    public void restorePermissionGrants(byte[] backup, int userId) throws RemoteException {
+
+    }
+
+    @Override
+    public ComponentName getHomeActivities(List<ResolveInfo> outHomeCandidates)
+        throws RemoteException {
+        return null;
+    }
+
+    @Override
+    public void setHomeActivity(ComponentName className, int userId) throws RemoteException {
+
+    }
+
+    @Override
+    public void setComponentEnabledSetting(ComponentName componentName, int newState, int flags,
+        int userId) throws RemoteException {
+
+    }
+
+    @Override
+    public int getComponentEnabledSetting(ComponentName componentName, int userId)
+        throws RemoteException {
+        return 0;
+    }
+
+    @Override
+    public void setApplicationEnabledSetting(String packageName, int newState, int flags,
+        int userId,
+        String callingPackage) throws RemoteException {
+
+    }
+
+    @Override
+    public void logAppProcessStartIfNeeded(String processName, int uid, String seinfo,
+        String apkFile,
+        int pid) throws RemoteException {
+
+    }
+
+    @Override
+    public void flushPackageRestrictionsAsUser(int userId) throws RemoteException {
+
+    }
+
+    @Override
+    public void setPackageStoppedState(String packageName, boolean stopped, int userId)
+        throws RemoteException {
+
+    }
+
+    @Override
+    public void freeStorageAndNotify(String volumeUuid, long freeStorageSize, int storageFlags,
+        IPackageDataObserver observer) throws RemoteException {
+
+    }
+
+    @Override
+    public void freeStorage(String volumeUuid, long freeStorageSize, int storageFlags,
+        IntentSender pi) throws RemoteException {
+
+    }
+
+    @Override
+    public void deleteApplicationCacheFiles(String packageName, IPackageDataObserver observer)
+        throws RemoteException {
+
+    }
+
+    @Override
+    public void deleteApplicationCacheFilesAsUser(String packageName, int userId,
+        IPackageDataObserver observer) throws RemoteException {
+
+    }
+
+    @Override
+    public void clearApplicationUserData(String packageName, IPackageDataObserver observer,
+        int userId) throws RemoteException {
+
+    }
+
+    @Override
+    public void clearApplicationProfileData(String packageName) throws RemoteException {
+
+    }
+
+    @Override
+    public void getPackageSizeInfo(String packageName, int userHandle,
+        IPackageStatsObserver observer)
+        throws RemoteException {
+
+    }
+
+    @Override
+    public String[] getSystemSharedLibraryNames() throws RemoteException {
+        return new String[0];
+    }
+
+    @Override
+    public ParceledListSlice getSystemAvailableFeatures() throws RemoteException {
+        return null;
+    }
+
+    @Override
+    public boolean hasSystemFeature(String name, int version) throws RemoteException {
+        return false;
+    }
+
+    @Override
+    public void enterSafeMode() throws RemoteException {
+
+    }
+
+    @Override
+    public boolean isSafeMode() throws RemoteException {
+        return false;
+    }
+
+    @Override
+    public void systemReady() throws RemoteException {
+
+    }
+
+    @Override
+    public boolean hasSystemUidErrors() throws RemoteException {
+        return false;
+    }
+
+    @Override
+    public void performFstrimIfNeeded() throws RemoteException {
+
+    }
+
+    @Override
+    public void updatePackagesIfNeeded() throws RemoteException {
+
+    }
+
+    @Override
+    public void notifyPackageUse(String packageName, int reason) throws RemoteException {
+
+    }
+
+    @Override
+    public void notifyDexLoad(String loadingPackageName, List<String> classLoadersNames,
+        List<String> classPaths, String loaderIsa) throws RemoteException {
+
+    }
+
+    @Override
+    public void registerDexModule(String packageName, String dexModulePath, boolean isSharedModule,
+        IDexModuleRegisterCallback callback) throws RemoteException {
+
+    }
+
+    @Override
+    public boolean performDexOptMode(String packageName, boolean checkProfiles,
+        String targetCompilerFilter, boolean force, boolean bootComplete, String splitName)
+        throws RemoteException {
+        return false;
+    }
+
+    @Override
+    public boolean performDexOptSecondary(String packageName, String targetCompilerFilter,
+        boolean force) throws RemoteException {
+        return false;
+    }
+
+    @Override
+    public boolean compileLayouts(String packageName) throws RemoteException {
+        return false;
+    }
+
+    @Override
+    public void dumpProfiles(String packageName) throws RemoteException {
+
+    }
+
+    @Override
+    public void forceDexOpt(String packageName) throws RemoteException {
+
+    }
+
+    @Override
+    public boolean runBackgroundDexoptJob(List<String> packageNames) throws RemoteException {
+        return false;
+    }
+
+    @Override
+    public void reconcileSecondaryDexFiles(String packageName) throws RemoteException {
+
+    }
+
+    @Override
+    public int getMoveStatus(int moveId) throws RemoteException {
+        return 0;
+    }
+
+    @Override
+    public void registerMoveCallback(IPackageMoveObserver callback) throws RemoteException {
+
+    }
+
+    @Override
+    public void unregisterMoveCallback(IPackageMoveObserver callback) throws RemoteException {
+
+    }
+
+    @Override
+    public int movePackage(String packageName, String volumeUuid) throws RemoteException {
+        return 0;
+    }
+
+    @Override
+    public int movePrimaryStorage(String volumeUuid) throws RemoteException {
+        return 0;
+    }
+
+    @Override
+    public boolean addPermissionAsync(PermissionInfo info) throws RemoteException {
+        return false;
+    }
+
+    @Override
+    public boolean setInstallLocation(int loc) throws RemoteException {
+        return false;
+    }
+
+    @Override
+    public int getInstallLocation() throws RemoteException {
+        return 0;
+    }
+
+    @Override
+    public int installExistingPackageAsUser(String packageName, int userId, int installFlags,
+        int installReason) throws RemoteException {
+        return 0;
+    }
+
+    @Override
+    public void verifyPendingInstall(int id, int verificationCode) throws RemoteException {
+
+    }
+
+    @Override
+    public void extendVerificationTimeout(int id, int verificationCodeAtTimeout,
+        long millisecondsToDelay) throws RemoteException {
+
+    }
+
+    @Override
+    public void verifyIntentFilter(int id, int verificationCode, List<String> failedDomains)
+        throws RemoteException {
+
+    }
+
+    @Override
+    public int getIntentVerificationStatus(String packageName, int userId) throws RemoteException {
+        return 0;
+    }
+
+    @Override
+    public boolean updateIntentVerificationStatus(String packageName, int status, int userId)
+        throws RemoteException {
+        return false;
+    }
+
+    @Override
+    public ParceledListSlice getIntentFilterVerifications(String packageName)
+        throws RemoteException {
+        return null;
+    }
+
+    @Override
+    public ParceledListSlice getAllIntentFilters(String packageName) throws RemoteException {
+        return null;
+    }
+
+    @Override
+    public boolean setDefaultBrowserPackageName(String packageName, int userId)
+        throws RemoteException {
+        return false;
+    }
+
+    @Override
+    public String getDefaultBrowserPackageName(int userId) throws RemoteException {
+        return null;
+    }
+
+    @Override
+    public VerifierDeviceIdentity getVerifierDeviceIdentity() throws RemoteException {
+        return null;
+    }
+
+    @Override
+    public boolean isFirstBoot() throws RemoteException {
+        return false;
+    }
+
+    @Override
+    public boolean isOnlyCoreApps() throws RemoteException {
+        return false;
+    }
+
+    @Override
+    public boolean isUpgrade() throws RemoteException {
+        return false;
+    }
+
+    @Override
+    public void setPermissionEnforced(String permission, boolean enforced) throws RemoteException {
+
+    }
+
+    @Override
+    public boolean isPermissionEnforced(String permission) throws RemoteException {
+        return false;
+    }
+
+    @Override
+    public boolean isStorageLow() throws RemoteException {
+        return false;
+    }
+
+    @Override
+    public boolean setApplicationHiddenSettingAsUser(String packageName, boolean hidden, int userId)
+        throws RemoteException {
+        return false;
+    }
+
+    @Override
+    public boolean getApplicationHiddenSettingAsUser(String packageName, int userId)
+        throws RemoteException {
+        return false;
+    }
+
+    @Override
+    public void setSystemAppHiddenUntilInstalled(String packageName, boolean hidden)
+        throws RemoteException {
+
+    }
+
+    @Override
+    public boolean setSystemAppInstallState(String packageName, boolean installed, int userId)
+        throws RemoteException {
+        return false;
+    }
+
+    @Override
+    public IPackageInstaller getPackageInstaller() throws RemoteException {
+        return null;
+    }
+
+    @Override
+    public boolean setBlockUninstallForUser(String packageName, boolean blockUninstall, int userId)
+        throws RemoteException {
+        return false;
+    }
+
+    @Override
+    public boolean getBlockUninstallForUser(String packageName, int userId) throws RemoteException {
+        return false;
+    }
+
+    @Override
+    public KeySet getKeySetByAlias(String packageName, String alias) throws RemoteException {
+        return null;
+    }
+
+    @Override
+    public KeySet getSigningKeySet(String packageName) throws RemoteException {
+        return null;
+    }
+
+    @Override
+    public boolean isPackageSignedByKeySet(String packageName, KeySet ks) throws RemoteException {
+        return false;
+    }
+
+    @Override
+    public boolean isPackageSignedByKeySetExactly(String packageName, KeySet ks)
+        throws RemoteException {
+        return false;
+    }
+
+    @Override
+    public void addOnPermissionsChangeListener(IOnPermissionsChangeListener listener)
+        throws RemoteException {
+
+    }
+
+    @Override
+    public void removeOnPermissionsChangeListener(IOnPermissionsChangeListener listener)
+        throws RemoteException {
+
+    }
+
+    @Override
+    public void grantDefaultPermissionsToEnabledCarrierApps(String[] packageNames, int userId)
+        throws RemoteException {
+
+    }
+
+    @Override
+    public void grantDefaultPermissionsToEnabledImsServices(String[] packageNames, int userId)
+        throws RemoteException {
+
+    }
+
+    @Override
+    public void grantDefaultPermissionsToEnabledTelephonyDataServices(String[] packageNames,
+        int userId) throws RemoteException {
+
+    }
+
+    @Override
+    public void revokeDefaultPermissionsFromDisabledTelephonyDataServices(String[] packageNames,
+        int userId) throws RemoteException {
+
+    }
+
+    @Override
+    public void grantDefaultPermissionsToActiveLuiApp(String packageName, int userId)
+        throws RemoteException {
+
+    }
+
+    @Override
+    public void revokeDefaultPermissionsFromLuiApps(String[] packageNames, int userId)
+        throws RemoteException {
+
+    }
+
+    @Override
+    public boolean isPermissionRevokedByPolicy(String permission, String packageName, int userId)
+        throws RemoteException {
+        return false;
+    }
+
+    @Override
+    public String getPermissionControllerPackageName() throws RemoteException {
+        return null;
+    }
+
+    @Override
+    public ParceledListSlice getInstantApps(int userId) throws RemoteException {
+        return null;
+    }
+
+    @Override
+    public byte[] getInstantAppCookie(String packageName, int userId) throws RemoteException {
+        return new byte[0];
+    }
+
+    @Override
+    public boolean setInstantAppCookie(String packageName, byte[] cookie, int userId)
+        throws RemoteException {
+        return false;
+    }
+
+    @Override
+    public Bitmap getInstantAppIcon(String packageName, int userId) throws RemoteException {
+        return null;
+    }
+
+    @Override
+    public boolean isInstantApp(String packageName, int userId) throws RemoteException {
+        return false;
+    }
+
+    @Override
+    public boolean setRequiredForSystemUser(String packageName, boolean systemUserApp)
+        throws RemoteException {
+        return false;
+    }
+
+    @Override
+    public void setUpdateAvailable(String packageName, boolean updateAvaialble)
+        throws RemoteException {
+
+    }
+
+    @Override
+    public String getServicesSystemSharedLibraryPackageName() throws RemoteException {
+        return null;
+    }
+
+    @Override
+    public String getSharedSystemSharedLibraryPackageName() throws RemoteException {
+        return null;
+    }
+
+    @Override
+    public ChangedPackages getChangedPackages(int sequenceNumber, int userId)
+        throws RemoteException {
+        return null;
+    }
+
+    @Override
+    public boolean isPackageDeviceAdminOnAnyUser(String packageName) throws RemoteException {
+        return false;
+    }
+
+    @Override
+    public int getInstallReason(String packageName, int userId) throws RemoteException {
+        return 0;
+    }
+
+    @Override
+    public ParceledListSlice getSharedLibraries(String packageName, int flags, int userId)
+        throws RemoteException {
+        return null;
+    }
+
+    @Override
+    public boolean canRequestPackageInstalls(String packageName, int userId)
+        throws RemoteException {
+        return false;
+    }
+
+    @Override
+    public void deletePreloadsFileCache() throws RemoteException {
+
+    }
+
+    @Override
+    public ComponentName getInstantAppResolverComponent() throws RemoteException {
+        return null;
+    }
+
+    @Override
+    public ComponentName getInstantAppResolverSettingsComponent() throws RemoteException {
+        return null;
+    }
+
+    @Override
+    public ComponentName getInstantAppInstallerComponent() throws RemoteException {
+        return null;
+    }
+
+    @Override
+    public String getInstantAppAndroidId(String packageName, int userId) throws RemoteException {
+        return null;
+    }
+
+    @Override
+    public IArtManager getArtManager() throws RemoteException {
+        return null;
+    }
+
+    @Override
+    public void setHarmfulAppWarning(String packageName, CharSequence warning, int userId)
+        throws RemoteException {
+
+    }
+
+    @Override
+    public CharSequence getHarmfulAppWarning(String packageName, int userId)
+        throws RemoteException {
+        return null;
+    }
+
+    @Override
+    public boolean hasSigningCertificate(String packageName, byte[] signingCertificate, int flags)
+        throws RemoteException {
+        return false;
+    }
+
+    @Override
+    public boolean hasUidSigningCertificate(int uid, byte[] signingCertificate, int flags)
+        throws RemoteException {
+        return false;
+    }
+
+    @Override
+    public String getSystemTextClassifierPackageName() throws RemoteException {
+        return null;
+    }
+
+    @Override
+    public String getWellbeingPackageName() throws RemoteException {
+        return null;
+    }
+
+    @Override
+    public boolean isPackageStateProtected(String packageName, int userId) throws RemoteException {
+        return false;
+    }
+
+    @Override
+    public void sendDeviceCustomizationReadyBroadcast() throws RemoteException {
+
+    }
+
+    @Override
+    public List<ModuleInfo> getInstalledModules(int flags) throws RemoteException {
+        return null;
+    }
+
+    @Override
+    public ModuleInfo getModuleInfo(String packageName, int flags) throws RemoteException {
+        return null;
+    }
+
+    @Override
+    public IBinder asBinder() {
+        return null;
+    }
+}
diff --git a/services/tests/servicestests/src/com/android/server/backup/testutils/PackageManagerStub.java b/services/tests/servicestests/src/com/android/server/backup/testutils/PackageManagerStub.java
index 525135c..7172752 100644
--- a/services/tests/servicestests/src/com/android/server/backup/testutils/PackageManagerStub.java
+++ b/services/tests/servicestests/src/com/android/server/backup/testutils/PackageManagerStub.java
@@ -48,11 +48,7 @@
     @Override
     public PackageInfo getPackageInfo(String packageName, int flags)
             throws NameNotFoundException {
-        if (sPackageInfo == null) {
-            throw new NameNotFoundException();
-        }
-
-        return sPackageInfo;
+        return getPackageInfoAsUser(packageName, flags, UserHandle.USER_SYSTEM);
     }
 
     @Override
@@ -64,7 +60,11 @@
     @Override
     public PackageInfo getPackageInfoAsUser(String packageName, int flags, int userId)
             throws NameNotFoundException {
-        return null;
+        if (sPackageInfo == null) {
+            throw new NameNotFoundException();
+        }
+
+        return sPackageInfo;
     }
 
     @Override
diff --git a/services/tests/servicestests/src/com/android/server/backup/utils/AppBackupUtilsTest.java b/services/tests/servicestests/src/com/android/server/backup/utils/AppBackupUtilsTest.java
index 479a19b..a92b576 100644
--- a/services/tests/servicestests/src/com/android/server/backup/utils/AppBackupUtilsTest.java
+++ b/services/tests/servicestests/src/com/android/server/backup/utils/AppBackupUtilsTest.java
@@ -29,13 +29,14 @@
 import android.content.pm.Signature;
 import android.content.pm.SigningInfo;
 import android.os.Process;
+import android.os.UserHandle;
 import android.platform.test.annotations.Presubmit;
 
 import androidx.test.filters.SmallTest;
 import androidx.test.runner.AndroidJUnit4;
 
 import com.android.server.backup.UserBackupManagerService;
-import com.android.server.backup.testutils.PackageManagerStub;
+import com.android.server.backup.testutils.IPackageManagerStub;
 
 import org.junit.Before;
 import org.junit.Test;
@@ -53,13 +54,17 @@
     private static final Signature SIGNATURE_3 = generateSignature((byte) 3);
     private static final Signature SIGNATURE_4 = generateSignature((byte) 4);
 
-    private PackageManagerStub mPackageManagerStub;
+    private IPackageManagerStub mPackageManagerStub;
     private PackageManagerInternal mMockPackageManagerInternal;
 
+    private int mUserId;
+
     @Before
     public void setUp() throws Exception {
-        mPackageManagerStub = new PackageManagerStub();
+        mPackageManagerStub = new IPackageManagerStub();
         mMockPackageManagerInternal = mock(PackageManagerInternal.class);
+
+        mUserId = UserHandle.USER_SYSTEM;
     }
 
     @Test
@@ -71,7 +76,7 @@
         applicationInfo.packageName = TEST_PACKAGE_NAME;
 
         boolean isEligible = AppBackupUtils.appIsEligibleForBackup(applicationInfo,
-                mPackageManagerStub);
+                mPackageManagerStub, mUserId);
 
         assertThat(isEligible).isFalse();
     }
@@ -86,7 +91,7 @@
         applicationInfo.packageName = TEST_PACKAGE_NAME;
 
         boolean isEligible = AppBackupUtils.appIsEligibleForBackup(applicationInfo,
-                mPackageManagerStub);
+                mPackageManagerStub, mUserId);
 
         assertThat(isEligible).isFalse();
     }
@@ -100,7 +105,7 @@
         applicationInfo.packageName = UserBackupManagerService.SHARED_BACKUP_AGENT_PACKAGE;
 
         boolean isEligible = AppBackupUtils.appIsEligibleForBackup(applicationInfo,
-                mPackageManagerStub);
+                mPackageManagerStub, mUserId);
 
         assertThat(isEligible).isFalse();
     }
@@ -114,11 +119,11 @@
         applicationInfo.backupAgentName = CUSTOM_BACKUP_AGENT_NAME;
         applicationInfo.packageName = TEST_PACKAGE_NAME;
 
-        PackageManagerStub.sApplicationEnabledSetting =
+        IPackageManagerStub.sApplicationEnabledSetting =
                 PackageManager.COMPONENT_ENABLED_STATE_ENABLED;
 
         boolean isEligible = AppBackupUtils.appIsEligibleForBackup(applicationInfo,
-                mPackageManagerStub);
+                mPackageManagerStub, mUserId);
 
         assertThat(isEligible).isTrue();
     }
@@ -132,11 +137,11 @@
         applicationInfo.backupAgentName = null;
         applicationInfo.packageName = TEST_PACKAGE_NAME;
 
-        PackageManagerStub.sApplicationEnabledSetting =
+        IPackageManagerStub.sApplicationEnabledSetting =
                 PackageManager.COMPONENT_ENABLED_STATE_ENABLED;
 
         boolean isEligible = AppBackupUtils.appIsEligibleForBackup(applicationInfo,
-                mPackageManagerStub);
+                mPackageManagerStub, mUserId);
 
         assertThat(isEligible).isTrue();
     }
@@ -150,11 +155,11 @@
         applicationInfo.backupAgentName = CUSTOM_BACKUP_AGENT_NAME;
         applicationInfo.packageName = TEST_PACKAGE_NAME;
 
-        PackageManagerStub.sApplicationEnabledSetting =
+        IPackageManagerStub.sApplicationEnabledSetting =
                 PackageManager.COMPONENT_ENABLED_STATE_ENABLED;
 
         boolean isEligible = AppBackupUtils.appIsEligibleForBackup(applicationInfo,
-                mPackageManagerStub);
+                mPackageManagerStub, mUserId);
 
         assertThat(isEligible).isTrue();
     }
@@ -168,11 +173,11 @@
         applicationInfo.backupAgentName = CUSTOM_BACKUP_AGENT_NAME;
         applicationInfo.packageName = TEST_PACKAGE_NAME;
 
-        PackageManagerStub.sApplicationEnabledSetting =
+        IPackageManagerStub.sApplicationEnabledSetting =
                 PackageManager.COMPONENT_ENABLED_STATE_DISABLED;
 
         boolean isEligible = AppBackupUtils.appIsEligibleForBackup(applicationInfo,
-                mPackageManagerStub);
+                mPackageManagerStub, mUserId);
 
         assertThat(isEligible).isFalse();
     }
@@ -186,11 +191,11 @@
         applicationInfo.backupAgentName = null;
         applicationInfo.packageName = TEST_PACKAGE_NAME;
 
-        PackageManagerStub.sApplicationEnabledSetting =
+        IPackageManagerStub.sApplicationEnabledSetting =
                 PackageManager.COMPONENT_ENABLED_STATE_DISABLED;
 
         boolean isEligible = AppBackupUtils.appIsEligibleForBackup(applicationInfo,
-                mPackageManagerStub);
+                mPackageManagerStub, mUserId);
 
         assertThat(isEligible).isFalse();
     }
@@ -204,11 +209,11 @@
         applicationInfo.backupAgentName = CUSTOM_BACKUP_AGENT_NAME;
         applicationInfo.packageName = TEST_PACKAGE_NAME;
 
-        PackageManagerStub.sApplicationEnabledSetting =
+        IPackageManagerStub.sApplicationEnabledSetting =
                 PackageManager.COMPONENT_ENABLED_STATE_DISABLED;
 
         boolean isEligible = AppBackupUtils.appIsEligibleForBackup(applicationInfo,
-                mPackageManagerStub);
+                mPackageManagerStub, mUserId);
 
         assertThat(isEligible).isFalse();
     }
@@ -222,10 +227,11 @@
         applicationInfo.packageName = TEST_PACKAGE_NAME;
         applicationInfo.enabled = true;
 
-        PackageManagerStub.sApplicationEnabledSetting =
+        IPackageManagerStub.sApplicationEnabledSetting =
                 PackageManager.COMPONENT_ENABLED_STATE_DEFAULT;
 
-        boolean isDisabled = AppBackupUtils.appIsDisabled(applicationInfo, mPackageManagerStub);
+        boolean isDisabled = AppBackupUtils.appIsDisabled(applicationInfo, mPackageManagerStub,
+            mUserId);
 
         assertThat(isDisabled).isFalse();
     }
@@ -239,10 +245,11 @@
         applicationInfo.packageName = TEST_PACKAGE_NAME;
         applicationInfo.enabled = false;
 
-        PackageManagerStub.sApplicationEnabledSetting =
+        IPackageManagerStub.sApplicationEnabledSetting =
                 PackageManager.COMPONENT_ENABLED_STATE_DEFAULT;
 
-        boolean isDisabled = AppBackupUtils.appIsDisabled(applicationInfo, mPackageManagerStub);
+        boolean isDisabled = AppBackupUtils.appIsDisabled(applicationInfo, mPackageManagerStub,
+            mUserId);
 
         assertThat(isDisabled).isTrue();
     }
@@ -255,10 +262,11 @@
         applicationInfo.backupAgentName = CUSTOM_BACKUP_AGENT_NAME;
         applicationInfo.packageName = TEST_PACKAGE_NAME;
 
-        PackageManagerStub.sApplicationEnabledSetting =
+        IPackageManagerStub.sApplicationEnabledSetting =
                 PackageManager.COMPONENT_ENABLED_STATE_ENABLED;
 
-        boolean isDisabled = AppBackupUtils.appIsDisabled(applicationInfo, mPackageManagerStub);
+        boolean isDisabled = AppBackupUtils.appIsDisabled(applicationInfo, mPackageManagerStub,
+            mUserId);
 
         assertThat(isDisabled).isFalse();
     }
@@ -271,10 +279,11 @@
         applicationInfo.backupAgentName = CUSTOM_BACKUP_AGENT_NAME;
         applicationInfo.packageName = TEST_PACKAGE_NAME;
 
-        PackageManagerStub.sApplicationEnabledSetting =
+        IPackageManagerStub.sApplicationEnabledSetting =
                 PackageManager.COMPONENT_ENABLED_STATE_DISABLED;
 
-        boolean isDisabled = AppBackupUtils.appIsDisabled(applicationInfo, mPackageManagerStub);
+        boolean isDisabled = AppBackupUtils.appIsDisabled(applicationInfo, mPackageManagerStub,
+            mUserId);
 
         assertThat(isDisabled).isTrue();
     }
@@ -287,10 +296,11 @@
         applicationInfo.backupAgentName = CUSTOM_BACKUP_AGENT_NAME;
         applicationInfo.packageName = TEST_PACKAGE_NAME;
 
-        PackageManagerStub.sApplicationEnabledSetting =
+        IPackageManagerStub.sApplicationEnabledSetting =
                 PackageManager.COMPONENT_ENABLED_STATE_DISABLED_USER;
 
-        boolean isDisabled = AppBackupUtils.appIsDisabled(applicationInfo, mPackageManagerStub);
+        boolean isDisabled = AppBackupUtils.appIsDisabled(applicationInfo, mPackageManagerStub,
+            mUserId);
 
         assertThat(isDisabled).isTrue();
     }
@@ -303,10 +313,11 @@
         applicationInfo.backupAgentName = CUSTOM_BACKUP_AGENT_NAME;
         applicationInfo.packageName = TEST_PACKAGE_NAME;
 
-        PackageManagerStub.sApplicationEnabledSetting =
+        IPackageManagerStub.sApplicationEnabledSetting =
                 PackageManager.COMPONENT_ENABLED_STATE_DISABLED_UNTIL_USED;
 
-        boolean isDisabled = AppBackupUtils.appIsDisabled(applicationInfo, mPackageManagerStub);
+        boolean isDisabled = AppBackupUtils.appIsDisabled(applicationInfo, mPackageManagerStub,
+            mUserId);
 
         assertThat(isDisabled).isTrue();
     }
diff --git a/services/tests/servicestests/src/com/android/server/backup/utils/TarBackupReaderTest.java b/services/tests/servicestests/src/com/android/server/backup/utils/TarBackupReaderTest.java
index d43b677..5fcce67 100644
--- a/services/tests/servicestests/src/com/android/server/backup/utils/TarBackupReaderTest.java
+++ b/services/tests/servicestests/src/com/android/server/backup/utils/TarBackupReaderTest.java
@@ -44,6 +44,7 @@
 import android.content.pm.SigningInfo;
 import android.os.Bundle;
 import android.os.Process;
+import android.os.UserHandle;
 import android.platform.test.annotations.Presubmit;
 
 import androidx.test.InstrumentationRegistry;
@@ -88,12 +89,14 @@
 
     private final PackageManagerStub mPackageManagerStub = new PackageManagerStub();
     private Context mContext;
+    private int mUserId;
 
     @Before
     public void setUp() throws Exception {
         MockitoAnnotations.initMocks(this);
 
         mContext = InstrumentationRegistry.getContext();
+        mUserId = UserHandle.USER_SYSTEM;
     }
 
     @Test
@@ -146,7 +149,7 @@
                 fileMetadata);
         RestorePolicy restorePolicy = tarBackupReader.chooseRestorePolicy(
                 mPackageManagerStub, false /* allowApks */, fileMetadata, signatures,
-                mMockPackageManagerInternal);
+                mMockPackageManagerInternal, mUserId);
 
         assertThat(restorePolicy).isEqualTo(RestorePolicy.IGNORE);
         assertThat(fileMetadata.packageName).isEqualTo(TEST_PACKAGE_NAME);
@@ -160,7 +163,7 @@
                 fileMetadata);
         restorePolicy = tarBackupReader.chooseRestorePolicy(
                 mPackageManagerStub, false /* allowApks */, fileMetadata, signatures,
-                mMockPackageManagerInternal);
+                mMockPackageManagerInternal, mUserId);
 
         assertThat(restorePolicy).isEqualTo(RestorePolicy.IGNORE);
         assertThat(fileMetadata.packageName).isEqualTo(TEST_PACKAGE_NAME);
@@ -223,7 +226,7 @@
 
         RestorePolicy policy = tarBackupReader.chooseRestorePolicy(mPackageManagerStub,
                 true /* allowApks */, new FileMetadata(), null /* signatures */,
-                mMockPackageManagerInternal);
+                mMockPackageManagerInternal, mUserId);
 
         assertThat(policy).isEqualTo(RestorePolicy.IGNORE);
         verifyZeroInteractions(mBackupManagerMonitorMock);
@@ -244,7 +247,7 @@
 
         RestorePolicy policy = tarBackupReader.chooseRestorePolicy(mPackageManagerStub,
                 true /* allowApks */, info, new Signature[0] /* signatures */,
-                mMockPackageManagerInternal);
+                mMockPackageManagerInternal, mUserId);
 
         assertThat(policy).isEqualTo(RestorePolicy.ACCEPT_IF_APK);
         ArgumentCaptor<Bundle> bundleCaptor = ArgumentCaptor.forClass(Bundle.class);
@@ -269,7 +272,7 @@
 
         RestorePolicy policy = tarBackupReader.chooseRestorePolicy(mPackageManagerStub,
                 true /* allowApks */, info, new Signature[0] /* signatures */,
-                mMockPackageManagerInternal);
+                mMockPackageManagerInternal, mUserId);
 
         assertThat(policy).isEqualTo(RestorePolicy.ACCEPT_IF_APK);
         ArgumentCaptor<Bundle> bundleCaptor = ArgumentCaptor.forClass(Bundle.class);
@@ -295,7 +298,7 @@
 
         RestorePolicy policy = tarBackupReader.chooseRestorePolicy(mPackageManagerStub,
                 false /* allowApks */, new FileMetadata(), new Signature[0] /* signatures */,
-                mMockPackageManagerInternal);
+                mMockPackageManagerInternal, mUserId);
 
         assertThat(policy).isEqualTo(RestorePolicy.IGNORE);
         ArgumentCaptor<Bundle> bundleCaptor = ArgumentCaptor.forClass(Bundle.class);
@@ -320,7 +323,7 @@
 
         RestorePolicy policy = tarBackupReader.chooseRestorePolicy(mPackageManagerStub,
                 false /* allowApks */, new FileMetadata(), new Signature[0] /* signatures */,
-                mMockPackageManagerInternal);
+                mMockPackageManagerInternal, mUserId);
 
         assertThat(policy).isEqualTo(RestorePolicy.IGNORE);
         ArgumentCaptor<Bundle> bundleCaptor = ArgumentCaptor.forClass(Bundle.class);
@@ -347,7 +350,7 @@
 
         RestorePolicy policy = tarBackupReader.chooseRestorePolicy(mPackageManagerStub,
                 false /* allowApks */, new FileMetadata(), new Signature[0] /* signatures */,
-                mMockPackageManagerInternal);
+                mMockPackageManagerInternal, mUserId);
 
         assertThat(policy).isEqualTo(RestorePolicy.IGNORE);
         ArgumentCaptor<Bundle> bundleCaptor = ArgumentCaptor.forClass(Bundle.class);
@@ -381,7 +384,8 @@
         PackageManagerStub.sPackageInfo = packageInfo;
 
         RestorePolicy policy = tarBackupReader.chooseRestorePolicy(mPackageManagerStub,
-                false /* allowApks */, new FileMetadata(), signatures, mMockPackageManagerInternal);
+                false /* allowApks */, new FileMetadata(), signatures,
+                mMockPackageManagerInternal, mUserId);
 
         assertThat(policy).isEqualTo(RestorePolicy.IGNORE);
         ArgumentCaptor<Bundle> bundleCaptor = ArgumentCaptor.forClass(Bundle.class);
@@ -419,7 +423,8 @@
         doReturn(true).when(mMockPackageManagerInternal).isDataRestoreSafe(FAKE_SIGNATURE_1,
                 packageInfo.packageName);
         RestorePolicy policy = tarBackupReader.chooseRestorePolicy(mPackageManagerStub,
-                false /* allowApks */, new FileMetadata(), signatures, mMockPackageManagerInternal);
+                false /* allowApks */, new FileMetadata(), signatures,
+                mMockPackageManagerInternal, mUserId);
 
         assertThat(policy).isEqualTo(RestorePolicy.ACCEPT);
         ArgumentCaptor<Bundle> bundleCaptor = ArgumentCaptor.forClass(Bundle.class);
@@ -456,7 +461,8 @@
         doReturn(true).when(mMockPackageManagerInternal).isDataRestoreSafe(FAKE_SIGNATURE_1,
                 packageInfo.packageName);
         RestorePolicy policy = tarBackupReader.chooseRestorePolicy(mPackageManagerStub,
-                false /* allowApks */, new FileMetadata(), signatures, mMockPackageManagerInternal);
+                false /* allowApks */, new FileMetadata(), signatures,
+                mMockPackageManagerInternal, mUserId);
 
         assertThat(policy).isEqualTo(RestorePolicy.ACCEPT);
         ArgumentCaptor<Bundle> bundleCaptor = ArgumentCaptor.forClass(Bundle.class);
@@ -497,7 +503,8 @@
         doReturn(true).when(mMockPackageManagerInternal).isDataRestoreSafe(FAKE_SIGNATURE_1,
                 packageInfo.packageName);
         RestorePolicy policy = tarBackupReader.chooseRestorePolicy(mPackageManagerStub,
-                false /* allowApks */, info, signatures, mMockPackageManagerInternal);
+                false /* allowApks */, info, signatures, mMockPackageManagerInternal,
+                mUserId);
 
         assertThat(policy).isEqualTo(RestorePolicy.ACCEPT);
         ArgumentCaptor<Bundle> bundleCaptor = ArgumentCaptor.forClass(Bundle.class);
@@ -540,7 +547,8 @@
         doReturn(true).when(mMockPackageManagerInternal).isDataRestoreSafe(FAKE_SIGNATURE_1,
                 packageInfo.packageName);
         RestorePolicy policy = tarBackupReader.chooseRestorePolicy(mPackageManagerStub,
-                true /* allowApks */, info, signatures, mMockPackageManagerInternal);
+                true /* allowApks */, info, signatures, mMockPackageManagerInternal,
+                mUserId);
 
         assertThat(policy).isEqualTo(RestorePolicy.ACCEPT_IF_APK);
         verifyNoMoreInteractions(mBackupManagerMonitorMock);
@@ -579,7 +587,8 @@
         doReturn(true).when(mMockPackageManagerInternal).isDataRestoreSafe(FAKE_SIGNATURE_1,
                 packageInfo.packageName);
         RestorePolicy policy = tarBackupReader.chooseRestorePolicy(mPackageManagerStub,
-                false /* allowApks */, info, signatures, mMockPackageManagerInternal);
+                false /* allowApks */, info, signatures, mMockPackageManagerInternal,
+                mUserId);
 
         assertThat(policy).isEqualTo(RestorePolicy.IGNORE);
         ArgumentCaptor<Bundle> bundleCaptor = ArgumentCaptor.forClass(Bundle.class);
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 38e8ac2..535198b 100644
--- a/services/tests/servicestests/src/com/android/server/devicepolicy/DevicePolicyManagerTest.java
+++ b/services/tests/servicestests/src/com/android/server/devicepolicy/DevicePolicyManagerTest.java
@@ -206,6 +206,8 @@
         mAdmin1Context.binder.callingUid = DpmMockContext.CALLER_UID;
 
         setUpUserManager();
+
+        when(getServices().lockPatternUtils.hasSecureLockScreen()).thenReturn(true);
     }
 
     private TransferOwnershipMetadataManager getMockTransferMetadataManager() {
@@ -836,6 +838,7 @@
                 MockUtils.checkIntent(intent),
                 MockUtils.checkUserHandle(MANAGED_PROFILE_USER_ID));
     }
+
     /**
      * Test for: {@link DevicePolicyManager#setDeviceOwner} DO on system user installs successfully.
      */
@@ -2618,6 +2621,7 @@
         when(getServices().lockPatternUtils
                 .isSeparateProfileChallengeEnabled(eq(PROFILE_USER))).thenReturn(false);
         dpmi.reportSeparateProfileChallengeChanged(PROFILE_USER);
+        when(getServices().lockPatternUtils.hasSecureLockScreen()).thenReturn(true);
 
         verifyScreenTimeoutCall(Long.MAX_VALUE, PROFILE_USER);
         verifyScreenTimeoutCall(10L , UserHandle.USER_SYSTEM);
@@ -4233,6 +4237,41 @@
         assertTrue(dpm.isActivePasswordSufficient());
     }
 
+    public void testIsActivePasswordSufficient_noLockScreen() throws Exception {
+        // If there is no lock screen, the password is considered empty no matter what, because
+        // it provides no security.
+        when(getServices().lockPatternUtils.hasSecureLockScreen()).thenReturn(false);
+
+        mContext.binder.callingUid = DpmMockContext.CALLER_SYSTEM_USER_UID;
+        mContext.packageName = admin1.getPackageName();
+        setupDeviceOwner();
+
+        // If no password requirements are set, isActivePasswordSufficient should succeed.
+        assertTrue(dpm.isActivePasswordSufficient());
+
+        // Now set some password quality requirements.
+        dpm.setPasswordQuality(admin1, DevicePolicyManager.PASSWORD_QUALITY_SOMETHING);
+
+        reset(mContext.spiedContext);
+        final int userHandle = UserHandle.getUserId(mContext.binder.callingUid);
+        PasswordMetrics passwordMetricsNoSymbols = new PasswordMetrics(
+                DevicePolicyManager.PASSWORD_QUALITY_COMPLEX, 9,
+                8, 2,
+                6, 1,
+                0, 1);
+        // This should be ignored, as there is no lock screen.
+        dpm.setActivePasswordState(passwordMetricsNoSymbols, userHandle);
+        dpm.reportPasswordChanged(userHandle);
+
+        // No broadcast should be sent.
+        verify(mContext.spiedContext, times(0)).sendBroadcastAsUser(
+                MockUtils.checkIntentAction(DeviceAdminReceiver.ACTION_PASSWORD_CHANGED),
+                MockUtils.checkUserHandle(userHandle));
+
+        // The active (nonexistent) password doesn't comply with the requirements.
+        assertFalse(dpm.isActivePasswordSufficient());
+    }
+
     private void setActivePasswordState(PasswordMetrics passwordMetrics)
             throws Exception {
         final int userHandle = UserHandle.getUserId(mContext.binder.callingUid);
@@ -5200,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/hdmi/HdmiCecLocalDeviceAudioSystemTest.java b/services/tests/servicestests/src/com/android/server/hdmi/HdmiCecLocalDeviceAudioSystemTest.java
index 3a6cdc2..a89198a 100644
--- a/services/tests/servicestests/src/com/android/server/hdmi/HdmiCecLocalDeviceAudioSystemTest.java
+++ b/services/tests/servicestests/src/com/android/server/hdmi/HdmiCecLocalDeviceAudioSystemTest.java
@@ -147,12 +147,12 @@
                 }
 
                 @Override
-                void writeStringSetting(String key, String value) {
+                void writeStringSystemProperty(String key, String value) {
                     // do nothing
                 }
 
                 @Override
-                boolean readBooleanSetting(String key, boolean defVal) {
+                boolean readBooleanSystemProperty(String key, boolean defVal) {
                     switch (key) {
                         case Constants.PROPERTY_SYSTEM_AUDIO_MODE_MUTING_ENABLE:
                             return mMutingEnabled;
diff --git a/services/tests/servicestests/src/com/android/server/inputmethod/InputMethodManagerServiceTests.java b/services/tests/servicestests/src/com/android/server/inputmethod/InputMethodManagerServiceTests.java
index 0328621..8afc3d3 100644
--- a/services/tests/servicestests/src/com/android/server/inputmethod/InputMethodManagerServiceTests.java
+++ b/services/tests/servicestests/src/com/android/server/inputmethod/InputMethodManagerServiceTests.java
@@ -22,7 +22,6 @@
 import static org.junit.Assert.assertEquals;
 import static org.junit.Assert.fail;
 
-import androidx.test.InstrumentationRegistry;
 import androidx.test.filters.SmallTest;
 import androidx.test.runner.AndroidJUnit4;
 
@@ -58,8 +57,7 @@
         // Make sure that there is a short-circuit for DEFAULT_DISPLAY.
         assertEquals(DEFAULT_DISPLAY,
                 InputMethodManagerService.computeImeDisplayIdForTarget(
-                        DEFAULT_DISPLAY, false /* isVrImeStarted */,
-                        sMustNotBeCalledChecker));
+                        DEFAULT_DISPLAY, sMustNotBeCalledChecker));
     }
 
     @Test
@@ -67,17 +65,7 @@
         // Make sure that there is a short-circuit for INVALID_DISPLAY.
         assertEquals(DEFAULT_DISPLAY,
                 InputMethodManagerService.computeImeDisplayIdForTarget(
-                        INVALID_DISPLAY, false /* isVrImeStarted */,
-                        sMustNotBeCalledChecker));
-    }
-
-    @Test
-    public void testComputeImeDisplayId_VrIme() {
-        // Make sure that there is a short-circuit for VR IME.
-        assertEquals(DEFAULT_DISPLAY,
-                InputMethodManagerService.computeImeDisplayIdForTarget(
-                        SYSTEM_DECORATION_SUPPORT_DISPLAY_ID, true /* isVrImeStarted */,
-                        sMustNotBeCalledChecker));
+                        INVALID_DISPLAY, sMustNotBeCalledChecker));
     }
 
     @Test
@@ -86,8 +74,7 @@
         // Make sure IME displayId is DEFAULT_DISPLAY.
         assertEquals(DEFAULT_DISPLAY,
                 InputMethodManagerService.computeImeDisplayIdForTarget(
-                        NO_SYSTEM_DECORATION_SUPPORT_DISPLAY_ID, false /* isVrImeStarted */,
-                        sChecker));
+                        NO_SYSTEM_DECORATION_SUPPORT_DISPLAY_ID, sChecker));
     }
 
     @Test
@@ -96,7 +83,6 @@
         // Make sure IME displayId is the same display.
         assertEquals(SYSTEM_DECORATION_SUPPORT_DISPLAY_ID,
                 InputMethodManagerService.computeImeDisplayIdForTarget(
-                        SYSTEM_DECORATION_SUPPORT_DISPLAY_ID, false /* isVrImeStarted */,
-                        sChecker));
+                        SYSTEM_DECORATION_SUPPORT_DISPLAY_ID, sChecker));
     }
 }
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/locksettings/BaseLockSettingsServiceTests.java b/services/tests/servicestests/src/com/android/server/locksettings/BaseLockSettingsServiceTests.java
index 2dc3510..cf89cb8 100644
--- a/services/tests/servicestests/src/com/android/server/locksettings/BaseLockSettingsServiceTests.java
+++ b/services/tests/servicestests/src/com/android/server/locksettings/BaseLockSettingsServiceTests.java
@@ -37,8 +37,8 @@
 import android.os.IProgressListener;
 import android.os.RemoteException;
 import android.os.UserManager;
-import android.os.storage.StorageManager;
 import android.os.storage.IStorageManager;
+import android.os.storage.StorageManager;
 import android.security.KeyStore;
 import android.test.AndroidTestCase;
 
@@ -46,6 +46,7 @@
 import com.android.internal.widget.LockPatternUtils;
 import com.android.internal.widget.LockSettingsInternal;
 import com.android.server.LocalServices;
+import com.android.server.wm.WindowManagerInternal;
 
 import org.mockito.invocation.InvocationOnMock;
 import org.mockito.stubbing.Answer;
@@ -85,6 +86,8 @@
     KeyStore mKeyStore;
     MockSyntheticPasswordManager mSpManager;
     IAuthSecret mAuthSecretService;
+    WindowManagerInternal mMockWindowManager;
+    protected boolean mHasSecureLockScreen;
 
     @Override
     protected void setUp() throws Exception {
@@ -97,10 +100,13 @@
         mActivityManager = mock(IActivityManager.class);
         mDevicePolicyManager = mock(DevicePolicyManager.class);
         mDevicePolicyManagerInternal = mock(DevicePolicyManagerInternal.class);
+        mMockWindowManager = mock(WindowManagerInternal.class);
 
         LocalServices.removeServiceForTest(LockSettingsInternal.class);
         LocalServices.removeServiceForTest(DevicePolicyManagerInternal.class);
+        LocalServices.removeServiceForTest(WindowManagerInternal.class);
         LocalServices.addService(DevicePolicyManagerInternal.class, mDevicePolicyManagerInternal);
+        LocalServices.addService(WindowManagerInternal.class, mMockWindowManager);
 
         mContext = new MockLockSettingsContext(getContext(), mUserManager, mNotificationManager,
                 mDevicePolicyManager, mock(StorageManager.class), mock(TrustManager.class),
@@ -114,11 +120,17 @@
             storageDir.mkdirs();
         }
 
+        mHasSecureLockScreen = true;
         mLockPatternUtils = new LockPatternUtils(mContext) {
             @Override
             public ILockSettings getLockSettings() {
                 return mService;
             }
+
+            @Override
+            public boolean hasSecureLockScreen() {
+                return mHasSecureLockScreen;
+            }
         };
         mSpManager = new MockSyntheticPasswordManager(mContext, mStorage, mGateKeeperService,
                 mUserManager);
diff --git a/services/tests/servicestests/src/com/android/server/locksettings/LockSettingsServiceTests.java b/services/tests/servicestests/src/com/android/server/locksettings/LockSettingsServiceTests.java
index e12f6d3b..5124803 100644
--- a/services/tests/servicestests/src/com/android/server/locksettings/LockSettingsServiceTests.java
+++ b/services/tests/servicestests/src/com/android/server/locksettings/LockSettingsServiceTests.java
@@ -26,13 +26,12 @@
 import static com.android.internal.widget.LockPatternUtils.CREDENTIAL_TYPE_PATTERN;
 
 import android.os.RemoteException;
-import android.os.UserHandle;
 import android.service.gatekeeper.GateKeeperResponse;
 
 import com.android.internal.widget.LockPatternUtils;
 import com.android.internal.widget.VerifyCredentialResponse;
-import com.android.server.locksettings.LockSettingsStorage.CredentialHash;
 import com.android.server.locksettings.FakeGateKeeperService.VerifyHandle;
+import com.android.server.locksettings.LockSettingsStorage.CredentialHash;
 
 /**
  * runtest frameworks-services -c com.android.server.locksettings.LockSettingsServiceTests
@@ -54,11 +53,21 @@
                 PASSWORD_QUALITY_ALPHABETIC);
     }
 
+    public void testCreatePasswordFailsWithoutLockScreen() throws RemoteException {
+        testCreateCredentialFailsWithoutLockScreen(PRIMARY_USER_ID, "password",
+                CREDENTIAL_TYPE_PASSWORD, PASSWORD_QUALITY_ALPHABETIC);
+    }
+
     public void testCreatePatternPrimaryUser() throws RemoteException {
         testCreateCredential(PRIMARY_USER_ID, "123456789", CREDENTIAL_TYPE_PATTERN,
                 PASSWORD_QUALITY_SOMETHING);
     }
 
+    public void testCreatePatternFailsWithoutLockScreen() throws RemoteException {
+        testCreateCredentialFailsWithoutLockScreen(PRIMARY_USER_ID, "123456789",
+                CREDENTIAL_TYPE_PATTERN, PASSWORD_QUALITY_SOMETHING);
+    }
+
     public void testChangePasswordPrimaryUser() throws RemoteException {
         testChangeCredentials(PRIMARY_USER_ID, "78963214", CREDENTIAL_TYPE_PATTERN,
                 "asdfghjk", CREDENTIAL_TYPE_PASSWORD, PASSWORD_QUALITY_ALPHABETIC);
@@ -198,6 +207,21 @@
         assertVerifyCredentials(userId, credential, type, -1);
     }
 
+    private void testCreateCredentialFailsWithoutLockScreen(
+            int userId, String credential, int type, int quality) throws RemoteException {
+        mHasSecureLockScreen = false;
+
+        try {
+            mService.setLockCredential(credential, type, null, quality, userId);
+            fail("An exception should have been thrown.");
+        } catch (UnsupportedOperationException e) {
+            // Success - the exception was expected.
+        }
+
+        assertFalse(mService.havePassword(userId));
+        assertFalse(mService.havePattern(userId));
+    }
+
     private void testChangeCredentials(int userId, String newCredential, int newType,
             String oldCredential, int oldType, int quality) throws RemoteException {
         final long sid = 1234;
diff --git a/services/tests/servicestests/src/com/android/server/locksettings/LockSettingsShellCommandTest.java b/services/tests/servicestests/src/com/android/server/locksettings/LockSettingsShellCommandTest.java
index a28a5a1..929c3b5 100644
--- a/services/tests/servicestests/src/com/android/server/locksettings/LockSettingsShellCommandTest.java
+++ b/services/tests/servicestests/src/com/android/server/locksettings/LockSettingsShellCommandTest.java
@@ -27,6 +27,7 @@
 import static org.mockito.Mockito.any;
 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.app.ActivityManager;
@@ -77,6 +78,7 @@
         final Context context = InstrumentationRegistry.getTargetContext();
         mUserId = ActivityManager.getCurrentUser();
         mCommand = new LockSettingsShellCommand(mLockPatternUtils);
+        when(mLockPatternUtils.hasSecureLockScreen()).thenReturn(true);
     }
 
     @Test
@@ -103,6 +105,16 @@
     }
 
     @Test
+    public void testChangePin_noLockScreen() throws Exception {
+        when(mLockPatternUtils.hasSecureLockScreen()).thenReturn(false);
+        assertEquals(-1, mCommand.exec(new Binder(), in, out, err,
+                new String[] { "set-pin", "--old", "1234", "4321" },
+                mShellCallback, mResultReceiver));
+        verify(mLockPatternUtils).hasSecureLockScreen();
+        verifyNoMoreInteractions(mLockPatternUtils);
+    }
+
+    @Test
     public void testChangePassword() throws Exception {
         when(mLockPatternUtils.isLockPatternEnabled(mUserId)).thenReturn(false);
         when(mLockPatternUtils.isLockPasswordEnabled(mUserId)).thenReturn(true);
@@ -115,6 +127,16 @@
     }
 
     @Test
+    public void testChangePassword_noLockScreen() throws Exception {
+        when(mLockPatternUtils.hasSecureLockScreen()).thenReturn(false);
+        assertEquals(-1,  mCommand.exec(new Binder(), in, out, err,
+                new String[] { "set-password", "--old", "1234", "4321" },
+                mShellCallback, mResultReceiver));
+        verify(mLockPatternUtils).hasSecureLockScreen();
+        verifyNoMoreInteractions(mLockPatternUtils);
+    }
+
+    @Test
     public void testChangePattern() throws Exception {
         when(mLockPatternUtils.isLockPatternEnabled(mUserId)).thenReturn(true);
         when(mLockPatternUtils.isLockPasswordEnabled(mUserId)).thenReturn(false);
@@ -126,6 +148,16 @@
     }
 
     @Test
+    public void testChangePattern_noLockScreen() throws Exception {
+        when(mLockPatternUtils.hasSecureLockScreen()).thenReturn(false);
+        assertEquals(-1, mCommand.exec(new Binder(), in, out, err,
+                new String[] { "set-pattern", "--old", "1234", "4321" },
+                mShellCallback, mResultReceiver));
+        verify(mLockPatternUtils).hasSecureLockScreen();
+        verifyNoMoreInteractions(mLockPatternUtils);
+    }
+
+    @Test
     public void testClear() throws Exception {
         when(mLockPatternUtils.isLockPatternEnabled(mUserId)).thenReturn(true);
         when(mLockPatternUtils.isLockPasswordEnabled(mUserId)).thenReturn(false);
diff --git a/services/tests/servicestests/src/com/android/server/locksettings/SyntheticPasswordTests.java b/services/tests/servicestests/src/com/android/server/locksettings/SyntheticPasswordTests.java
index 94e02bc..0595a5b 100644
--- a/services/tests/servicestests/src/com/android/server/locksettings/SyntheticPasswordTests.java
+++ b/services/tests/servicestests/src/com/android/server/locksettings/SyntheticPasswordTests.java
@@ -40,10 +40,10 @@
 import com.android.server.locksettings.SyntheticPasswordManager.AuthenticationToken;
 import com.android.server.locksettings.SyntheticPasswordManager.PasswordData;
 
-import java.util.ArrayList;
-
 import org.mockito.ArgumentCaptor;
 
+import java.util.ArrayList;
+
 
 /**
  * runtest frameworks-services -c com.android.server.locksettings.SyntheticPasswordTests
@@ -448,6 +448,37 @@
         assertTrue(mLocalService.isEscrowTokenActive(handle, PRIMARY_USER_ID));
     }
 
+    public void testSetLockCredentialWithTokenFailsWithoutLockScreen() throws Exception {
+        final String password = "password";
+        final String pattern = "123654";
+        final String token = "some-high-entropy-secure-token";
+
+        mHasSecureLockScreen = false;
+        enableSyntheticPassword();
+        long handle = mLocalService.addEscrowToken(token.getBytes(), PRIMARY_USER_ID);
+        assertTrue(mLocalService.isEscrowTokenActive(handle, PRIMARY_USER_ID));
+
+        try {
+            mLocalService.setLockCredentialWithToken(password,
+                    LockPatternUtils.CREDENTIAL_TYPE_PASSWORD, handle, token.getBytes(),
+                    PASSWORD_QUALITY_ALPHABETIC, PRIMARY_USER_ID);
+            fail("An exception should have been thrown.");
+        } catch (UnsupportedOperationException e) {
+            // Success - the exception was expected.
+        }
+        assertFalse(mService.havePassword(PRIMARY_USER_ID));
+
+        try {
+            mLocalService.setLockCredentialWithToken(pattern,
+                    LockPatternUtils.CREDENTIAL_TYPE_PATTERN, handle, token.getBytes(),
+                    PASSWORD_QUALITY_ALPHABETIC, PRIMARY_USER_ID);
+            fail("An exception should have been thrown.");
+        } catch (UnsupportedOperationException e) {
+            // Success - the exception was expected.
+        }
+        assertFalse(mService.havePattern(PRIMARY_USER_ID));
+    }
+
     public void testgetHashFactorPrimaryUser() throws RemoteException {
         final String password = "password";
         mService.setLockCredential(password, LockPatternUtils.CREDENTIAL_TYPE_PASSWORD, null,
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
new file mode 100644
index 0000000..742ae41
--- /dev/null
+++ b/services/tests/servicestests/src/com/android/server/pm/PackageInstallerSessionTest.java
@@ -0,0 +1,308 @@
+/*
+ * 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.pm;
+
+import static org.junit.Assert.assertArrayEquals;
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertTrue;
+import static org.xmlpull.v1.XmlPullParser.END_DOCUMENT;
+import static org.xmlpull.v1.XmlPullParser.START_TAG;
+
+import android.content.pm.PackageInstaller;
+import android.util.AtomicFile;
+import android.util.Slog;
+import android.util.Xml;
+
+import androidx.test.runner.AndroidJUnit4;
+
+import com.android.internal.os.BackgroundThread;
+import com.android.internal.util.FastXmlSerializer;
+
+import libcore.io.IoUtils;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.mockito.Mock;
+import org.mockito.MockitoAnnotations;
+import org.xmlpull.v1.XmlPullParser;
+import org.xmlpull.v1.XmlPullParserException;
+import org.xmlpull.v1.XmlSerializer;
+
+import java.io.File;
+import java.io.FileInputStream;
+import java.io.FileNotFoundException;
+import java.io.FileOutputStream;
+import java.io.IOException;
+import java.nio.charset.StandardCharsets;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.List;
+
+@RunWith(AndroidJUnit4.class)
+public class PackageInstallerSessionTest {
+    private File mTmpDir;
+    private AtomicFile mSessionsFile;
+    private static final String TAG_SESSIONS = "sessions";
+
+    @Mock
+    PackageManagerService mMockPackageManagerInternal;
+
+    @Before
+    public void setUp() throws Exception {
+        mTmpDir = IoUtils.createTemporaryDirectory("PackageInstallerSessionTest");
+        mSessionsFile = new AtomicFile(
+                new File(mTmpDir.getAbsolutePath() + "/sessions.xml"), "package-session");
+        MockitoAnnotations.initMocks(this);
+    }
+
+    @Test
+    public void testWriteAndRestoreSessionXmlSimpleSession() {
+        PackageInstallerSession session = createSimpleSession();
+        dumpSession(session);
+        List<PackageInstallerSession> restored = restoreSessions();
+        assertEquals(1, restored.size());
+        assertSessionsEquivalent(session, restored.get(0));
+    }
+
+    @Test
+    public void testWriteAndRestoreSessionXmlStagedSession() {
+        PackageInstallerSession session = createStagedSession();
+        dumpSession(session);
+        List<PackageInstallerSession> restored = restoreSessions();
+        assertEquals(1, restored.size());
+        assertSessionsEquivalent(session, restored.get(0));
+    }
+
+    @Test
+    public void testWriteAndRestoreSessionXmlGrantedPermission() {
+        PackageInstallerSession session = createSessionWithGrantedPermissions();
+        dumpSession(session);
+        List<PackageInstallerSession> restored = restoreSessions();
+        assertEquals(1, restored.size());
+        assertSessionsEquivalent(session, restored.get(0));
+    }
+
+    @Test
+    public void testWriteAndRestoreSessionXmlMultiPackageSessions() {
+        PackageInstallerSession session = createMultiPackageParentSession(123, new int[]{234, 345});
+        PackageInstallerSession childSession1 = createMultiPackageChildSession(234, 123);
+        PackageInstallerSession childSession2 = createMultiPackageChildSession(345, 123);
+        List<PackageInstallerSession> sessionGroup =
+                Arrays.asList(session, childSession1, childSession2);
+        dumpSessions(sessionGroup);
+        List<PackageInstallerSession> restored = restoreSessions();
+        assertEquals(3, restored.size());
+        assertSessionsEquivalent(sessionGroup, restored);
+    }
+
+    private PackageInstallerSession createSimpleSession() {
+        return createSession(false, false, 123, false, PackageInstaller.SessionInfo.INVALID_ID,
+                null);
+    }
+
+    private PackageInstallerSession createStagedSession() {
+        return createSession(true, false, 123, false, PackageInstaller.SessionInfo.INVALID_ID,
+                null);
+    }
+
+    private PackageInstallerSession createSessionWithGrantedPermissions() {
+        return createSession(false, true, 123, false, PackageInstaller.SessionInfo.INVALID_ID,
+                null);
+    }
+
+    private PackageInstallerSession createMultiPackageParentSession(int sessionId,
+                                                                    int[] childSessionIds) {
+        return createSession(false, false, sessionId, true,
+                PackageInstaller.SessionInfo.INVALID_ID, childSessionIds);
+    }
+
+    private PackageInstallerSession createMultiPackageChildSession(int sessionId,
+                                                                   int parentSessionId) {
+        return createSession(false, false, sessionId, false, parentSessionId, null);
+    }
+
+    private PackageInstallerSession createSession(boolean staged, boolean withGrantedPermissions,
+                                                  int sessionId, boolean isMultiPackage,
+                                                  int parentSessionId, int[] childSessionIds) {
+        PackageInstaller.SessionParams params = new PackageInstaller.SessionParams(
+                PackageInstaller.SessionParams.MODE_FULL_INSTALL);
+        if (staged) {
+            params.isStaged = true;
+        }
+        if (withGrantedPermissions) {
+            params.grantedRuntimePermissions = new String[]{"permission1", "permission2"};
+        }
+        if (isMultiPackage) {
+            params.isMultiPackage = true;
+        }
+        return new PackageInstallerSession(
+                /* callback */ null,
+                /* context */null,
+                /* pm */ mMockPackageManagerInternal,
+                /* sessionProvider */ null,
+                /* looper */ BackgroundThread.getHandler().getLooper(),
+                /* stagingManager */ null,
+                /* sessionId */ sessionId,
+                /* userId */  456,
+                /* installerPackageName */ "testInstaller",
+                /* installerUid */ -1,
+                /* sessionParams */ params,
+                /* createdMillis */ 0L,
+                /* stageDir */ mTmpDir,
+                /* stageCid */ null,
+                /* prepared */ true,
+                /* sealed */ false,  // Setting to true would trigger some PM logic.
+                /* childSessionIds */ childSessionIds != null ? childSessionIds : new int[0],
+                /* parentSessionId */ parentSessionId,
+                /* isReady */ staged ? true : false,
+                /* isFailed */ false,
+                /* isApplied */false,
+                /* stagedSessionErrorCode */ PackageInstaller.SessionInfo.VERIFICATION_FAILED,
+                /* stagedSessionErrorMessage */ "some error");
+    }
+
+    private void dumpSession(PackageInstallerSession session) {
+        dumpSessions(Arrays.asList(session));
+    }
+
+    private void dumpSessions(List<PackageInstallerSession> sessions) {
+        FileOutputStream fos = null;
+        try {
+            fos = mSessionsFile.startWrite();
+
+            XmlSerializer out = new FastXmlSerializer();
+            out.setOutput(fos, StandardCharsets.UTF_8.name());
+            out.startDocument(null, true);
+            out.startTag(null, TAG_SESSIONS);
+            for (PackageInstallerSession session : sessions) {
+                session.write(out, mTmpDir);
+            }
+            out.endTag(null, TAG_SESSIONS);
+            out.endDocument();
+
+            mSessionsFile.finishWrite(fos);
+            Slog.d("PackageInstallerSessionTest", new String(mSessionsFile.readFully()));
+        } catch (IOException e) {
+            if (fos != null) {
+                mSessionsFile.failWrite(fos);
+            }
+        }
+    }
+
+    // This is roughly the logic used in PackageInstallerService to read the session. Note that
+    // this test stresses readFromXml method from PackageInstallerSession, and doesn't cover the
+    // PackageInstallerService portion of the parsing.
+    private List<PackageInstallerSession> restoreSessions() {
+        List<PackageInstallerSession> ret = new ArrayList<>();
+        FileInputStream fis = null;
+        try {
+            fis = mSessionsFile.openRead();
+            final XmlPullParser in = Xml.newPullParser();
+            in.setInput(fis, StandardCharsets.UTF_8.name());
+
+            int type;
+            while ((type = in.next()) != END_DOCUMENT) {
+                if (type == START_TAG) {
+                    final String tag = in.getName();
+                    if (PackageInstallerSession.TAG_SESSION.equals(tag)) {
+                        final PackageInstallerSession session;
+                        try {
+                            session = PackageInstallerSession.readFromXml(in, null,
+                                    null, mMockPackageManagerInternal,
+                                    BackgroundThread.getHandler().getLooper(), null,
+                                    mTmpDir, null);
+                            ret.add(session);
+                        } catch (Exception e) {
+                            Slog.e("PackageInstallerSessionTest", "Exception ", e);
+                            continue;
+                        }
+                    }
+                }
+            }
+        } catch (FileNotFoundException e) {
+            // Missing sessions are okay, probably first boot
+        } catch (IOException | XmlPullParserException e) {
+
+        } finally {
+            IoUtils.closeQuietly(fis);
+        }
+        return ret;
+    }
+
+    private void assertSessionParamsEquivalent(PackageInstaller.SessionParams expected,
+                                               PackageInstaller.SessionParams actual) {
+        assertEquals(expected.mode, actual.mode);
+        assertEquals(expected.installFlags, actual.installFlags);
+        assertEquals(expected.installLocation, actual.installLocation);
+        assertEquals(expected.installReason, actual.installReason);
+        assertEquals(expected.sizeBytes, actual.sizeBytes);
+        assertEquals(expected.appPackageName, actual.appPackageName);
+        assertEquals(expected.appIcon, actual.appIcon);
+        assertEquals(expected.originatingUri, actual.originatingUri);
+        assertEquals(expected.originatingUid, actual.originatingUid);
+        assertEquals(expected.referrerUri, actual.referrerUri);
+        assertEquals(expected.abiOverride, actual.abiOverride);
+        assertEquals(expected.volumeUuid, actual.volumeUuid);
+        assertArrayEquals(expected.grantedRuntimePermissions, actual.grantedRuntimePermissions);
+        assertEquals(expected.installerPackageName, actual.installerPackageName);
+        assertEquals(expected.isMultiPackage, actual.isMultiPackage);
+        assertEquals(expected.isStaged, actual.isStaged);
+    }
+
+    private void assertSessionsEquivalent(List<PackageInstallerSession> expected,
+                                          List<PackageInstallerSession> actual) {
+        assertEquals(expected.size(), actual.size());
+        for (PackageInstallerSession expectedSession : expected) {
+            boolean foundSession = false;
+            for (PackageInstallerSession actualSession : actual) {
+                if (expectedSession.sessionId == actualSession.sessionId) {
+                    // We should only encounter each expected session once.
+                    assertFalse(foundSession);
+                    foundSession = true;
+                    assertSessionsEquivalent(expectedSession, actualSession);
+                }
+            }
+            assertTrue(foundSession);
+        }
+    }
+
+    private void assertSessionsEquivalent(PackageInstallerSession expected,
+                                          PackageInstallerSession actual) {
+        assertEquals(expected.sessionId, actual.sessionId);
+        assertEquals(expected.userId, actual.userId);
+        assertSessionParamsEquivalent(expected.params, actual.params);
+        assertEquals(expected.getInstallerUid(), actual.getInstallerUid());
+        assertEquals(expected.stageDir.getAbsolutePath(), actual.stageDir.getAbsolutePath());
+        assertEquals(expected.stageCid, actual.stageCid);
+        assertEquals(expected.isPrepared(), actual.isPrepared());
+        assertEquals(expected.isStaged(), actual.isStaged());
+        assertEquals(expected.isStagedSessionApplied(), actual.isStagedSessionApplied());
+        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());
+        assertEquals(expected.hasParentSessionId(), actual.hasParentSessionId());
+        assertEquals(expected.getParentSessionId(), actual.getParentSessionId());
+        assertArrayEquals(expected.getChildSessionIds(), actual.getChildSessionIds());
+    }
+}
diff --git a/services/tests/servicestests/src/com/android/server/pm/dex/DexLoggerTests.java b/services/tests/servicestests/src/com/android/server/pm/dex/DexLoggerTests.java
index f817e8e..6da202b 100644
--- a/services/tests/servicestests/src/com/android/server/pm/dex/DexLoggerTests.java
+++ b/services/tests/servicestests/src/com/android/server/pm/dex/DexLoggerTests.java
@@ -16,8 +16,6 @@
 
 package com.android.server.pm.dex;
 
-import static com.android.server.pm.dex.PackageDynamicCodeLoading.FILE_TYPE_DEX;
-
 import static com.google.common.truth.Truth.assertThat;
 
 import static org.mockito.Mockito.atMost;
@@ -26,10 +24,12 @@
 import static org.mockito.Mockito.reset;
 import static org.mockito.Mockito.verify;
 import static org.mockito.Mockito.verifyZeroInteractions;
+import static org.mockito.Mockito.when;
 
 import android.content.pm.ApplicationInfo;
 import android.content.pm.IPackageManager;
 import android.content.pm.PackageInfo;
+import android.os.UserHandle;
 import android.os.storage.StorageManager;
 
 import androidx.test.filters.SmallTest;
@@ -56,40 +56,44 @@
 public class DexLoggerTests {
     private static final String OWNING_PACKAGE_NAME = "package.name";
     private static final String VOLUME_UUID = "volUuid";
-    private static final String DEX_PATH = "/bar/foo.jar";
+    private static final String FILE_PATH = "/bar/foo.jar";
     private static final int STORAGE_FLAGS = StorageManager.FLAG_STORAGE_DE;
     private static final int OWNER_UID = 43;
     private static final int OWNER_USER_ID = 44;
 
     // Obtained via: echo -n "foo.jar" | sha256sum
-    private static final String DEX_FILENAME_HASH =
+    private static final String FILENAME_HASH =
             "91D7B844D7CC9673748FF057D8DC83972280FC28537D381AA42015A9CF214B9F";
 
-    private static final byte[] CONTENT_HASH_BYTES = new byte[] {
-        1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16,
-        17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30, 31, 32
+    private static final byte[] CONTENT_HASH_BYTES = new byte[]{
+            1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16,
+            17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30, 31, 32
     };
     private static final String CONTENT_HASH =
             "0102030405060708090A0B0C0D0E0F101112131415161718191A1B1C1D1E1F20";
     private static final byte[] EMPTY_BYTES = {};
 
-    @Rule public MockitoRule mockito = MockitoJUnit.rule().strictness(Strictness.STRICT_STUBS);
+    private static final String EXPECTED_MESSAGE_WITHOUT_CONTENT_HASH =
+            "dcl:" + FILENAME_HASH;
+    private static final String EXPECTED_MESSAGE_WITH_CONTENT_HASH =
+            EXPECTED_MESSAGE_WITHOUT_CONTENT_HASH + " " + CONTENT_HASH;
+    private static final String EXPECTED_MESSAGE_NATIVE_WITH_CONTENT_HASH =
+            "dcln:" + FILENAME_HASH + " " + CONTENT_HASH;
+
+    @Rule public MockitoRule mockito = MockitoJUnit.rule().strictness(Strictness.LENIENT);
 
     @Mock IPackageManager mPM;
     @Mock Installer mInstaller;
 
-    private PackageDynamicCodeLoading mPackageDynamicCodeLoading;
     private DexLogger mDexLogger;
 
     private final ListMultimap<Integer, String> mMessagesForUid = ArrayListMultimap.create();
     private boolean mWriteTriggered = false;
-    private static final String EXPECTED_MESSAGE_WITH_CONTENT_HASH =
-            DEX_FILENAME_HASH + " " + CONTENT_HASH;
 
     @Before
     public void setup() throws Exception {
         // Disable actually attempting to do file writes.
-        mPackageDynamicCodeLoading = new PackageDynamicCodeLoading() {
+        PackageDynamicCodeLoading packageDynamicCodeLoading = new PackageDynamicCodeLoading() {
             @Override
             void maybeWriteAsync() {
                 mWriteTriggered = true;
@@ -102,13 +106,13 @@
         };
 
         // For test purposes capture log messages as well as sending to the event log.
-        mDexLogger = new DexLogger(mPM, mInstaller, mPackageDynamicCodeLoading) {
+        mDexLogger = new DexLogger(mPM, mInstaller, packageDynamicCodeLoading) {
             @Override
-                void writeDclEvent(int uid, String message) {
-                    super.writeDclEvent(uid, message);
-                    mMessagesForUid.put(uid, message);
-                }
-            };
+            void writeDclEvent(String subtag, int uid, String message) {
+                super.writeDclEvent(subtag, uid, message);
+                mMessagesForUid.put(uid, subtag + ":" + message);
+            }
+        };
 
         // Make the owning package exist in our mock PackageManager.
         ApplicationInfo appInfo = new ApplicationInfo();
@@ -124,9 +128,9 @@
 
     @Test
     public void testOneLoader_ownFile_withFileHash() throws Exception {
-        whenFileIsHashed(DEX_PATH, doReturn(CONTENT_HASH_BYTES));
+        whenFileIsHashed(FILE_PATH, doReturn(CONTENT_HASH_BYTES));
 
-        recordLoad(OWNING_PACKAGE_NAME, DEX_PATH);
+        recordLoad(OWNING_PACKAGE_NAME, FILE_PATH);
         mDexLogger.logDynamicCodeLoading(OWNING_PACKAGE_NAME);
 
         assertThat(mMessagesForUid.keys()).containsExactly(OWNER_UID);
@@ -139,13 +143,13 @@
 
     @Test
     public void testOneLoader_ownFile_noFileHash() throws Exception {
-        whenFileIsHashed(DEX_PATH, doReturn(EMPTY_BYTES));
+        whenFileIsHashed(FILE_PATH, doReturn(EMPTY_BYTES));
 
-        recordLoad(OWNING_PACKAGE_NAME, DEX_PATH);
+        recordLoad(OWNING_PACKAGE_NAME, FILE_PATH);
         mDexLogger.logDynamicCodeLoading(OWNING_PACKAGE_NAME);
 
         assertThat(mMessagesForUid.keys()).containsExactly(OWNER_UID);
-        assertThat(mMessagesForUid).containsEntry(OWNER_UID, DEX_FILENAME_HASH);
+        assertThat(mMessagesForUid).containsEntry(OWNER_UID, EXPECTED_MESSAGE_WITHOUT_CONTENT_HASH);
 
         // File should be removed from the DCL list, since we can't hash it.
         assertThat(mWriteTriggered).isTrue();
@@ -154,13 +158,14 @@
 
     @Test
     public void testOneLoader_ownFile_hashingFails() throws Exception {
-        whenFileIsHashed(DEX_PATH, doThrow(new InstallerException("Intentional failure for test")));
+        whenFileIsHashed(FILE_PATH,
+                doThrow(new InstallerException("Intentional failure for test")));
 
-        recordLoad(OWNING_PACKAGE_NAME, DEX_PATH);
+        recordLoad(OWNING_PACKAGE_NAME, FILE_PATH);
         mDexLogger.logDynamicCodeLoading(OWNING_PACKAGE_NAME);
 
         assertThat(mMessagesForUid.keys()).containsExactly(OWNER_UID);
-        assertThat(mMessagesForUid).containsEntry(OWNER_UID, DEX_FILENAME_HASH);
+        assertThat(mMessagesForUid).containsEntry(OWNER_UID, EXPECTED_MESSAGE_WITHOUT_CONTENT_HASH);
 
         // File should be removed from the DCL list, since we can't hash it.
         assertThat(mWriteTriggered).isTrue();
@@ -178,11 +183,23 @@
     }
 
     @Test
+    public void testOneLoader_pathTraversal() throws Exception {
+        String filePath = "/bar/../secret/foo.jar";
+        whenFileIsHashed(filePath, doReturn(CONTENT_HASH_BYTES));
+        setPackageUid(OWNING_PACKAGE_NAME, -1);
+
+        recordLoad(OWNING_PACKAGE_NAME, filePath);
+        mDexLogger.logDynamicCodeLoading(OWNING_PACKAGE_NAME);
+
+        assertThat(mMessagesForUid).isEmpty();
+    }
+
+    @Test
     public void testOneLoader_differentOwner() throws Exception {
-        whenFileIsHashed(DEX_PATH, doReturn(CONTENT_HASH_BYTES));
+        whenFileIsHashed(FILE_PATH, doReturn(CONTENT_HASH_BYTES));
         setPackageUid("other.package.name", 1001);
 
-        recordLoad("other.package.name", DEX_PATH);
+        recordLoad("other.package.name", FILE_PATH);
         mDexLogger.logDynamicCodeLoading(OWNING_PACKAGE_NAME);
 
         assertThat(mMessagesForUid.keys()).containsExactly(1001);
@@ -192,10 +209,10 @@
 
     @Test
     public void testOneLoader_differentOwner_uninstalled() throws Exception {
-        whenFileIsHashed(DEX_PATH, doReturn(CONTENT_HASH_BYTES));
+        whenFileIsHashed(FILE_PATH, doReturn(CONTENT_HASH_BYTES));
         setPackageUid("other.package.name", -1);
 
-        recordLoad("other.package.name", DEX_PATH);
+        recordLoad("other.package.name", FILE_PATH);
         mDexLogger.logDynamicCodeLoading(OWNING_PACKAGE_NAME);
 
         assertThat(mMessagesForUid).isEmpty();
@@ -203,22 +220,38 @@
     }
 
     @Test
+    public void testNativeCodeLoad() throws Exception {
+        whenFileIsHashed(FILE_PATH, doReturn(CONTENT_HASH_BYTES));
+
+        recordLoadNative(FILE_PATH);
+        mDexLogger.logDynamicCodeLoading(OWNING_PACKAGE_NAME);
+
+        assertThat(mMessagesForUid.keys()).containsExactly(OWNER_UID);
+        assertThat(mMessagesForUid)
+                .containsEntry(OWNER_UID, EXPECTED_MESSAGE_NATIVE_WITH_CONTENT_HASH);
+
+        assertThat(mWriteTriggered).isFalse();
+        assertThat(mDexLogger.getAllPackagesWithDynamicCodeLoading())
+                .containsExactly(OWNING_PACKAGE_NAME);
+    }
+
+    @Test
     public void testMultipleLoadersAndFiles() throws Exception {
         String otherDexPath = "/bar/nosuchdir/foo.jar";
-        whenFileIsHashed(DEX_PATH, doReturn(CONTENT_HASH_BYTES));
+        whenFileIsHashed(FILE_PATH, doReturn(CONTENT_HASH_BYTES));
         whenFileIsHashed(otherDexPath, doReturn(EMPTY_BYTES));
         setPackageUid("other.package.name1", 1001);
         setPackageUid("other.package.name2", 1002);
 
-        recordLoad("other.package.name1", DEX_PATH);
+        recordLoad("other.package.name1", FILE_PATH);
         recordLoad("other.package.name1", otherDexPath);
-        recordLoad("other.package.name2", DEX_PATH);
-        recordLoad(OWNING_PACKAGE_NAME, DEX_PATH);
+        recordLoad("other.package.name2", FILE_PATH);
+        recordLoad(OWNING_PACKAGE_NAME, FILE_PATH);
         mDexLogger.logDynamicCodeLoading(OWNING_PACKAGE_NAME);
 
         assertThat(mMessagesForUid.keys()).containsExactly(1001, 1001, 1002, OWNER_UID);
         assertThat(mMessagesForUid).containsEntry(1001, EXPECTED_MESSAGE_WITH_CONTENT_HASH);
-        assertThat(mMessagesForUid).containsEntry(1001, DEX_FILENAME_HASH);
+        assertThat(mMessagesForUid).containsEntry(1001, EXPECTED_MESSAGE_WITHOUT_CONTENT_HASH);
         assertThat(mMessagesForUid).containsEntry(1002, EXPECTED_MESSAGE_WITH_CONTENT_HASH);
         assertThat(mMessagesForUid).containsEntry(OWNER_UID, EXPECTED_MESSAGE_WITH_CONTENT_HASH);
 
@@ -233,7 +266,7 @@
     @Test
     public void testUnknownOwner() {
         reset(mPM);
-        recordLoad(OWNING_PACKAGE_NAME, DEX_PATH);
+        recordLoad(OWNING_PACKAGE_NAME, FILE_PATH);
         mDexLogger.logDynamicCodeLoading("other.package.name");
 
         assertThat(mMessagesForUid).isEmpty();
@@ -244,7 +277,7 @@
     @Test
     public void testUninstalledPackage() {
         reset(mPM);
-        recordLoad(OWNING_PACKAGE_NAME, DEX_PATH);
+        recordLoad(OWNING_PACKAGE_NAME, FILE_PATH);
         mDexLogger.logDynamicCodeLoading(OWNING_PACKAGE_NAME);
 
         assertThat(mMessagesForUid).isEmpty();
@@ -262,7 +295,16 @@
     }
 
     private void recordLoad(String loadingPackageName, String dexPath) {
-        mPackageDynamicCodeLoading.record(
-                OWNING_PACKAGE_NAME, dexPath, FILE_TYPE_DEX, OWNER_USER_ID, loadingPackageName);
+        mDexLogger.recordDex(OWNER_USER_ID, dexPath, OWNING_PACKAGE_NAME, loadingPackageName);
+        mWriteTriggered = false;
+    }
+
+    private void recordLoadNative(String nativePath) throws Exception {
+        int loadingUid = UserHandle.getUid(OWNER_USER_ID, OWNER_UID);
+        String[] packageNames = { OWNING_PACKAGE_NAME };
+        when(mPM.getPackagesForUid(loadingUid)).thenReturn(packageNames);
+
+        mDexLogger.recordNative(loadingUid, nativePath);
+        mWriteTriggered = false;
     }
 }
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/servicestests/src/com/android/server/usage/UsageStatsDatabaseTest.java b/services/tests/servicestests/src/com/android/server/usage/UsageStatsDatabaseTest.java
index 860656b..8d9b3cf 100644
--- a/services/tests/servicestests/src/com/android/server/usage/UsageStatsDatabaseTest.java
+++ b/services/tests/servicestests/src/com/android/server/usage/UsageStatsDatabaseTest.java
@@ -45,6 +45,8 @@
 @RunWith(AndroidJUnit4.class)
 @SmallTest
 public class UsageStatsDatabaseTest {
+
+    private static final int MAX_TESTED_VERSION = 4;
     protected Context mContext;
     private UsageStatsDatabase mUsageStatsDatabase;
     private File mTestDir;
@@ -131,8 +133,8 @@
 
         for (int i = 0; i < numberOfEvents; i++) {
             Event event = new Event();
-            final int packageInt = ((i / 3) % 7);
-            event.mPackage = "fake.package.name" + packageInt; //clusters of 3 events from 7 "apps"
+            final int packageInt = ((i / 3) % 7); //clusters of 3 events from 7 "apps"
+            event.mPackage = "fake.package.name" + packageInt;
             if (packageInt == 3) {
                 // Third app is an instant app
                 event.mFlags |= Event.FLAG_IS_PACKAGE_INSTANT_APP;
@@ -144,6 +146,13 @@
             event.mEventType = i % (MAX_EVENT_TYPE + 1); //"random" event type
             event.mInstanceId = instanceId;
 
+
+            final int rootPackageInt = (i % 5); // 5 "apps" start each task
+            event.mTaskRootPackage = "fake.package.name" + rootPackageInt;
+
+            final int rootClassInt = i % 6;
+            event.mTaskRootClass = ".fake.class.name" + rootClassInt;
+
             switch (event.mEventType) {
                 case Event.CONFIGURATION_CHANGE:
                     //empty config,
@@ -163,7 +172,7 @@
                     break;
             }
 
-            mIntervalStats.events.insert(event);
+            mIntervalStats.addEvent(event);
             mIntervalStats.update(event.mPackage, event.mClass, event.mTimeStamp, event.mEventType,
                     event.mInstanceId);
 
@@ -234,31 +243,40 @@
         assertEquals(us1.mChooserCounts, us2.mChooserCounts);
     }
 
-    void compareUsageEvent(Event e1, Event e2, int debugId) {
-        assertEquals(e1.mPackage, e2.mPackage, "Usage event " + debugId);
-        assertEquals(e1.mClass, e2.mClass, "Usage event " + debugId);
-        assertEquals(e1.mTimeStamp, e2.mTimeStamp, "Usage event " + debugId);
-        assertEquals(e1.mEventType, e2.mEventType, "Usage event " + debugId);
-        switch (e1.mEventType) {
-            case Event.CONFIGURATION_CHANGE:
-                assertEquals(e1.mConfiguration, e2.mConfiguration,
-                        "Usage event " + debugId + e2.mConfiguration.toString());
-                break;
-            case Event.SHORTCUT_INVOCATION:
-                assertEquals(e1.mShortcutId, e2.mShortcutId, "Usage event " + debugId);
-                break;
-            case Event.STANDBY_BUCKET_CHANGED:
-                assertEquals(e1.mBucketAndReason, e2.mBucketAndReason, "Usage event " + debugId);
-                break;
-            case Event.NOTIFICATION_INTERRUPTION:
-                assertEquals(e1.mNotificationChannelId, e2.mNotificationChannelId,
-                        "Usage event " + debugId);
-                break;
+    void compareUsageEvent(Event e1, Event e2, int debugId, int minVersion) {
+        switch (minVersion) {
+            case 4: // test fields added in version 4
+                assertEquals(e1.mInstanceId, e2.mInstanceId, "Usage event " + debugId);
+                assertEquals(e1.mTaskRootPackage, e2.mTaskRootPackage, "Usage event " + debugId);
+                assertEquals(e1.mTaskRootClass, e2.mTaskRootClass, "Usage event " + debugId);
+                // fallthrough
+            default:
+                assertEquals(e1.mPackage, e2.mPackage, "Usage event " + debugId);
+                assertEquals(e1.mClass, e2.mClass, "Usage event " + debugId);
+                assertEquals(e1.mTimeStamp, e2.mTimeStamp, "Usage event " + debugId);
+                assertEquals(e1.mEventType, e2.mEventType, "Usage event " + debugId);
+                switch (e1.mEventType) {
+                    case Event.CONFIGURATION_CHANGE:
+                        assertEquals(e1.mConfiguration, e2.mConfiguration,
+                                "Usage event " + debugId + e2.mConfiguration.toString());
+                        break;
+                    case Event.SHORTCUT_INVOCATION:
+                        assertEquals(e1.mShortcutId, e2.mShortcutId, "Usage event " + debugId);
+                        break;
+                    case Event.STANDBY_BUCKET_CHANGED:
+                        assertEquals(e1.mBucketAndReason, e2.mBucketAndReason,
+                                "Usage event " + debugId);
+                        break;
+                    case Event.NOTIFICATION_INTERRUPTION:
+                        assertEquals(e1.mNotificationChannelId, e2.mNotificationChannelId,
+                                "Usage event " + debugId);
+                        break;
+                }
+                assertEquals(e1.mFlags, e2.mFlags);
         }
-        assertEquals(e1.mFlags, e2.mFlags);
     }
 
-    void compareIntervalStats(IntervalStats stats1, IntervalStats stats2) {
+    void compareIntervalStats(IntervalStats stats1, IntervalStats stats2, int minVersion) {
         assertEquals(stats1.majorVersion, stats2.majorVersion);
         assertEquals(stats1.minorVersion, stats2.minorVersion);
         assertEquals(stats1.beginTime, stats2.beginTime);
@@ -311,7 +329,7 @@
         } else {
             assertEquals(stats1.events.size(), stats2.events.size());
             for (int i = 0; i < stats1.events.size(); i++) {
-                compareUsageEvent(stats1.events.get(i), stats2.events.get(i), i);
+                compareUsageEvent(stats1.events.get(i), stats2.events.get(i), i, minVersion);
             }
         }
     }
@@ -326,7 +344,7 @@
                 mIntervalStatsVerifier);
 
         assertEquals(1, stats.size());
-        compareIntervalStats(mIntervalStats, stats.get(0));
+        compareIntervalStats(mIntervalStats, stats.get(0), MAX_TESTED_VERSION);
     }
 
     /**
@@ -359,8 +377,10 @@
                 mIntervalStatsVerifier);
 
         assertEquals(1, stats.size());
+
+        final int minVersion = oldVersion < newVersion ? oldVersion : newVersion;
         // The written and read IntervalStats should match
-        compareIntervalStats(mIntervalStats, stats.get(0));
+        compareIntervalStats(mIntervalStats, stats.get(0), minVersion);
     }
 
     /**
@@ -401,7 +421,7 @@
         if (mIntervalStats.events != null) mIntervalStats.events.clear();
 
         // The written and read IntervalStats should match
-        compareIntervalStats(mIntervalStats, stats.get(0));
+        compareIntervalStats(mIntervalStats, stats.get(0), version);
     }
 
     /**
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 3f6361b..9c6ab0a 100644
--- a/services/tests/uiservicestests/src/com/android/server/notification/NotificationManagerServiceTest.java
+++ b/services/tests/uiservicestests/src/com/android/server/notification/NotificationManagerServiceTest.java
@@ -185,6 +185,9 @@
 
     private NotificationChannel mTestNotificationChannel = new NotificationChannel(
             TEST_CHANNEL_ID, TEST_CHANNEL_ID, IMPORTANCE_DEFAULT);
+
+    private static final int NOTIFICATION_LOCATION_UNKNOWN = 0;
+
     @Mock
     private NotificationListeners mListeners;
     @Mock private NotificationAssistants mAssistants;
@@ -242,8 +245,8 @@
         }
 
         @Override
-        void logSmartSuggestionsVisible(NotificationRecord r) {
-            super.logSmartSuggestionsVisible(r);
+        void logSmartSuggestionsVisible(NotificationRecord r, int notificationLocation) {
+            super.logSmartSuggestionsVisible(r, notificationLocation);
             countLogSmartSuggestionsVisible++;
         }
 
@@ -2528,11 +2531,13 @@
         NotificationRecord r = generateNotificationRecord(mTestNotificationChannel);
         mService.addNotification(r);
 
-        mService.mNotificationDelegate.onNotificationExpansionChanged(r.getKey(), true, true);
+        mService.mNotificationDelegate.onNotificationExpansionChanged(r.getKey(), true, true,
+                NOTIFICATION_LOCATION_UNKNOWN);
         verify(mAssistants).notifyAssistantExpansionChangedLocked(eq(r.sbn), eq(true), eq((true)));
         assertTrue(mService.getNotificationRecord(r.getKey()).getStats().hasExpanded());
 
-        mService.mNotificationDelegate.onNotificationExpansionChanged(r.getKey(), true, false);
+        mService.mNotificationDelegate.onNotificationExpansionChanged(r.getKey(), true, false,
+                NOTIFICATION_LOCATION_UNKNOWN);
         verify(mAssistants).notifyAssistantExpansionChangedLocked(eq(r.sbn), eq(true), eq((false)));
         assertTrue(mService.getNotificationRecord(r.getKey()).getStats().hasExpanded());
     }
@@ -2542,11 +2547,13 @@
         NotificationRecord r = generateNotificationRecord(mTestNotificationChannel);
         mService.addNotification(r);
 
-        mService.mNotificationDelegate.onNotificationExpansionChanged(r.getKey(), false, true);
+        mService.mNotificationDelegate.onNotificationExpansionChanged(r.getKey(), false, true,
+                NOTIFICATION_LOCATION_UNKNOWN);
         assertFalse(mService.getNotificationRecord(r.getKey()).getStats().hasExpanded());
         verify(mAssistants).notifyAssistantExpansionChangedLocked(eq(r.sbn), eq(false), eq((true)));
 
-        mService.mNotificationDelegate.onNotificationExpansionChanged(r.getKey(), false, false);
+        mService.mNotificationDelegate.onNotificationExpansionChanged(r.getKey(), false, false,
+                NOTIFICATION_LOCATION_UNKNOWN);
         assertFalse(mService.getNotificationRecord(r.getKey()).getStats().hasExpanded());
         verify(mAssistants).notifyAssistantExpansionChangedLocked(
                 eq(r.sbn), eq(false), eq((false)));
@@ -3864,7 +3871,8 @@
         NotificationRecord r = generateNotificationRecord(mTestNotificationChannel);
         mService.addNotification(r);
 
-        mService.mNotificationDelegate.onNotificationExpansionChanged(r.getKey(), false, true);
+        mService.mNotificationDelegate.onNotificationExpansionChanged(r.getKey(), false, true,
+                NOTIFICATION_LOCATION_UNKNOWN);
         NotificationVisibility[] notificationVisibility = new NotificationVisibility[] {
                 NotificationVisibility.obtain(r.getKey(), 0, 0, true)
         };
@@ -3879,7 +3887,8 @@
         NotificationRecord r = generateNotificationRecord(mTestNotificationChannel);
         mService.addNotification(r);
 
-        mService.mNotificationDelegate.onNotificationExpansionChanged(r.getKey(), false, true);
+        mService.mNotificationDelegate.onNotificationExpansionChanged(r.getKey(), false, true,
+                NOTIFICATION_LOCATION_UNKNOWN);
 
         assertEquals(0, mService.countLogSmartSuggestionsVisible);
     }
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 319ffed..8be63fc 100644
--- a/services/tests/wmtests/src/com/android/server/wm/ActivityRecordTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/ActivityRecordTests.java
@@ -31,7 +31,6 @@
 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;
 
@@ -76,9 +75,6 @@
         mStack = (TestActivityStack) new StackBuilder(mRootActivityContainer).build();
         mTask = mStack.getChildAt(0);
         mActivity = mTask.getTopActivity();
-
-        doReturn(false).when(mService).isBooting();
-        doReturn(true).when(mService).isBooted();
     }
 
     @Test
@@ -121,23 +117,22 @@
 
         mActivity.setState(STOPPED, "testPausingWhenVisibleFromStopped");
 
-        // The activity is in the focused stack so it should be resumed.
+        // The activity is in the focused stack so it should not move to paused.
         mActivity.makeVisibleIfNeeded(null /* starting */, true /* reportToClient */);
-        assertTrue(mActivity.isState(RESUMED));
+        assertTrue(mActivity.isState(STOPPED));
         assertFalse(pauseFound.value);
 
-        // Make the activity non focusable
-        mActivity.setState(STOPPED, "testPausingWhenVisibleFromStopped");
-        doReturn(false).when(mActivity).isFocusable();
+        // Clear focused stack
+        final ActivityDisplay display = mRootActivityContainer.getDefaultDisplay();
+        when(display.getFocusedStack()).thenReturn(null);
 
-        // If the activity is not focusable, it should move to paused.
+        // In the unfocused stack, the activity 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 ea8f33f..68df87e 100644
--- a/services/tests/wmtests/src/com/android/server/wm/ActivityTestsBase.java
+++ b/services/tests/wmtests/src/com/android/server/wm/ActivityTestsBase.java
@@ -55,7 +55,6 @@
 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;
@@ -426,7 +425,6 @@
             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,
@@ -582,8 +580,6 @@
             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/AppWindowTokenAnimationTests.java b/services/tests/wmtests/src/com/android/server/wm/AppWindowTokenAnimationTests.java
index d0b9225..ea5ab7b 100644
--- a/services/tests/wmtests/src/com/android/server/wm/AppWindowTokenAnimationTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/AppWindowTokenAnimationTests.java
@@ -70,9 +70,9 @@
 
         mToken.mSurfaceAnimator.startAnimation(mTransaction, mSpec, true /* hidden */);
         verify(mTransaction).reparent(eq(mToken.getSurfaceControl()),
-                eq(mToken.mSurfaceAnimator.mLeash.getHandle()));
+                eq(mToken.mSurfaceAnimator.mLeash));
         verify(mTransaction).reparent(eq(mToken.mSurfaceAnimator.mLeash),
-                eq(mToken.mAnimationBoundsLayer.getHandle()));
+                eq(mToken.mAnimationBoundsLayer));
     }
 
     @Test
@@ -111,7 +111,7 @@
 
         mToken.mSurfaceAnimator.startAnimation(mTransaction, mSpec, true /* hidden */);
         verify(mTransaction).reparent(eq(mToken.getSurfaceControl()),
-                eq(mToken.mSurfaceAnimator.mLeash.getHandle()));
+                eq(mToken.mSurfaceAnimator.mLeash));
         assertThat(mToken.mAnimationBoundsLayer).isNull();
     }
 }
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/SurfaceAnimatorTest.java b/services/tests/wmtests/src/com/android/server/wm/SurfaceAnimatorTest.java
index ad80cd6..9b84215 100644
--- a/services/tests/wmtests/src/com/android/server/wm/SurfaceAnimatorTest.java
+++ b/services/tests/wmtests/src/com/android/server/wm/SurfaceAnimatorTest.java
@@ -90,7 +90,7 @@
         final ArgumentCaptor<OnAnimationFinishedCallback> callbackCaptor = ArgumentCaptor.forClass(
                 OnAnimationFinishedCallback.class);
         assertAnimating(mAnimatable);
-        verify(mTransaction).reparent(eq(mAnimatable.mSurface), eq(mAnimatable.mLeash.getHandle()));
+        verify(mTransaction).reparent(eq(mAnimatable.mSurface), eq(mAnimatable.mLeash));
         verify(mSpec).startAnimation(any(), any(), callbackCaptor.capture());
 
         callbackCaptor.getValue().onAnimationFinished(mSpec);
diff --git a/services/tests/wmtests/src/com/android/server/wm/TaskPositionerTests.java b/services/tests/wmtests/src/com/android/server/wm/TaskPositionerTests.java
index b996bfb..c595868 100644
--- a/services/tests/wmtests/src/com/android/server/wm/TaskPositionerTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/TaskPositionerTests.java
@@ -19,6 +19,10 @@
 import static android.app.WindowConfiguration.WINDOWING_MODE_FREEFORM;
 import static android.view.WindowManager.LayoutParams.TYPE_BASE_APPLICATION;
 
+import static com.android.dx.mockito.inline.extended.ExtendedMockito.any;
+import static com.android.dx.mockito.inline.extended.ExtendedMockito.doAnswer;
+import static com.android.dx.mockito.inline.extended.ExtendedMockito.mock;
+import static com.android.dx.mockito.inline.extended.ExtendedMockito.spyOn;
 import static com.android.server.wm.TaskPositioner.MIN_ASPECT;
 import static com.android.server.wm.WindowManagerService.dipToPixel;
 import static com.android.server.wm.WindowState.MINIMUM_VISIBLE_HEIGHT_IN_DP;
@@ -38,13 +42,12 @@
 
 import org.junit.Before;
 import org.junit.Test;
-import org.mockito.Mockito;
 
 /**
  * Tests for the {@link TaskPositioner} class.
  *
  * Build/Install/Run:
- *  atest FrameworksServicesTests:TaskPositionerTests
+ *  atest WmTests:TaskPositionerTests
  */
 @SmallTest
 public class TaskPositionerTests extends WindowTestsBase {
@@ -73,18 +76,17 @@
         mMinVisibleWidth = dipToPixel(MINIMUM_VISIBLE_WIDTH_IN_DP, dm);
         mMinVisibleHeight = dipToPixel(MINIMUM_VISIBLE_HEIGHT_IN_DP, dm);
 
-        mPositioner = new TaskPositioner(mWm, Mockito.mock(IActivityTaskManager.class));
+        mPositioner = new TaskPositioner(mWm, mock(IActivityTaskManager.class));
         mPositioner.register(mDisplayContent);
 
-        mWindow = Mockito.spy(createWindow(null, TYPE_BASE_APPLICATION, "window"));
-        final Task task = Mockito.spy(mWindow.getTask());
-        Mockito.when(mWindow.getTask()).thenReturn(task);
-
-        Mockito.doAnswer(invocation -> {
+        mWindow = createWindow(null, TYPE_BASE_APPLICATION, "window");
+        final Task task = mWindow.getTask();
+        spyOn(task);
+        doAnswer(invocation -> {
             final Rect rect = (Rect) invocation.getArguments()[0];
             rect.set(mDimBounds);
-            return (Void) null;
-        }).when(task).getDimBounds(Mockito.any(Rect.class));
+            return null;
+        }).when(task).getDimBounds(any(Rect.class));
 
         mWindow.getStack().setWindowingMode(WINDOWING_MODE_FREEFORM);
     }
diff --git a/services/tests/wmtests/src/com/android/server/wm/TaskSnapshotPersisterTestBase.java b/services/tests/wmtests/src/com/android/server/wm/TaskSnapshotPersisterTestBase.java
index 946ffb60..d29e3fa 100644
--- a/services/tests/wmtests/src/com/android/server/wm/TaskSnapshotPersisterTestBase.java
+++ b/services/tests/wmtests/src/com/android/server/wm/TaskSnapshotPersisterTestBase.java
@@ -53,7 +53,7 @@
     public void setUp() {
         final UserManager um = UserManager.get(getInstrumentation().getTargetContext());
         mTestUserId = um.getUserHandle();
-        mPersister = new TaskSnapshotPersister(userId -> FILES_DIR);
+        mPersister = new TaskSnapshotPersister(mWm, userId -> FILES_DIR);
         mLoader = new TaskSnapshotLoader(mPersister);
         mPersister.start();
     }
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/IntervalStats.java b/services/usage/java/com/android/server/usage/IntervalStats.java
index 9a5bd13..f1ddfe4 100644
--- a/services/usage/java/com/android/server/usage/IntervalStats.java
+++ b/services/usage/java/com/android/server/usage/IntervalStats.java
@@ -218,6 +218,14 @@
                 case (int) IntervalStatsProto.Event.INSTANCE_ID:
                     event.mInstanceId = parser.readInt(IntervalStatsProto.Event.INSTANCE_ID);
                     break;
+                case (int) IntervalStatsProto.Event.TASK_ROOT_PACKAGE_INDEX:
+                    event.mTaskRootPackage = getCachedStringRef(stringPool.get(
+                            parser.readInt(IntervalStatsProto.Event.TASK_ROOT_PACKAGE_INDEX) - 1));
+                    break;
+                case (int) IntervalStatsProto.Event.TASK_ROOT_CLASS_INDEX:
+                    event.mTaskRootClass = getCachedStringRef(stringPool.get(
+                            parser.readInt(IntervalStatsProto.Event.TASK_ROOT_CLASS_INDEX) - 1));
+                    break;
                 case ProtoInputStream.NO_MORE_FIELDS:
                     // Handle default values for certain events types
                     switch (event.mEventType) {
@@ -332,6 +340,12 @@
         if (event.mClass != null) {
             event.mClass = getCachedStringRef(event.mClass);
         }
+        if (event.mTaskRootPackage != null) {
+            event.mTaskRootPackage = getCachedStringRef(event.mTaskRootPackage);
+        }
+        if (event.mTaskRootClass != null) {
+            event.mTaskRootClass = getCachedStringRef(event.mTaskRootClass);
+        }
         if (event.mEventType == NOTIFICATION_INTERRUPTION) {
             event.mNotificationChannelId = getCachedStringRef(event.mNotificationChannelId);
         }
diff --git a/services/usage/java/com/android/server/usage/UsageStatsProto.java b/services/usage/java/com/android/server/usage/UsageStatsProto.java
index d706537..11d49eb 100644
--- a/services/usage/java/com/android/server/usage/UsageStatsProto.java
+++ b/services/usage/java/com/android/server/usage/UsageStatsProto.java
@@ -442,6 +442,28 @@
         proto.write(IntervalStatsProto.Event.FLAGS, event.mFlags);
         proto.write(IntervalStatsProto.Event.TYPE, event.mEventType);
         proto.write(IntervalStatsProto.Event.INSTANCE_ID, event.mInstanceId);
+        if (event.mTaskRootPackage != null) {
+            final int taskRootPackageIndex = stats.mStringCache.indexOf(event.mTaskRootPackage);
+            if (taskRootPackageIndex >= 0) {
+                proto.write(IntervalStatsProto.Event.TASK_ROOT_PACKAGE_INDEX,
+                        taskRootPackageIndex + 1);
+            } else {
+                // Task root package not in Stringpool for some reason.
+                Slog.w(TAG, "Usage event task root package name (" + event.mTaskRootPackage
+                        + ") not found in IntervalStats string cache");
+            }
+        }
+        if (event.mTaskRootClass != null) {
+            final int taskRootClassIndex = stats.mStringCache.indexOf(event.mTaskRootClass);
+            if (taskRootClassIndex >= 0) {
+                proto.write(IntervalStatsProto.Event.TASK_ROOT_CLASS_INDEX,
+                        taskRootClassIndex + 1);
+            } else {
+                // Task root class not in Stringpool for some reason.
+                Slog.w(TAG, "Usage event task root class name (" + event.mTaskRootClass
+                        + ") not found in IntervalStats string cache");
+            }
+        }
         switch (event.mEventType) {
             case UsageEvents.Event.CONFIGURATION_CHANGE:
                 if (event.mConfiguration != null) {
diff --git a/services/usage/java/com/android/server/usage/UsageStatsService.java b/services/usage/java/com/android/server/usage/UsageStatsService.java
index 76a3aa8..85939d4 100644
--- a/services/usage/java/com/android/server/usage/UsageStatsService.java
+++ b/services/usage/java/com/android/server/usage/UsageStatsService.java
@@ -22,6 +22,8 @@
 import static android.app.usage.UsageEvents.Event.FLUSH_TO_DISK;
 import static android.app.usage.UsageEvents.Event.NOTIFICATION_INTERRUPTION;
 import static android.app.usage.UsageEvents.Event.SHORTCUT_INVOCATION;
+import static android.app.usage.UsageStatsManager.USAGE_SOURCE_CURRENT_ACTIVITY;
+import static android.app.usage.UsageStatsManager.USAGE_SOURCE_TASK_ROOT_ACTIVITY;
 
 import android.Manifest;
 import android.app.ActivityManager;
@@ -39,6 +41,7 @@
 import android.app.usage.UsageStats;
 import android.app.usage.UsageStatsManager;
 import android.app.usage.UsageStatsManager.StandbyBuckets;
+import android.app.usage.UsageStatsManager.UsageSource;
 import android.app.usage.UsageStatsManagerInternal;
 import android.content.BroadcastReceiver;
 import android.content.ComponentName;
@@ -65,6 +68,7 @@
 import android.os.SystemProperties;
 import android.os.UserHandle;
 import android.os.UserManager;
+import android.provider.Settings;
 import android.util.ArraySet;
 import android.util.Slog;
 import android.util.SparseArray;
@@ -132,6 +136,7 @@
     private File mUsageStatsDir;
     long mRealTimeSnapshot;
     long mSystemTimeSnapshot;
+    int mUsageSource;
 
     /** Manages the standby state of apps. */
     AppStandbyController mAppStandby;
@@ -258,6 +263,7 @@
             } else {
                 Slog.w(TAG, "Missing procfs interface: " + KERNEL_COUNTER_FILE);
             }
+            readUsageSourceSetting();
         }
     }
 
@@ -268,6 +274,13 @@
         return mDpmInternal;
     }
 
+    private void readUsageSourceSetting() {
+        synchronized (mLock) {
+            mUsageSource = Settings.Global.getInt(getContext().getContentResolver(),
+                    Settings.Global.APP_TIME_LIMIT_USAGE_SOURCE, USAGE_SOURCE_TASK_ROOT_ACTIVITY);
+        }
+    }
+
     private class UserActionsReceiver extends BroadcastReceiver {
         @Override
         public void onReceive(Context context, Intent intent) {
@@ -459,12 +472,28 @@
             service.reportEvent(event);
 
             mAppStandby.reportEvent(event, elapsedRealtime, userId);
+
+            String packageName;
+
+            switch(mUsageSource) {
+                case USAGE_SOURCE_CURRENT_ACTIVITY:
+                    packageName = event.getPackageName();
+                    break;
+                case USAGE_SOURCE_TASK_ROOT_ACTIVITY:
+                default:
+                    packageName = event.getTaskRootPackageName();
+                    if (packageName == null) {
+                        packageName = event.getPackageName();
+                    }
+                    break;
+            }
+
             switch (event.mEventType) {
                 case Event.ACTIVITY_RESUMED:
                     synchronized (mVisibleActivities) {
                         mVisibleActivities.put(event.mInstanceId, event.getClassName());
                         try {
-                            mAppTimeLimit.noteUsageStart(event.getPackageName(), userId);
+                            mAppTimeLimit.noteUsageStart(packageName, userId);
                         } catch (IllegalArgumentException iae) {
                             Slog.e(TAG, "Failed to note usage start", iae);
                         }
@@ -496,7 +525,7 @@
                     synchronized (mVisibleActivities) {
                         if (mVisibleActivities.removeReturnOld(event.mInstanceId) != null) {
                             try {
-                                mAppTimeLimit.noteUsageStop(event.getPackageName(), userId);
+                                mAppTimeLimit.noteUsageStop(packageName, userId);
                             } catch (IllegalArgumentException iae) {
                                 Slog.w(TAG, "Failed to note usage stop", iae);
                             }
@@ -638,7 +667,7 @@
      * Called by the Binder stub.
      */
     UsageEvents queryEventsForPackage(int userId, long beginTime, long endTime,
-            String packageName) {
+            String packageName, boolean includeTaskRoot) {
         synchronized (mLock) {
             final long timeNow = checkAndGetTimeLocked();
             if (!validRange(timeNow, beginTime, endTime)) {
@@ -647,7 +676,7 @@
 
             final UserUsageStatsService service =
                     getUserDataAndInitializeIfNeededLocked(userId, timeNow);
-            return service.queryEventsForPackage(beginTime, endTime, packageName);
+            return service.queryEventsForPackage(beginTime, endTime, packageName, includeTaskRoot);
         }
     }
 
@@ -738,6 +767,10 @@
                 mAppStandby.dumpState(args, pw);
             }
 
+            idpw.println();
+            idpw.printPair("Usage Source", UsageStatsManager.usageSourceToString(mUsageSource));
+            idpw.println();
+
             mAppTimeLimit.dump(null, pw);
         }
     }
@@ -808,7 +841,7 @@
             return mode == AppOpsManager.MODE_ALLOWED;
         }
 
-        private boolean hasObserverPermission(String callingPackage) {
+        private boolean hasObserverPermission() {
             final int callingUid = Binder.getCallingUid();
             DevicePolicyManagerInternal dpmInternal = getDpmInternal();
             if (callingUid == Process.SYSTEM_UID
@@ -822,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;
@@ -939,10 +988,12 @@
             final int callingUserId = UserHandle.getUserId(callingUid);
 
             checkCallerIsSameApp(callingPackage);
+            final boolean includeTaskRoot = hasPermission(callingPackage);
+
             final long token = Binder.clearCallingIdentity();
             try {
                 return UsageStatsService.this.queryEventsForPackage(callingUserId, beginTime,
-                        endTime, callingPackage);
+                        endTime, callingPackage, includeTaskRoot);
             } finally {
                 Binder.restoreCallingIdentity(token);
             }
@@ -989,7 +1040,7 @@
             final long token = Binder.clearCallingIdentity();
             try {
                 return UsageStatsService.this.queryEventsForPackage(userId, beginTime,
-                        endTime, pkg);
+                        endTime, pkg, true);
             } finally {
                 Binder.restoreCallingIdentity(token);
             }
@@ -1229,7 +1280,7 @@
         public void registerAppUsageObserver(int observerId,
                 String[] packages, long timeLimitMs, PendingIntent
                 callbackIntent, String callingPackage) {
-            if (!hasObserverPermission(callingPackage)) {
+            if (!hasObserverPermission()) {
                 throw new SecurityException("Caller doesn't have OBSERVE_APP_USAGE permission");
             }
 
@@ -1252,7 +1303,7 @@
 
         @Override
         public void unregisterAppUsageObserver(int observerId, String callingPackage) {
-            if (!hasObserverPermission(callingPackage)) {
+            if (!hasObserverPermission()) {
                 throw new SecurityException("Caller doesn't have OBSERVE_APP_USAGE permission");
             }
 
@@ -1271,7 +1322,7 @@
                 long timeLimitMs, long sessionThresholdTimeMs,
                 PendingIntent limitReachedCallbackIntent, PendingIntent sessionEndCallbackIntent,
                 String callingPackage) {
-            if (!hasObserverPermission(callingPackage)) {
+            if (!hasObserverPermission()) {
                 throw new SecurityException("Caller doesn't have OBSERVE_APP_USAGE permission");
             }
 
@@ -1295,7 +1346,7 @@
 
         @Override
         public void unregisterUsageSessionObserver(int sessionObserverId, String callingPackage) {
-            if (!hasObserverPermission(callingPackage)) {
+            if (!hasObserverPermission()) {
                 throw new SecurityException("Caller doesn't have OBSERVE_APP_USAGE permission");
             }
 
@@ -1311,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);
         }
@@ -1373,6 +1469,21 @@
                 Binder.restoreCallingIdentity(binderToken);
             }
         }
+
+        @Override
+        public @UsageSource int getUsageSource() {
+            if (!hasObserverPermission()) {
+                throw new SecurityException("Caller doesn't have OBSERVE_APP_USAGE permission");
+            }
+            synchronized (mLock) {
+                return mUsageSource;
+            }
+        }
+
+        @Override
+        public void forceUsageSourceSettingRead() {
+            readUsageSourceSetting();
+        }
     }
 
     void registerAppUsageObserver(int callingUid, int observerId, String[] packages,
@@ -1397,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
@@ -1406,7 +1527,7 @@
 
         @Override
         public void reportEvent(ComponentName component, int userId, int eventType,
-                int instanceId) {
+                int instanceId, ComponentName taskRoot) {
             if (component == null) {
                 Slog.w(TAG, "Event reported without a component name");
                 return;
@@ -1416,6 +1537,13 @@
             event.mPackage = component.getPackageName();
             event.mClass = component.getClassName();
             event.mInstanceId = instanceId;
+            if (taskRoot == null) {
+                event.mTaskRootPackage = null;
+                event.mTaskRootClass = null;
+            } else {
+                event.mTaskRootPackage = taskRoot.getPackageName();
+                event.mTaskRootClass = taskRoot.getClassName();
+            }
             mHandler.obtainMessage(MSG_REPORT_EVENT, userId, 0, event).sendToTarget();
         }
 
@@ -1595,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/usage/java/com/android/server/usage/UserUsageStatsService.java b/services/usage/java/com/android/server/usage/UserUsageStatsService.java
index 2d1098c7..d52d32f 100644
--- a/services/usage/java/com/android/server/usage/UserUsageStatsService.java
+++ b/services/usage/java/com/android/server/usage/UserUsageStatsService.java
@@ -401,7 +401,7 @@
     }
 
     UsageEvents queryEvents(final long beginTime, final long endTime,
-            boolean obfuscateInstantApps) {
+                            boolean obfuscateInstantApps) {
         final ArraySet<String> names = new ArraySet<>();
         List<Event> results = queryStats(INTERVAL_DAILY,
                 beginTime, endTime, new StatCombiner<Event>() {
@@ -425,6 +425,12 @@
                             if (event.mClass != null) {
                                 names.add(event.mClass);
                             }
+                            if (event.mTaskRootPackage != null) {
+                                names.add(event.mTaskRootPackage);
+                            }
+                            if (event.mTaskRootClass != null) {
+                                names.add(event.mTaskRootClass);
+                            }
                             accumulatedResult.add(event);
                         }
                     }
@@ -436,11 +442,11 @@
 
         String[] table = names.toArray(new String[names.size()]);
         Arrays.sort(table);
-        return new UsageEvents(results, table);
+        return new UsageEvents(results, table, true);
     }
 
     UsageEvents queryEventsForPackage(final long beginTime, final long endTime,
-            final String packageName) {
+            final String packageName, boolean includeTaskRoot) {
         final ArraySet<String> names = new ArraySet<>();
         names.add(packageName);
         final List<Event> results = queryStats(INTERVAL_DAILY,
@@ -459,6 +465,12 @@
                         if (event.mClass != null) {
                             names.add(event.mClass);
                         }
+                        if (includeTaskRoot && event.mTaskRootPackage != null) {
+                            names.add(event.mTaskRootPackage);
+                        }
+                        if (includeTaskRoot && event.mTaskRootClass != null) {
+                            names.add(event.mTaskRootClass);
+                        }
                         accumulatedResult.add(event);
                     }
                 });
@@ -469,7 +481,7 @@
 
         final String[] table = names.toArray(new String[names.size()]);
         Arrays.sort(table);
-        return new UsageEvents(results, table);
+        return new UsageEvents(results, table, includeTaskRoot);
     }
 
     void persistActiveStats() {
@@ -684,6 +696,14 @@
             pw.printPair("instanceId", event.getInstanceId());
         }
 
+        if (event.getTaskRootPackageName() != null) {
+            pw.printPair("taskRootPackage", event.getTaskRootPackageName());
+        }
+
+        if (event.getTaskRootClassName() != null) {
+            pw.printPair("taskRootClass", event.getTaskRootClassName());
+        }
+
         if (event.mNotificationChannelId != null) {
             pw.printPair("channelId", event.mNotificationChannelId);
         }
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 bfe96d3..5cf9582 100644
--- a/startop/OWNERS
+++ b/startop/OWNERS
@@ -2,4 +2,5 @@
 chriswailes@google.com
 eholk@google.com
 iam@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/Conference.java b/telecomm/java/android/telecom/Conference.java
index a39e885..7d1f8ce 100644
--- a/telecomm/java/android/telecom/Conference.java
+++ b/telecomm/java/android/telecom/Conference.java
@@ -19,6 +19,7 @@
 import android.annotation.NonNull;
 import android.annotation.Nullable;
 import android.annotation.SystemApi;
+import android.net.Uri;
 import android.os.Bundle;
 import android.os.SystemClock;
 import android.telecom.Connection.VideoProvider;
@@ -64,6 +65,10 @@
         public void onStatusHintsChanged(Conference conference, StatusHints statusHints) {}
         public void onExtrasChanged(Conference c, Bundle extras) {}
         public void onExtrasRemoved(Conference c, List<String> keys) {}
+        public void onConferenceStateChanged(Conference c, boolean isConference) {}
+        public void onAddressChanged(Conference c, Uri newAddress, int presentation) {}
+        public void onCallerDisplayNameChanged(
+                Conference c, String callerDisplayName, int presentation) {}
     }
 
     private final Set<Listener> mListeners = new CopyOnWriteArraySet<>();
@@ -946,6 +951,62 @@
     public void onExtrasChanged(Bundle extras) {}
 
     /**
+     * Set whether Telecom should treat this {@link Conference} as a conference call or if it
+     * should treat it as a single-party call.
+     * This method is used as part of a workaround regarding IMS conference calls and user
+     * expectation.  In IMS, once a conference is formed, the UE is connected to an IMS conference
+     * server.  If all participants of the conference drop out of the conference except for one, the
+     * UE is still connected to the IMS conference server.  At this point, the user logically
+     * assumes they're no longer in a conference, yet the underlying network actually is.
+     * To help provide a better user experiece, IMS conference calls can pretend to actually be a
+     * single-party call when the participant count drops to 1.  Although the dialer/phone app
+     * could perform this trickery, it makes sense to do this in Telephony since a fix there will
+     * ensure that bluetooth head units, auto and wearable apps all behave consistently.
+     *
+     * @param isConference {@code true} if this {@link Conference} should be treated like a
+     *      conference call, {@code false} if it should be treated like a single-party call.
+     * @hide
+     */
+    public void setConferenceState(boolean isConference) {
+        for (Listener l : mListeners) {
+            l.onConferenceStateChanged(this, isConference);
+        }
+    }
+
+    /**
+     * Sets the address of this {@link Conference}.  Used when {@link #setConferenceState(boolean)}
+     * is called to mark a conference temporarily as NOT a conference.
+     *
+     * @param address The new address.
+     * @param presentation The presentation requirements for the address.
+     *        See {@link TelecomManager} for valid values.
+     * @hide
+     */
+    public final void setAddress(Uri address, int presentation) {
+        Log.d(this, "setAddress %s", address);
+        for (Listener l : mListeners) {
+            l.onAddressChanged(this, address, presentation);
+        }
+    }
+
+    /**
+     * Sets the caller display name (CNAP) of this {@link Conference}.  Used when
+     * {@link #setConferenceState(boolean)} is called to mark a conference temporarily as NOT a
+     * conference.
+     *
+     * @param callerDisplayName The new display name.
+     * @param presentation The presentation requirements for the handle.
+     *        See {@link TelecomManager} for valid values.
+     * @hide
+     */
+    public final void setCallerDisplayName(String callerDisplayName, int presentation) {
+        Log.d(this, "setCallerDisplayName %s", callerDisplayName);
+        for (Listener l : mListeners) {
+            l.onCallerDisplayNameChanged(this, callerDisplayName, presentation);
+        }
+    }
+
+    /**
      * Handles a change to extras received from Telecom.
      *
      * @param extras The new extras.
diff --git a/telecomm/java/android/telecom/ConnectionService.java b/telecomm/java/android/telecom/ConnectionService.java
index 4d5f5e1..9bafbe0 100644
--- a/telecomm/java/android/telecom/ConnectionService.java
+++ b/telecomm/java/android/telecom/ConnectionService.java
@@ -1254,6 +1254,31 @@
                 mAdapter.removeExtras(id, keys);
             }
         }
+
+        @Override
+        public void onConferenceStateChanged(Conference c, boolean isConference) {
+            String id = mIdByConference.get(c);
+            if (id != null) {
+                mAdapter.setConferenceState(id, isConference);
+            }
+        }
+
+        @Override
+        public void onAddressChanged(Conference c, Uri newAddress, int presentation) {
+            String id = mIdByConference.get(c);
+            if (id != null) {
+                mAdapter.setAddress(id, newAddress, presentation);
+            }
+        }
+
+        @Override
+        public void onCallerDisplayNameChanged(Conference c, String callerDisplayName,
+                int presentation) {
+            String id = mIdByConference.get(c);
+            if (id != null) {
+                mAdapter.setCallerDisplayName(id, callerDisplayName, presentation);
+            }
+        }
     };
 
     private final Connection.Listener mConnectionListener = new Connection.Listener() {
diff --git a/telecomm/java/android/telecom/ConnectionServiceAdapter.java b/telecomm/java/android/telecom/ConnectionServiceAdapter.java
index 520e7ed..6c3f4f3 100644
--- a/telecomm/java/android/telecom/ConnectionServiceAdapter.java
+++ b/telecomm/java/android/telecom/ConnectionServiceAdapter.java
@@ -653,4 +653,22 @@
             }
         }
     }
+
+    /**
+     * Sets whether a conference is treated as a conference or a single party call.
+     * See {@link Conference#setConferenceState(boolean)} for more information.
+     *
+     * @param callId The ID of the telecom call.
+     * @param isConference {@code true} if this call should be treated as a conference,
+     * {@code false} otherwise.
+     */
+    void setConferenceState(String callId, boolean isConference) {
+        Log.v(this, "setConferenceState: %s %b", callId, isConference);
+        for (IConnectionServiceAdapter adapter : mAdapters) {
+            try {
+                adapter.setConferenceState(callId, isConference, Log.getExternalSession());
+            } catch (RemoteException ignored) {
+            }
+        }
+    }
 }
diff --git a/telecomm/java/android/telecom/ConnectionServiceAdapterServant.java b/telecomm/java/android/telecom/ConnectionServiceAdapterServant.java
index 78d65e6..f99b218 100644
--- a/telecomm/java/android/telecom/ConnectionServiceAdapterServant.java
+++ b/telecomm/java/android/telecom/ConnectionServiceAdapterServant.java
@@ -74,6 +74,7 @@
     private static final int MSG_ON_RTT_UPGRADE_REQUEST = 33;
     private static final int MSG_SET_PHONE_ACCOUNT_CHANGED = 34;
     private static final int MSG_CONNECTION_SERVICE_FOCUS_RELEASED = 35;
+    private static final int MSG_SET_CONFERENCE_STATE = 36;
 
     private final IConnectionServiceAdapter mDelegate;
 
@@ -333,6 +334,14 @@
                 case MSG_CONNECTION_SERVICE_FOCUS_RELEASED:
                     mDelegate.onConnectionServiceFocusReleased(null /*Session.Info*/);
                     break;
+                case MSG_SET_CONFERENCE_STATE:
+                    SomeArgs args = (SomeArgs) msg.obj;
+                    try {
+                        mDelegate.setConferenceState((String) args.arg1, (Boolean) args.arg2,
+                                (Session.Info) args.arg3);
+                    } finally {
+                        args.recycle();
+                    }
             }
         }
     };
@@ -615,6 +624,16 @@
         public void resetConnectionTime(String callId, Session.Info sessionInfo) {
             // Do nothing
         }
+
+        @Override
+        public void setConferenceState(String callId, boolean isConference,
+                Session.Info sessionInfo) {
+            SomeArgs args = SomeArgs.obtain();
+            args.arg1 = callId;
+            args.arg2 = isConference;
+            args.arg3 = sessionInfo;
+            mHandler.obtainMessage(MSG_SET_CONFERENCE_STATE, args).sendToTarget();
+        }
     };
 
     public ConnectionServiceAdapterServant(IConnectionServiceAdapter delegate) {
diff --git a/telecomm/java/android/telecom/RemoteConnectionService.java b/telecomm/java/android/telecom/RemoteConnectionService.java
index 9821dcb..744544e 100644
--- a/telecomm/java/android/telecom/RemoteConnectionService.java
+++ b/telecomm/java/android/telecom/RemoteConnectionService.java
@@ -471,6 +471,12 @@
         public void resetConnectionTime(String callId, Session.Info sessionInfo) {
             // Do nothing
         }
+
+        @Override
+        public void setConferenceState(String callId, boolean isConference,
+                Session.Info sessionInfo) {
+            // Do nothing
+        }
     };
 
     private final ConnectionServiceAdapterServant mServant =
diff --git a/telecomm/java/android/telecom/TelecomManager.java b/telecomm/java/android/telecom/TelecomManager.java
index 6c4b1af8..12a5344 100644
--- a/telecomm/java/android/telecom/TelecomManager.java
+++ b/telecomm/java/android/telecom/TelecomManager.java
@@ -793,15 +793,17 @@
      * <p>
      * Apps must be prepared for this method to return {@code null}, indicating that there currently
      * exists no user-chosen default {@code PhoneAccount}.
+     * <p>
+     * The default dialer has access to use this method.
      *
      * @return The user outgoing phone account selected by the user.
-     * @hide
      */
-    @UnsupportedAppUsage
+    @RequiresPermission(android.Manifest.permission.READ_PHONE_STATE)
     public PhoneAccountHandle getUserSelectedOutgoingPhoneAccount() {
         try {
             if (isServiceConnected()) {
-                return getTelecomService().getUserSelectedOutgoingPhoneAccount();
+                return getTelecomService().getUserSelectedOutgoingPhoneAccount(
+                        mContext.getOpPackageName());
             }
         } catch (RemoteException e) {
             Log.e(TAG, "Error calling ITelecomService#getUserSelectedOutgoingPhoneAccount", e);
@@ -810,10 +812,14 @@
     }
 
     /**
-     * Sets the user-chosen default for making outgoing phone calls.
+     * Sets the user-chosen default {@link PhoneAccountHandle} for making outgoing phone calls.
+     *
+     * @param accountHandle The {@link PhoneAccountHandle} which will be used by default for making
+     *                      outgoing voice calls.
      * @hide
      */
-    @UnsupportedAppUsage
+    @RequiresPermission(android.Manifest.permission.MODIFY_PHONE_STATE)
+    @SystemApi
     public void setUserSelectedOutgoingPhoneAccount(PhoneAccountHandle accountHandle) {
         try {
             if (isServiceConnected()) {
@@ -1964,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/IConnectionServiceAdapter.aidl b/telecomm/java/com/android/internal/telecom/IConnectionServiceAdapter.aidl
index 0157a58..76ac88e 100644
--- a/telecomm/java/com/android/internal/telecom/IConnectionServiceAdapter.aidl
+++ b/telecomm/java/com/android/internal/telecom/IConnectionServiceAdapter.aidl
@@ -123,4 +123,6 @@
     void onConnectionServiceFocusReleased(in Session.Info sessionInfo);
 
     void resetConnectionTime(String callIdi, in Session.Info sessionInfo);
+
+    void setConferenceState(String callId, boolean isConference, in Session.Info sessionInfo);
 }
diff --git a/telecomm/java/com/android/internal/telecom/ITelecomService.aidl b/telecomm/java/com/android/internal/telecom/ITelecomService.aidl
index 954a709..5030f90 100644
--- a/telecomm/java/com/android/internal/telecom/ITelecomService.aidl
+++ b/telecomm/java/com/android/internal/telecom/ITelecomService.aidl
@@ -45,7 +45,7 @@
     /**
      * @see TelecomServiceImpl#getUserSelectedOutgoingPhoneAccount
      */
-    PhoneAccountHandle getUserSelectedOutgoingPhoneAccount();
+    PhoneAccountHandle getUserSelectedOutgoingPhoneAccount(String callingPackage);
 
     /**
      * @see TelecomServiceImpl#setUserSelectedOutgoingPhoneAccount
@@ -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 312b318..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.
@@ -2508,10 +2538,18 @@
         public static final String KEY_GPS_LOCK_STRING = KEY_PREFIX + "gps_lock";
 
         /**
-         * SUPL NI emergency extension time in seconds. Default to "0".
+         * Control Plane / SUPL NI emergency extension time in seconds. Default to "0".
          */
         public static final String KEY_ES_EXTENSION_SEC = KEY_PREFIX + "es_extension_sec";
 
+        /**
+         * Space separated list of Android package names of proxy applications representing
+         * the non-framework entities requesting location directly from GNSS without involving
+         * the framework, as managed by IGnssVisibilityControl.hal. For example,
+         * "com.example.mdt com.example.ims".
+         */
+        public static final String KEY_NFW_PROXY_APPS = KEY_PREFIX + "nfw_proxy_apps";
+
         private static PersistableBundle getDefaults() {
             PersistableBundle defaults = new PersistableBundle();
             defaults.putBoolean(KEY_PERSIST_LPP_MODE_BOOL, true);
@@ -2525,6 +2563,7 @@
             defaults.putString(KEY_A_GLONASS_POS_PROTOCOL_SELECT_STRING, "0");
             defaults.putString(KEY_GPS_LOCK_STRING, "3");
             defaults.putString(KEY_ES_EXTENSION_SEC, "0");
+            defaults.putString(KEY_NFW_PROXY_APPS, "");
             return defaults;
         }
     }
@@ -2566,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/CellSignalStrengthGsm.java b/telephony/java/android/telephony/CellSignalStrengthGsm.java
index 7b29f69..30e641d 100644
--- a/telephony/java/android/telephony/CellSignalStrengthGsm.java
+++ b/telephony/java/android/telephony/CellSignalStrengthGsm.java
@@ -148,8 +148,9 @@
 
     /**
      * Return the Bit Error Rate
-     * @returns the bit error rate (0-7, 99) as defined in TS 27.007 8.5 or UNAVAILABLE.
-     * @hide
+     *
+     * @return the bit error rate (0-7, 99) as defined in TS 27.007 8.5 or
+     *         {@link android.telephony.CellInfo#UNAVAILABLE UNAVAILABLE}.
      */
     public int getBitErrorRate() {
         return mBitErrorRate;
diff --git a/telephony/java/android/telephony/DataFailCause.java b/telephony/java/android/telephony/DataFailCause.java
index 26ec6de..ca264f7 100644
--- a/telephony/java/android/telephony/DataFailCause.java
+++ b/telephony/java/android/telephony/DataFailCause.java
@@ -42,7 +42,7 @@
 
     // This series of errors as specified by the standards
     // specified in ril.h
-    /** Operator determined barring. */
+    /** Operator determined barring. (no retry) */
     public static final int OPERATOR_BARRED = 0x08;
     /** NAS signalling. */
     public static final int NAS_SIGNALLING = 0x0E;
@@ -91,6 +91,11 @@
     public static final int FILTER_SYTAX_ERROR = 0x2D;
     /** Packet Data Protocol (PDP) without active traffic flow template (TFT). */
     public static final int PDP_WITHOUT_ACTIVE_TFT = 0x2E;
+    /**
+     * UE requested to modify QoS parameters or the bearer control mode, which is not compatible
+     * with the selected bearer control mode.
+     */
+    public static final int ACTIVATION_REJECTED_BCM_VIOLATION = 0x30;
     /** Packet Data Protocol (PDP) type IPv4 only allowed. */
     public static final int ONLY_IPV4_ALLOWED = 0x32;                /* no retry */
     /** Packet Data Protocol (PDP) type IPv6 only allowed. */
@@ -103,6 +108,27 @@
     public static final int PDN_CONN_DOES_NOT_EXIST = 0x36;
     /** Multiple connections to a same PDN is not allowed. */
     public static final int MULTI_CONN_TO_SAME_PDN_NOT_ALLOWED = 0x37;
+    /**
+     * Network has already initiated the activation, modification, or deactivation of bearer
+     * resources that was requested by the UE.
+     */
+    public static final int COLLISION_WITH_NETWORK_INITIATED_REQUEST = 0x38;
+    /**
+     * Network supports IPv4v6 PDP type only. Non-IP type is not allowed. In LTE mode of operation,
+     * this is a PDN throttling cause code, meaning the UE may throttle further requests to the
+     * same APN.
+     */
+    public static final int ONLY_IPV4V6_ALLOWED = 0x39;
+    /**
+     * Network supports non-IP PDP type only. IPv4, IPv6 and IPv4v6 is not allowed. In LTE mode of
+     * operation, this is a PDN throttling cause code, meaning the UE can throttle further requests
+     * to the same APN.
+     */
+    public static final int ONLY_NON_IP_ALLOWED = 0x3A;
+    /** QCI (QoS Class Identifier) indicated in the UE request cannot be supported. */
+    public static final int UNSUPPORTED_QCI_VALUE = 0x3B;
+    /** Procedure requested by the UE was rejected because the bearer handling is not supported. */
+    public static final int BEARER_HANDLING_NOT_SUPPORTED = 0x3C;
     /** Max number of Packet Data Protocol (PDP) context reached. */
     public static final int ACTIVE_PDP_CONTEXT_MAX_NUMBER_REACHED = 0x41;
     /** Unsupported APN in current public land mobile network (PLMN). */
@@ -146,6 +172,742 @@
     public static final int EMM_ACCESS_BARRED_INFINITE_RETRY = 0x79;
     /** Authentication failure on emergency call. */
     public static final int AUTH_FAILURE_ON_EMERGENCY_CALL = 0x7A;
+    /** Not receiving a DNS address that was mandatory. */
+    public static final int INVALID_DNS_ADDR = 0x7B;
+    /** Not receiving either a PCSCF or a DNS address, one of them being mandatory. */
+    public static final int INVALID_PCSCF_OR_DNS_ADDRESS = 0x7C;
+    /** Emergency call bring up on a different ePDG. */
+    public static final int CALL_PREEMPT_BY_EMERGENCY_APN = 0x7F;
+    /** UE performs a detach or disconnect PDN action based on TE requirements. */
+    public static final int UE_INITIATED_DETACH_OR_DISCONNECT = 0x80;
+
+    /** Reason unspecified for foreign agent rejected MIP (Mobile IP) registration. */
+    public static final int MIP_FA_REASON_UNSPECIFIED = 0x7D0;
+    /** Foreign agent administratively prohibited MIP (Mobile IP) registration. */
+    public static final int MIP_FA_ADMIN_PROHIBITED = 0x7D1;
+    /** Foreign agent rejected MIP (Mobile IP) registration because of insufficient resources. */
+    public static final int MIP_FA_INSUFFICIENT_RESOURCES = 0x7D2;
+    /**
+     * Foreign agent rejected MIP (Mobile IP) registration because of MN-AAA authenticator was
+     * wrong.
+     */
+    public static final int MIP_FA_MOBILE_NODE_AUTHENTICATION_FAILURE = 0x7D3;
+    /**
+     * Foreign agent rejected MIP (Mobile IP) registration because of home agent authentication
+     * failure.
+     */
+    public static final int MIP_FA_HOME_AGENT_AUTHENTICATION_FAILURE = 0x7D4;
+    /**
+     * Foreign agent rejected MIP (Mobile IP) registration because of requested lifetime was too
+     * long.
+     */
+    public static final int MIP_FA_REQUESTED_LIFETIME_TOO_LONG = 0x7D5;
+    /** Foreign agent rejected MIP (Mobile IP) registration because of malformed request. */
+    public static final int MIP_FA_MALFORMED_REQUEST = 0x7D6;
+    /** Foreign agent rejected MIP (Mobile IP) registration because of malformed reply. */
+    public static final int MIP_FA_MALFORMED_REPLY = 0x7D7;
+    /**
+     * Foreign agent rejected MIP (Mobile IP) registration because of requested encapsulation was
+     * unavailable.
+     */
+    public static final int MIP_FA_ENCAPSULATION_UNAVAILABLE = 0x7D8;
+    /**
+     * Foreign agent rejected MIP (Mobile IP) registration of VJ Header Compression was
+     * unavailable.
+     */
+    public static final int MIP_FA_VJ_HEADER_COMPRESSION_UNAVAILABLE = 0x7D9;
+    /**
+     * Foreign agent rejected MIP (Mobile IP) registration because of reverse tunnel was
+     * unavailable.
+     */
+    public static final int MIP_FA_REVERSE_TUNNEL_UNAVAILABLE = 0x7DA;
+    /**
+     * Foreign agent rejected MIP (Mobile IP) registration because of reverse tunnel was mandatory
+     * but not requested by device.
+     */
+    public static final int MIP_FA_REVERSE_TUNNEL_IS_MANDATORY = 0x7DB;
+    /**
+     * Foreign agent rejected MIP (Mobile IP) registration because of delivery style was not
+     * supported.
+     */
+    public static final int MIP_FA_DELIVERY_STYLE_NOT_SUPPORTED = 0x7DC;
+    /**
+     * Foreign agent rejected MIP (Mobile IP) registration because of missing NAI (Network Access
+     * Identifier).
+     */
+    public static final int MIP_FA_MISSING_NAI = 0x7DD;
+    /** Foreign agent rejected MIP (Mobile IP) registration because of missing Home Agent. */
+    public static final int MIP_FA_MISSING_HOME_AGENT = 0x7DE;
+    /** Foreign agent rejected MIP (Mobile IP) registration because of missing Home Address. */
+    public static final int MIP_FA_MISSING_HOME_ADDRESS = 0x7DF;
+    /** Foreign agent rejected MIP (Mobile IP) registration because of unknown challenge. */
+    public static final int MIP_FA_UNKNOWN_CHALLENGE = 0x7E0;
+    /** Foreign agent rejected MIP (Mobile IP) registration because of missing challenge. */
+    public static final int MIP_FA_MISSING_CHALLENGE = 0x7E1;
+    /** Foreign agent rejected MIP (Mobile IP) registration because of stale challenge. */
+    public static final int MIP_FA_STALE_CHALLENGE = 0x7E2;
+    /** Reason unspecified for home agent rejected MIP (Mobile IP) registration. */
+    public static final int MIP_HA_REASON_UNSPECIFIED = 0x7E3;
+    /** Home agent administratively prohibited MIP (Mobile IP) registration. */
+    public static final int MIP_HA_ADMIN_PROHIBITED = 0x7E4;
+    /** Home agent rejected MIP (Mobile IP) registration because of insufficient resources. */
+    public static final int MIP_HA_INSUFFICIENT_RESOURCES = 0x7E5;
+    /**
+     * Home agent rejected MIP (Mobile IP) registration because of MN-HA authenticator was
+     * wrong.
+     */
+    public static final int MIP_HA_MOBILE_NODE_AUTHENTICATION_FAILURE = 0x7E6;
+    /**
+     * Home agent rejected MIP (Mobile IP) registration because of foreign agent authentication
+     * failure.
+     */
+    public static final int MIP_HA_FOREIGN_AGENT_AUTHENTICATION_FAILURE = 0x7E7;
+    /** Home agent rejected MIP (Mobile IP) registration because of registration id mismatch. */
+    public static final int MIP_HA_REGISTRATION_ID_MISMATCH = 0x7E8;
+    /** Home agent rejected MIP (Mobile IP) registration because of malformed request. */
+    public static final int MIP_HA_MALFORMED_REQUEST = 0x7E9;
+    /** Home agent rejected MIP (Mobile IP) registration because of unknown home agent address. */
+    public static final int MIP_HA_UNKNOWN_HOME_AGENT_ADDRESS = 0x7EA;
+    /**
+     * Home agent rejected MIP (Mobile IP) registration because of reverse tunnel was
+     * unavailable.
+     */
+    public static final int MIP_HA_REVERSE_TUNNEL_UNAVAILABLE = 0x7EB;
+    /**
+     * Home agent rejected MIP (Mobile IP) registration because of reverse tunnel is mandatory but
+     * not requested by device.
+     */
+    public static final int MIP_HA_REVERSE_TUNNEL_IS_MANDATORY = 0x7EC;
+    /** Home agent rejected MIP (Mobile IP) registration because of encapsulation unavailable. */
+    public static final int MIP_HA_ENCAPSULATION_UNAVAILABLE = 0x7ED;
+    /** Tearing down is in progress. */
+    public static final int CLOSE_IN_PROGRESS = 0x7EE;
+    /** Brought down by the network. */
+    public static final int NETWORK_INITIATED_TERMINATION = 0x7EF;
+    /** Another application in modem preempts the data call. */
+    public static final int MODEM_APP_PREEMPTED = 0x7F0;
+    /**
+     * IPV4 PDN is in throttled state due to network providing only IPV6 address during the
+     * previous VSNCP bringup (subs_limited_to_v6).
+     */
+    public static final int PDN_IPV4_CALL_DISALLOWED = 0x7F1;
+    /** IPV4 PDN is in throttled state due to previous VSNCP bringup failure(s). */
+    public static final int PDN_IPV4_CALL_THROTTLED = 0x7F2;
+    /**
+     * IPV6 PDN is in throttled state due to network providing only IPV4 address during the
+     * previous VSNCP bringup (subs_limited_to_v4).
+     */
+    public static final int PDN_IPV6_CALL_DISALLOWED = 0x7F3;
+    /** IPV6 PDN is in throttled state due to previous VSNCP bringup failure(s). */
+    public static final int PDN_IPV6_CALL_THROTTLED = 0x7F4;
+    /** Modem restart. */
+    public static final int MODEM_RESTART = 0x7F5;
+    /** PDP PPP calls are not supported. */
+    public static final int PDP_PPP_NOT_SUPPORTED = 0x7F6;
+    /** RAT on which the data call is attempted/connected is no longer the preferred RAT. */
+    public static final int UNPREFERRED_RAT = 0x7F7;
+    /** Physical link is in the process of cleanup. */
+    public static final int PHYSICAL_LINK_CLOSE_IN_PROGRESS = 0x7F8;
+    /** Interface bring up is attempted for an APN that is yet to be handed over to target RAT. */
+    public static final int APN_PENDING_HANDOVER = 0x7F9;
+    /** APN bearer type in the profile does not match preferred network mode. */
+    public static final int PROFILE_BEARER_INCOMPATIBLE = 0x7FA;
+    /** Card was refreshed or removed. */
+    public static final int SIM_CARD_CHANGED = 0x7FB;
+    /** Device is going into lower power mode or powering down. */
+    public static final int LOW_POWER_MODE_OR_POWERING_DOWN = 0x7FC;
+    /** APN has been disabled. */
+    public static final int APN_DISABLED = 0x7FD;
+    /** Maximum PPP inactivity timer expired. */
+    public static final int MAX_PPP_INACTIVITY_TIMER_EXPIRED = 0x7FE;
+    /** IPv6 address transfer failed. */
+    public static final int IPV6_ADDRESS_TRANSFER_FAILED = 0x7FF;
+    /** Target RAT swap failed. */
+    public static final int TRAT_SWAP_FAILED = 0x800;
+    /** Device falls back from eHRPD to HRPD. */
+    public static final int EHRPD_TO_HRPD_FALLBACK = 0x801;
+    /**
+     * UE is in MIP-only configuration but the MIP configuration fails on call bring up due to
+     * incorrect provisioning.
+     */
+    public static final int MIP_CONFIG_FAILURE = 0x802;
+    /**
+     * PDN inactivity timer expired due to no data transmission in a configurable duration of time.
+     */
+    public static final int PDN_INACTIVITY_TIMER_EXPIRED = 0x803;
+    /**
+     * IPv4 data call bring up is rejected because the UE already maintains the allotted maximum
+     * number of IPv4 data connections.
+     */
+    public static final int MAX_IPV4_CONNECTIONS = 0x804;
+    /**
+     * IPv6 data call bring up is rejected because the UE already maintains the allotted maximum
+     * number of IPv6 data connections.
+     */
+    public static final int MAX_IPV6_CONNECTIONS = 0x805;
+    /**
+     * New PDN bring up is rejected during interface selection because the UE has already allotted
+     * the available interfaces for other PDNs.
+     */
+    public static final int APN_MISMATCH = 0x806;
+    /**
+     * New call bring up is rejected since the existing data call IP type doesn't match the
+     * requested IP.
+     */
+    public static final int IP_VERSION_MISMATCH = 0x807;
+    /** Dial up networking (DUN) call bring up is rejected since UE is in eHRPD RAT. */
+    public static final int DUN_CALL_DISALLOWED = 0x808;
+    /*** Rejected/Brought down since UE is transition between EPC and NONEPC RAT. */
+    public static final int INTERNAL_EPC_NONEPC_TRANSITION = 0x809;
+    /** The current interface is being in use. */
+    public static final int INTERFACE_IN_USE = 0x80A;
+    /** PDN connection to the APN is disallowed on the roaming network. */
+    public static final int APN_DISALLOWED_ON_ROAMING = 0x80B;
+    /** APN-related parameters are changed. */
+    public static final int APN_PARAMETERS_CHANGED = 0x80C;
+    /** PDN is attempted to be brought up with NULL APN but NULL APN is not supported. */
+    public static final int NULL_APN_DISALLOWED = 0x80D;
+    /**
+     * Thermal level increases and causes calls to be torn down when normal mode of operation is
+     * not allowed.
+     */
+    public static final int THERMAL_MITIGATION = 0x80E;
+    /**
+     * PDN Connection to a given APN is disallowed because data is disabled from the device user
+     * interface settings.
+     */
+    public static final int DATA_SETTINGS_DISABLED = 0x80F;
+    /**
+     * PDN Connection to a given APN is disallowed because data roaming is disabled from the device
+     * user interface settings and the UE is roaming.
+     */
+    public static final int DATA_ROAMING_SETTINGS_DISABLED = 0x810;
+    /** DDS (Default data subscription) switch occurs. */
+    public static final int DDS_SWITCHED = 0x811;
+    /** PDN being brought up with an APN that is part of forbidden APN Name list. */
+    public static final int FORBIDDEN_APN_NAME = 0x812;
+    /** Default data subscription switch is in progress. */
+    public static final int DDS_SWITCH_IN_PROGRESS = 0x813;
+    /** Roaming is disallowed during call bring up. */
+    public static final int CALL_DISALLOWED_IN_ROAMING = 0x814;
+    /**
+     * UE is unable to bring up a non-IP data call because the device is not camped on a NB1 cell.
+     */
+    public static final int NON_IP_NOT_SUPPORTED = 0x815;
+    /** Non-IP PDN is in throttled state due to previous VSNCP bringup failure(s). */
+    public static final int PDN_NON_IP_CALL_THROTTLED = 0x816;
+    /** Non-IP PDN is in disallowed state due to the network providing only an IP address. */
+    public static final int PDN_NON_IP_CALL_DISALLOWED = 0x817;
+    /** Device in CDMA locked state. */
+    public static final int CDMA_LOCK = 0x818;
+    /** Received an intercept order from the base station. */
+    public static final int CDMA_INTERCEPT = 0x819;
+    /** Receiving a reorder from the base station. */
+    public static final int CDMA_REORDER = 0x81A;
+    /** Receiving a release from the base station with a SO (Service Option) Reject reason. */
+    public static final int CDMA_RELEASE_DUE_TO_SO_REJECTION = 0x81B;
+    /** Receiving an incoming call from the base station. */
+    public static final int CDMA_INCOMING_CALL = 0x81C;
+    /** Received an alert stop from the base station due to incoming only. */
+    public static final int CDMA_ALERT_STOP = 0x81D;
+    /**
+     * Channel acquisition failures. This indicates that device has failed acquiring all the
+     * channels in the PRL.
+     */
+    public static final int CHANNEL_ACQUISITION_FAILURE = 0x81E;
+    /** Maximum access probes transmitted. */
+    public static final int MAX_ACCESS_PROBE = 0x81F;
+    /** Concurrent service is not supported by base station. */
+    public static final int CONCURRENT_SERVICE_NOT_SUPPORTED_BY_BASE_STATION = 0x820;
+    /** There was no response received from the base station. */
+    public static final int NO_RESPONSE_FROM_BASE_STATION = 0x821;
+    /** The base station rejecting the call. */
+    public static final int REJECTED_BY_BASE_STATION = 0x822;
+    /** The concurrent services requested were not compatible. */
+    public static final int CONCURRENT_SERVICES_INCOMPATIBLE = 0x823;
+    /** Device does not have CDMA service. */
+    public static final int NO_CDMA_SERVICE = 0x824;
+    /** RUIM not being present. */
+    public static final int RUIM_NOT_PRESENT = 0x825;
+    /** Receiving a retry order from the base station. */
+    public static final int CDMA_RETRY_ORDER = 0x826;
+    /** Access blocked by the base station. */
+    public static final int ACCESS_BLOCK = 0x827;
+    /** Access blocked by the base station for all mobile devices. */
+    public static final int ACCESS_BLOCK_ALL = 0x828;
+    /** Maximum access probes for the IS-707B call. */
+    public static final int IS707B_MAX_ACCESS_PROBES = 0x829;
+    /** Put device in thermal emergency. */
+    public static final int THERMAL_EMERGENCY = 0x82A;
+    /** In favor of a voice call or SMS when concurrent voice and data are not supported. */
+    public static final int CONCURRENT_SERVICES_NOT_ALLOWED = 0x82B;
+    /** The other clients rejected incoming call. */
+    public static final int INCOMING_CALL_REJECTED = 0x82C;
+    /** No service on the gateway. */
+    public static final int NO_SERVICE_ON_GATEWAY = 0x82D;
+    /** GPRS context is not available. */
+    public static final int NO_GPRS_CONTEXT = 0x82E;
+    /**
+     * Network refuses service to the MS because either an identity of the MS is not acceptable to
+     * the network or the MS does not pass the authentication check.
+     */
+    public static final int ILLEGAL_MS = 0x82F;
+    /** ME could not be authenticated and the ME used is not acceptable to the network. */
+    public static final int ILLEGAL_ME = 0x830;
+    /** Not allowed to operate either GPRS or non-GPRS services. */
+    public static final int GPRS_SERVICES_AND_NON_GPRS_SERVICES_NOT_ALLOWED = 0x831;
+    /** MS is not allowed to operate GPRS services. */
+    public static final int GPRS_SERVICES_NOT_ALLOWED = 0x832;
+    /** No matching identity or context could be found in the network. */
+    public static final int MS_IDENTITY_CANNOT_BE_DERIVED_BY_THE_NETWORK = 0x833;
+    /**
+     * Mobile reachable timer has expired, or the GMM context data related to the subscription does
+     * not exist in the SGSN.
+     */
+    public static final int IMPLICITLY_DETACHED = 0x834;
+    /**
+     * UE requests GPRS service, or the network initiates a detach request in a PLMN which does not
+     * offer roaming for GPRS services to that MS.
+     */
+    public static final int PLMN_NOT_ALLOWED = 0x835;
+    /**
+     * MS requests service, or the network initiates a detach request, in a location area where the
+     * HPLMN determines that the MS, by subscription, is not allowed to operate.
+     */
+    public static final int LOCATION_AREA_NOT_ALLOWED = 0x836;
+    /**
+     * UE requests GPRS service or the network initiates a detach request in a PLMN that does not
+     * offer roaming for GPRS services.
+     */
+    public static final int GPRS_SERVICES_NOT_ALLOWED_IN_THIS_PLMN = 0x837;
+    /** PDP context already exists. */
+    public static final int PDP_DUPLICATE = 0x838;
+    /** RAT change on the UE. */
+    public static final int UE_RAT_CHANGE = 0x839;
+    /** Network cannot serve a request from the MS due to congestion. */
+    public static final int CONGESTION = 0x83A;
+    /**
+     * MS requests an establishment of the radio access bearers for all active PDP contexts by
+     * sending a service request message indicating data to the network, but the SGSN does not have
+     * any active PDP context.
+     */
+    public static final int NO_PDP_CONTEXT_ACTIVATED = 0x83B;
+    /** Access class blocking restrictions for the current camped cell. */
+    public static final int ACCESS_CLASS_DSAC_REJECTION = 0x83C;
+    /** SM attempts PDP activation for a maximum of four attempts. */
+    public static final int PDP_ACTIVATE_MAX_RETRY_FAILED = 0x83D;
+    /** Radio access bearer failure. */
+    public static final int RADIO_ACCESS_BEARER_FAILURE = 0x83E;
+    /** Invalid EPS bearer identity in the request. */
+    public static final int ESM_UNKNOWN_EPS_BEARER_CONTEXT = 0x83F;
+    /** Data radio bearer is released by the RRC. */
+    public static final int DRB_RELEASED_BY_RRC = 0x840;
+    /** Indicate the connection was released. */
+    public static final int CONNECTION_RELEASED = 0x841;
+    /** UE is detached. */
+    public static final int EMM_DETACHED = 0x842;
+    /** Attach procedure is rejected by the network. */
+    public static final int EMM_ATTACH_FAILED = 0x843;
+    /** Attach procedure is started for EMC purposes. */
+    public static final int EMM_ATTACH_STARTED = 0x844;
+    /** Service request procedure failure. */
+    public static final int LTE_NAS_SERVICE_REQUEST_FAILED = 0x845;
+    /** Active dedicated bearer was requested using the same default bearer ID. */
+    public static final int DUPLICATE_BEARER_ID = 0x846;
+    /** Collision scenarios for the UE and network-initiated procedures. */
+    public static final int ESM_COLLISION_SCENARIOS = 0x847;
+    /** Bearer must be deactivated to synchronize with the network. */
+    public static final int ESM_BEARER_DEACTIVATED_TO_SYNC_WITH_NETWORK = 0x848;
+    /** Active dedicated bearer was requested for an existing default bearer. */
+    public static final int ESM_NW_ACTIVATED_DED_BEARER_WITH_ID_OF_DEF_BEARER = 0x849;
+    /** Bad OTA message is received from the network. */
+    public static final int ESM_BAD_OTA_MESSAGE = 0x84A;
+    /** Download server rejected the call. */
+    public static final int ESM_DOWNLOAD_SERVER_REJECTED_THE_CALL = 0x84B;
+    /** PDN was disconnected by the downlaod server due to IRAT. */
+    public static final int ESM_CONTEXT_TRANSFERRED_DUE_TO_IRAT = 0x84C;
+    /** Dedicated bearer will be deactivated regardless of the network response. */
+    public static final int DS_EXPLICIT_DEACTIVATION = 0x84D;
+    /** No specific local cause is mentioned, usually a valid OTA cause. */
+    public static final int ESM_LOCAL_CAUSE_NONE = 0x84E;
+    /** Throttling is not needed for this service request failure. */
+    public static final int LTE_THROTTLING_NOT_REQUIRED = 0x84F;
+    /** Access control list check failure at the lower layer. */
+    public static final int ACCESS_CONTROL_LIST_CHECK_FAILURE = 0x850;
+    /** Service is not allowed on the requested PLMN. */
+    public static final int SERVICE_NOT_ALLOWED_ON_PLMN = 0x851;
+    /** T3417 timer expiration of the service request procedure. */
+    public static final int EMM_T3417_EXPIRED = 0x852;
+    /** Extended service request fails due to expiration of the T3417 EXT timer. */
+    public static final int EMM_T3417_EXT_EXPIRED = 0x853;
+    /** Transmission failure of radio resource control (RRC) uplink data. */
+    public static final int RRC_UPLINK_DATA_TRANSMISSION_FAILURE = 0x854;
+    /** Radio resource control (RRC) uplink data delivery failed due to a handover. */
+    public static final int RRC_UPLINK_DELIVERY_FAILED_DUE_TO_HANDOVER = 0x855;
+    /** Radio resource control (RRC) uplink data delivery failed due to a connection release. */
+    public static final int RRC_UPLINK_CONNECTION_RELEASE = 0x856;
+    /** Radio resource control (RRC) uplink data delivery failed due to a radio link failure. */
+    public static final int RRC_UPLINK_RADIO_LINK_FAILURE = 0x857;
+    /**
+     * Radio resource control (RRC) is not connected but the non-access stratum (NAS) sends an
+     * uplink data request.
+     */
+    public static final int RRC_UPLINK_ERROR_REQUEST_FROM_NAS = 0x858;
+    /** Radio resource control (RRC) connection failure at access stratum. */
+    public static final int RRC_CONNECTION_ACCESS_STRATUM_FAILURE = 0x859;
+    /**
+     * Radio resource control (RRC) connection establishment is aborted due to another procedure.
+     */
+    public static final int RRC_CONNECTION_ANOTHER_PROCEDURE_IN_PROGRESS = 0x85A;
+    /** Radio resource control (RRC) connection establishment failed due to access barrred. */
+    public static final int RRC_CONNECTION_ACCESS_BARRED = 0x85B;
+    /**
+     * Radio resource control (RRC) connection establishment failed due to cell reselection at
+     * access stratum.
+     */
+    public static final int RRC_CONNECTION_CELL_RESELECTION = 0x85C;
+    /**
+     * Connection establishment failed due to configuration failure at the radio resource control
+     * (RRC).
+     */
+    public static final int RRC_CONNECTION_CONFIG_FAILURE = 0x85D;
+    /** Radio resource control (RRC) connection could not be established in the time limit. */
+    public static final int RRC_CONNECTION_TIMER_EXPIRED = 0x85E;
+    /**
+     * Connection establishment failed due to a link failure at the radio resource control (RRC).
+     */
+    public static final int RRC_CONNECTION_LINK_FAILURE = 0x85F;
+    /**
+     * Connection establishment failed as the radio resource control (RRC) is not camped on any
+     * cell.
+     */
+    public static final int RRC_CONNECTION_CELL_NOT_CAMPED = 0x860;
+    /**
+     * Connection establishment failed due to a service interval failure at the radio resource
+     * control (RRC).
+     */
+    public static final int RRC_CONNECTION_SYSTEM_INTERVAL_FAILURE = 0x861;
+    /**
+     * Radio resource control (RRC) connection establishment failed due to the network rejecting
+     * the UE connection request.
+     */
+    public static final int RRC_CONNECTION_REJECT_BY_NETWORK = 0x862;
+    /** Normal radio resource control (RRC) connection release. */
+    public static final int RRC_CONNECTION_NORMAL_RELEASE = 0x863;
+    /**
+     * Radio resource control (RRC) connection release failed due to radio link failure conditions.
+     */
+    public static final int RRC_CONNECTION_RADIO_LINK_FAILURE = 0x864;
+    /** Radio resource control (RRC) connection re-establishment failure. */
+    public static final int RRC_CONNECTION_REESTABLISHMENT_FAILURE = 0x865;
+    /** UE is out of service during the call register. */
+    public static final int RRC_CONNECTION_OUT_OF_SERVICE_DURING_CELL_REGISTER = 0x866;
+    /**
+     * Connection has been released by the radio resource control (RRC) due to an abort request.
+     */
+    public static final int RRC_CONNECTION_ABORT_REQUEST = 0x867;
+    /**
+     * Radio resource control (RRC) connection released due to a system information block read
+     * error.
+     */
+    public static final int RRC_CONNECTION_SYSTEM_INFORMATION_BLOCK_READ_ERROR = 0x868;
+    /** Network-initiated detach with reattach. */
+    public static final int NETWORK_INITIATED_DETACH_WITH_AUTO_REATTACH = 0x869;
+    /** Network-initiated detach without reattach. */
+    public static final int NETWORK_INITIATED_DETACH_NO_AUTO_REATTACH = 0x86A;
+    /** ESM procedure maximum attempt timeout failure. */
+    public static final int ESM_PROCEDURE_TIME_OUT = 0x86B;
+    /**
+     * No PDP exists with the given connection ID while modifying or deactivating or activation for
+     * an already active PDP.
+     */
+    public static final int INVALID_CONNECTION_ID = 0x86C;
+    /** Maximum NSAPIs have been exceeded during PDP activation. */
+    public static final int MAXIMIUM_NSAPIS_EXCEEDED = 0x86D;
+    /** Primary context for NSAPI does not exist. */
+    public static final int INVALID_PRIMARY_NSAPI = 0x86E;
+    /** Unable to encode the OTA message for MT PDP or deactivate PDP. */
+    public static final int CANNOT_ENCODE_OTA_MESSAGE = 0x86F;
+    /**
+     * Radio access bearer is not established by the lower layers during activation, modification,
+     * or deactivation.
+     */
+    public static final int RADIO_ACCESS_BEARER_SETUP_FAILURE = 0x870;
+    /** Expiration of the PDP establish timer with a maximum of five retries. */
+    public static final int PDP_ESTABLISH_TIMEOUT_EXPIRED = 0x871;
+    /** Expiration of the PDP modify timer with a maximum of four retries. */
+    public static final int PDP_MODIFY_TIMEOUT_EXPIRED = 0x872;
+    /** Expiration of the PDP deactivate timer with a maximum of four retries. */
+    public static final int PDP_INACTIVE_TIMEOUT_EXPIRED = 0x873;
+    /** PDP activation failed due to RRC_ABORT or a forbidden PLMN. */
+    public static final int PDP_LOWERLAYER_ERROR = 0x874;
+    /** MO PDP modify collision when the MT PDP is already in progress. */
+    public static final int PDP_MODIFY_COLLISION = 0x875;
+    /** Maximum size of the L2 message was exceeded. */
+    public static final int MAXINUM_SIZE_OF_L2_MESSAGE_EXCEEDED = 0x876;
+    /** Non-access stratum (NAS) request was rejected by the network. */
+    public static final int NAS_REQUEST_REJECTED_BY_NETWORK = 0x877;
+    /**
+     * Radio resource control (RRC) connection establishment failure due to an error in the request
+     * message.
+     */
+    public static final int RRC_CONNECTION_INVALID_REQUEST = 0x878;
+    /**
+     * Radio resource control (RRC) connection establishment failure due to a change in the
+     * tracking area ID.
+     */
+    public static final int RRC_CONNECTION_TRACKING_AREA_ID_CHANGED = 0x879;
+    /**
+     * Radio resource control (RRC) connection establishment failure due to the RF was unavailable.
+     */
+    public static final int RRC_CONNECTION_RF_UNAVAILABLE = 0x87A;
+    /**
+     * Radio resource control (RRC) connection was aborted before deactivating the LTE stack due to
+     * a successful LTE to WCDMA/GSM/TD-SCDMA IRAT change.
+     */
+    public static final int RRC_CONNECTION_ABORTED_DUE_TO_IRAT_CHANGE = 0x87B;
+    /**
+     * If the UE has an LTE radio link failure before security is established, the radio resource
+     * control (RRC) connection must be released and the UE must return to idle.
+     */
+    public static final int RRC_CONNECTION_RELEASED_SECURITY_NOT_ACTIVE = 0x87C;
+    /**
+     * Radio resource control (RRC) connection was aborted by the non-access stratum (NAS) after an
+     * IRAT to LTE IRAT handover.
+     */
+    public static final int RRC_CONNECTION_ABORTED_AFTER_HANDOVER = 0x87D;
+    /**
+     * Radio resource control (RRC) connection was aborted before deactivating the LTE stack after
+     * a successful LTE to GSM/EDGE IRAT cell change order procedure.
+     */
+    public static final int RRC_CONNECTION_ABORTED_AFTER_IRAT_CELL_CHANGE = 0x87E;
+    /**
+     * Radio resource control (RRC) connection was aborted in the middle of a LTE to GSM IRAT cell
+     * change order procedure.
+     */
+    public static final int RRC_CONNECTION_ABORTED_DURING_IRAT_CELL_CHANGE = 0x87F;
+    /** IMSI present in the UE is unknown in the home subscriber server. */
+    public static final int IMSI_UNKNOWN_IN_HOME_SUBSCRIBER_SERVER = 0x880;
+    /** IMEI of the UE is not accepted by the network. */
+    public static final int IMEI_NOT_ACCEPTED = 0x881;
+    /** EPS and non-EPS services are not allowed by the network. */
+    public static final int EPS_SERVICES_AND_NON_EPS_SERVICES_NOT_ALLOWED = 0x882;
+    /** EPS services are not allowed in the PLMN. */
+    public static final int EPS_SERVICES_NOT_ALLOWED_IN_PLMN = 0x883;
+    /** Mobile switching center is temporarily unreachable. */
+    public static final int MSC_TEMPORARILY_NOT_REACHABLE = 0x884;
+    /** CS domain is not available. */
+    public static final int CS_DOMAIN_NOT_AVAILABLE = 0x885;
+    /** ESM level failure. */
+    public static final int ESM_FAILURE = 0x886;
+    /** MAC level failure. */
+    public static final int MAC_FAILURE = 0x887;
+    /** Synchronization failure. */
+    public static final int SYNCHRONIZATION_FAILURE = 0x888;
+    /** UE security capabilities mismatch. */
+    public static final int UE_SECURITY_CAPABILITIES_MISMATCH = 0x889;
+    /** Unspecified security mode reject. */
+    public static final int SECURITY_MODE_REJECTED = 0x88A;
+    /** Unacceptable non-EPS authentication. */
+    public static final int UNACCEPTABLE_NON_EPS_AUTHENTICATION = 0x88B;
+    /** CS fallback call establishment is not allowed. */
+    public static final int CS_FALLBACK_CALL_ESTABLISHMENT_NOT_ALLOWED = 0x88C;
+    /** No EPS bearer context was activated. */
+    public static final int NO_EPS_BEARER_CONTEXT_ACTIVATED = 0x88D;
+    /** Invalid EMM state. */
+    public static final int INVALID_EMM_STATE = 0x88E;
+    /** Non-Access Spectrum layer failure. */
+    public static final int NAS_LAYER_FAILURE = 0x88F;
+    /** Multiple PDP call feature is disabled. */
+    public static final int MULTIPLE_PDP_CALL_NOT_ALLOWED = 0x890;
+    /** Data call has been brought down because EMBMS is not enabled at the RRC layer. */
+    public static final int EMBMS_NOT_ENABLED = 0x891;
+    /** Data call was unsuccessfully transferred during the IRAT handover. */
+    public static final int IRAT_HANDOVER_FAILED = 0x892;
+    /** EMBMS data call has been successfully brought down. */
+    public static final int EMBMS_REGULAR_DEACTIVATION = 0x893;
+    /** Test loop-back data call has been successfully brought down. */
+    public static final int TEST_LOOPBACK_REGULAR_DEACTIVATION = 0x894;
+    /** Lower layer registration failure. */
+    public static final int LOWER_LAYER_REGISTRATION_FAILURE = 0x895;
+    /**
+     * Network initiates a detach on LTE with error cause ""data plan has been replenished or has
+     * expired.
+     */
+    public static final int DATA_PLAN_EXPIRED = 0x896;
+    /** UMTS interface is brought down due to handover from UMTS to iWLAN. */
+    public static final int UMTS_HANDOVER_TO_IWLAN = 0x897;
+    /** Received a connection deny due to general or network busy on EVDO network. */
+    public static final int EVDO_CONNECTION_DENY_BY_GENERAL_OR_NETWORK_BUSY = 0x898;
+    /** Received a connection deny due to billing or authentication failure on EVDO network. */
+    public static final int EVDO_CONNECTION_DENY_BY_BILLING_OR_AUTHENTICATION_FAILURE = 0x899;
+    /** HDR system has been changed due to redirection or the PRL was not preferred. */
+    public static final int EVDO_HDR_CHANGED = 0x89A;
+    /** Device exited HDR due to redirection or the PRL was not preferred. */
+    public static final int EVDO_HDR_EXITED = 0x89B;
+    /** Device does not have an HDR session. */
+    public static final int EVDO_HDR_NO_SESSION = 0x89C;
+    /** It is ending an HDR call origination in favor of a GPS fix. */
+    public static final int EVDO_USING_GPS_FIX_INSTEAD_OF_HDR_CALL = 0x89D;
+    /** Connection setup on the HDR system was time out. */
+    public static final int EVDO_HDR_CONNECTION_SETUP_TIMEOUT = 0x89E;
+    /** Device failed to acquire a co-located HDR for origination. */
+    public static final int FAILED_TO_ACQUIRE_COLOCATED_HDR = 0x89F;
+    /** OTASP commit is in progress. */
+    public static final int OTASP_COMMIT_IN_PROGRESS = 0x8A0;
+    /** Device has no hybrid HDR service. */
+    public static final int NO_HYBRID_HDR_SERVICE = 0x8A1;
+    /** HDR module could not be obtained because of the RF locked. */
+    public static final int HDR_NO_LOCK_GRANTED = 0x8A2;
+    /** DBM or SMS is in progress. */
+    public static final int DBM_OR_SMS_IN_PROGRESS = 0x8A3;
+    /** HDR module released the call due to fade. */
+    public static final int HDR_FADE = 0x8A4;
+    /** HDR system access failure. */
+    public static final int HDR_ACCESS_FAILURE = 0x8A5;
+    /**
+     * P_rev supported by 1 base station is less than 6, which is not supported for a 1X data call.
+     * The UE must be in the footprint of BS which has p_rev >= 6 to support this SO33 call.
+     */
+    public static final int UNSUPPORTED_1X_PREV = 0x8A6;
+    /** Client ended the data call. */
+    public static final int LOCAL_END = 0x8A7;
+    /** Device has no service. */
+    public static final int NO_SERVICE = 0x8A8;
+    /** Device lost the system due to fade. */
+    public static final int FADE = 0x8A9;
+    /** Receiving a release from the base station with no reason. */
+    public static final int NORMAL_RELEASE = 0x8AA;
+    /** Access attempt is already in progress. */
+    public static final int ACCESS_ATTEMPT_ALREADY_IN_PROGRESS = 0x8AB;
+    /** Device is in the process of redirecting or handing off to a different target system. */
+    public static final int REDIRECTION_OR_HANDOFF_IN_PROGRESS = 0x8AC;
+    /** Device is operating in Emergency mode. */
+    public static final int EMERGENCY_MODE = 0x8AD;
+    /** Device is in use (e.g., voice call). */
+    public static final int PHONE_IN_USE = 0x8AE;
+    /**
+     * Device operational mode is different from the mode requested in the traffic channel bring up.
+     */
+    public static final int INVALID_MODE = 0x8AF;
+    /** SIM was marked by the network as invalid for the circuit and/or packet service domain. */
+    public static final int INVALID_SIM_STATE = 0x8B0;
+    /** There is no co-located HDR. */
+    public static final int NO_COLLOCATED_HDR = 0x8B1;
+    /** UE is entering power save mode. */
+    public static final int UE_IS_ENTERING_POWERSAVE_MODE = 0x8B2;
+    /** Dual switch from single standby to dual standby is in progress. */
+    public static final int DUAL_SWITCH = 0x8B3;
+    /**
+     * Data call bring up fails in the PPP setup due to a timeout.
+     * (e.g., an LCP conf ack was not received from the network)
+     */
+    public static final int PPP_TIMEOUT = 0x8B4;
+    /**
+     * Data call bring up fails in the PPP setup due to an authorization failure.
+     * (e.g., authorization is required, but not negotiated with the network during an LCP phase)
+     */
+    public static final int PPP_AUTH_FAILURE = 0x8B5;
+    /** Data call bring up fails in the PPP setup due to an option mismatch. */
+    public static final int PPP_OPTION_MISMATCH = 0x8B6;
+    /** Data call bring up fails in the PPP setup due to a PAP failure. */
+    public static final int PPP_PAP_FAILURE = 0x8B7;
+    /** Data call bring up fails in the PPP setup due to a CHAP failure. */
+    public static final int PPP_CHAP_FAILURE = 0x8B8;
+    /**
+     * Data call bring up fails in the PPP setup because the PPP is in the process of cleaning the
+     * previous PPP session.
+     */
+    public static final int PPP_CLOSE_IN_PROGRESS = 0x8B9;
+    /**
+     * IPv6 interface bring up fails because the network provided only the IPv4 address for the
+     * upcoming PDN permanent client can reattempt a IPv6 call bring up after the IPv4 interface is
+     * also brought down. However, there is no guarantee that the network will provide a IPv6
+     * address.
+     */
+    public static final int LIMITED_TO_IPV4 = 0x8BA;
+    /**
+     * IPv4 interface bring up fails because the network provided only the IPv6 address for the
+     * upcoming PDN permanent client can reattempt a IPv4 call bring up after the IPv6 interface is
+     * also brought down. However there is no guarantee that the network will provide a IPv4
+     * address.
+     */
+    public static final int LIMITED_TO_IPV6 = 0x8BB;
+    /** Data call bring up fails in the VSNCP phase due to a VSNCP timeout error. */
+    public static final int VSNCP_TIMEOUT = 0x8BC;
+    /**
+     * Data call bring up fails in the VSNCP phase due to a general error. It's used when there is
+     * no other specific error code available to report the failure.
+     */
+    public static final int VSNCP_GEN_ERROR = 0x8BD;
+    /**
+     * Data call bring up fails in the VSNCP phase due to a network rejection of the VSNCP
+     * configuration request because the requested APN is unauthorized.
+     */
+    public static final int VSNCP_APN_UNATHORIZED = 0x8BE;
+    /**
+     * Data call bring up fails in the VSNCP phase due to a network rejection of the VSNCP
+     * configuration request because the PDN limit has been exceeded.
+     */
+    public static final int VSNCP_PDN_LIMIT_EXCEEDED = 0x8BF;
+    /**
+     * Data call bring up fails in the VSNCP phase due to the network rejected the VSNCP
+     * configuration request due to no PDN gateway address.
+     */
+    public static final int VSNCP_NO_PDN_GATEWAY_ADDRESS = 0x8C0;
+    /**
+     * Data call bring up fails in the VSNCP phase due to a network rejection of the VSNCP
+     * configuration request because the PDN gateway is unreachable.
+     */
+    public static final int VSNCP_PDN_GATEWAY_UNREACHABLE = 0x8C1;
+    /**
+     * Data call bring up fails in the VSNCP phase due to a network rejection of the VSNCP
+     * configuration request due to a PDN gateway reject.
+     */
+    public static final int VSNCP_PDN_GATEWAY_REJECT = 0x8C2;
+    /**
+     * Data call bring up fails in the VSNCP phase due to a network rejection of the VSNCP
+     * configuration request with the reason of insufficient parameter.
+     */
+    public static final int VSNCP_INSUFFICIENT_PARAMETERS = 0x8C3;
+    /**
+     * Data call bring up fails in the VSNCP phase due to a network rejection of the VSNCP
+     * configuration request with the reason of resource unavailable.
+     */
+    public static final int VSNCP_RESOURCE_UNAVAILABLE = 0x8C4;
+    /**
+     * Data call bring up fails in the VSNCP phase due to a network rejection of the VSNCP
+     * configuration request with the reason of administratively prohibited at the HSGW.
+     */
+    public static final int VSNCP_ADMINISTRATIVELY_PROHIBITED = 0x8C5;
+    /**
+     * Data call bring up fails in the VSNCP phase due to a network rejection of PDN ID in use, or
+     * all existing PDNs are brought down with this end reason because one of the PDN bring up was
+     * rejected by the network with the reason of PDN ID in use.
+     */
+    public static final int VSNCP_PDN_ID_IN_USE = 0x8C6;
+    /**
+     * Data call bring up fails in the VSNCP phase due to a network rejection of the VSNCP
+     * configuration request for the reason of subscriber limitation.
+     */
+    public static final int VSNCP_SUBSCRIBER_LIMITATION = 0x8C7;
+    /**
+     * Data call bring up fails in the VSNCP phase due to a network rejection of the VSNCP
+     * configuration request because the PDN exists for this APN.
+     */
+    public static final int VSNCP_PDN_EXISTS_FOR_THIS_APN = 0x8C8;
+    /**
+     * Data call bring up fails in the VSNCP phase due to a network rejection of the VSNCP
+     * configuration request with reconnect to this PDN not allowed, or an active data call is
+     * terminated by the network because reconnection to this PDN is not allowed. Upon receiving
+     * this error code from the network, the modem infinitely throttles the PDN until the next
+     * power cycle.
+     */
+    public static final int VSNCP_RECONNECT_NOT_ALLOWED = 0x8C9;
+    /** Device failure to obtain the prefix from the network. */
+    public static final int IPV6_PREFIX_UNAVAILABLE = 0x8CA;
+    /** System preference change back to SRAT during handoff */
+    public static final int HANDOFF_PREFERENCE_CHANGED = 0x8CB;
 
     // OEM sepecific error codes. To be used by OEMs when they don't
     // want to reveal error code which would be replaced by ERROR_UNSPECIFIED
@@ -199,6 +961,13 @@
     /** @hide */
     public static final int RESET_BY_FRAMEWORK = 0x10005;
 
+    /**
+     * Data handover failed.
+     *
+     * @hide
+     */
+    public static final int HANDOVER_FAILED = 0x10006;
+
     /** @hide */
     @IntDef(value = {
             NONE,
@@ -226,12 +995,18 @@
             FILTER_SEMANTIC_ERROR,
             FILTER_SYTAX_ERROR,
             PDP_WITHOUT_ACTIVE_TFT,
+            ACTIVATION_REJECTED_BCM_VIOLATION,
             ONLY_IPV4_ALLOWED,
             ONLY_IPV6_ALLOWED,
             ONLY_SINGLE_BEARER_ALLOWED,
             ESM_INFO_NOT_RECEIVED,
             PDN_CONN_DOES_NOT_EXIST,
             MULTI_CONN_TO_SAME_PDN_NOT_ALLOWED,
+            COLLISION_WITH_NETWORK_INITIATED_REQUEST,
+            ONLY_IPV4V6_ALLOWED,
+            ONLY_NON_IP_ALLOWED,
+            UNSUPPORTED_QCI_VALUE,
+            BEARER_HANDLING_NOT_SUPPORTED,
             ACTIVE_PDP_CONTEXT_MAX_NUMBER_REACHED,
             UNSUPPORTED_APN_IN_CURRENT_PLMN,
             INVALID_TRANSACTION_ID,
@@ -242,7 +1017,7 @@
             UNKNOWN_INFO_ELEMENT,
             CONDITIONAL_IE_ERROR,
             MSG_AND_PROTOCOL_STATE_UNCOMPATIBLE,
-            PROTOCOL_ERRORS,                 /* no retry */
+            PROTOCOL_ERRORS,
             APN_TYPE_CONFLICT,
             INVALID_PCSCF_ADDR,
             INTERNAL_CALL_PREEMPT_BY_HIGH_PRIO_APN,
@@ -254,6 +1029,262 @@
             IFACE_AND_POL_FAMILY_MISMATCH,
             EMM_ACCESS_BARRED_INFINITE_RETRY,
             AUTH_FAILURE_ON_EMERGENCY_CALL,
+            INVALID_DNS_ADDR,
+            INVALID_PCSCF_OR_DNS_ADDRESS,
+            CALL_PREEMPT_BY_EMERGENCY_APN,
+            UE_INITIATED_DETACH_OR_DISCONNECT,
+            MIP_FA_REASON_UNSPECIFIED,
+            MIP_FA_ADMIN_PROHIBITED,
+            MIP_FA_INSUFFICIENT_RESOURCES,
+            MIP_FA_MOBILE_NODE_AUTHENTICATION_FAILURE,
+            MIP_FA_HOME_AGENT_AUTHENTICATION_FAILURE,
+            MIP_FA_REQUESTED_LIFETIME_TOO_LONG,
+            MIP_FA_MALFORMED_REQUEST,
+            MIP_FA_MALFORMED_REPLY,
+            MIP_FA_ENCAPSULATION_UNAVAILABLE,
+            MIP_FA_VJ_HEADER_COMPRESSION_UNAVAILABLE,
+            MIP_FA_REVERSE_TUNNEL_UNAVAILABLE,
+            MIP_FA_REVERSE_TUNNEL_IS_MANDATORY,
+            MIP_FA_DELIVERY_STYLE_NOT_SUPPORTED,
+            MIP_FA_MISSING_NAI,
+            MIP_FA_MISSING_HOME_AGENT,
+            MIP_FA_MISSING_HOME_ADDRESS,
+            MIP_FA_UNKNOWN_CHALLENGE,
+            MIP_FA_MISSING_CHALLENGE,
+            MIP_FA_STALE_CHALLENGE,
+            MIP_HA_REASON_UNSPECIFIED,
+            MIP_HA_ADMIN_PROHIBITED,
+            MIP_HA_INSUFFICIENT_RESOURCES,
+            MIP_HA_MOBILE_NODE_AUTHENTICATION_FAILURE,
+            MIP_HA_FOREIGN_AGENT_AUTHENTICATION_FAILURE,
+            MIP_HA_REGISTRATION_ID_MISMATCH,
+            MIP_HA_MALFORMED_REQUEST,
+            MIP_HA_UNKNOWN_HOME_AGENT_ADDRESS,
+            MIP_HA_REVERSE_TUNNEL_UNAVAILABLE,
+            MIP_HA_REVERSE_TUNNEL_IS_MANDATORY,
+            MIP_HA_ENCAPSULATION_UNAVAILABLE,
+            CLOSE_IN_PROGRESS,
+            NETWORK_INITIATED_TERMINATION,
+            MODEM_APP_PREEMPTED,
+            PDN_IPV4_CALL_DISALLOWED,
+            PDN_IPV4_CALL_THROTTLED,
+            PDN_IPV6_CALL_DISALLOWED,
+            PDN_IPV6_CALL_THROTTLED,
+            MODEM_RESTART,
+            PDP_PPP_NOT_SUPPORTED,
+            UNPREFERRED_RAT,
+            PHYSICAL_LINK_CLOSE_IN_PROGRESS,
+            APN_PENDING_HANDOVER,
+            PROFILE_BEARER_INCOMPATIBLE,
+            SIM_CARD_CHANGED,
+            LOW_POWER_MODE_OR_POWERING_DOWN,
+            APN_DISABLED,
+            MAX_PPP_INACTIVITY_TIMER_EXPIRED,
+            IPV6_ADDRESS_TRANSFER_FAILED,
+            TRAT_SWAP_FAILED,
+            EHRPD_TO_HRPD_FALLBACK,
+            MIP_CONFIG_FAILURE,
+            PDN_INACTIVITY_TIMER_EXPIRED,
+            MAX_IPV4_CONNECTIONS,
+            MAX_IPV6_CONNECTIONS,
+            APN_MISMATCH,
+            IP_VERSION_MISMATCH,
+            DUN_CALL_DISALLOWED,
+            INTERNAL_EPC_NONEPC_TRANSITION,
+            INTERFACE_IN_USE,
+            APN_DISALLOWED_ON_ROAMING,
+            APN_PARAMETERS_CHANGED,
+            NULL_APN_DISALLOWED,
+            THERMAL_MITIGATION,
+            DATA_SETTINGS_DISABLED,
+            DATA_ROAMING_SETTINGS_DISABLED,
+            DDS_SWITCHED,
+            FORBIDDEN_APN_NAME,
+            DDS_SWITCH_IN_PROGRESS,
+            CALL_DISALLOWED_IN_ROAMING,
+            NON_IP_NOT_SUPPORTED,
+            PDN_NON_IP_CALL_THROTTLED,
+            PDN_NON_IP_CALL_DISALLOWED,
+            CDMA_LOCK,
+            CDMA_INTERCEPT,
+            CDMA_REORDER,
+            CDMA_RELEASE_DUE_TO_SO_REJECTION,
+            CDMA_INCOMING_CALL,
+            CDMA_ALERT_STOP,
+            CHANNEL_ACQUISITION_FAILURE,
+            MAX_ACCESS_PROBE,
+            CONCURRENT_SERVICE_NOT_SUPPORTED_BY_BASE_STATION,
+            NO_RESPONSE_FROM_BASE_STATION,
+            REJECTED_BY_BASE_STATION,
+            CONCURRENT_SERVICES_INCOMPATIBLE,
+            NO_CDMA_SERVICE,
+            RUIM_NOT_PRESENT,
+            CDMA_RETRY_ORDER,
+            ACCESS_BLOCK,
+            ACCESS_BLOCK_ALL,
+            IS707B_MAX_ACCESS_PROBES,
+            THERMAL_EMERGENCY,
+            CONCURRENT_SERVICES_NOT_ALLOWED,
+            INCOMING_CALL_REJECTED,
+            NO_SERVICE_ON_GATEWAY,
+            NO_GPRS_CONTEXT,
+            ILLEGAL_MS,
+            ILLEGAL_ME,
+            GPRS_SERVICES_AND_NON_GPRS_SERVICES_NOT_ALLOWED,
+            GPRS_SERVICES_NOT_ALLOWED,
+            MS_IDENTITY_CANNOT_BE_DERIVED_BY_THE_NETWORK,
+            IMPLICITLY_DETACHED,
+            PLMN_NOT_ALLOWED,
+            LOCATION_AREA_NOT_ALLOWED,
+            GPRS_SERVICES_NOT_ALLOWED_IN_THIS_PLMN,
+            PDP_DUPLICATE,
+            UE_RAT_CHANGE,
+            CONGESTION,
+            NO_PDP_CONTEXT_ACTIVATED,
+            ACCESS_CLASS_DSAC_REJECTION,
+            PDP_ACTIVATE_MAX_RETRY_FAILED,
+            RADIO_ACCESS_BEARER_FAILURE,
+            ESM_UNKNOWN_EPS_BEARER_CONTEXT,
+            DRB_RELEASED_BY_RRC,
+            CONNECTION_RELEASED,
+            EMM_DETACHED,
+            EMM_ATTACH_FAILED,
+            EMM_ATTACH_STARTED,
+            LTE_NAS_SERVICE_REQUEST_FAILED,
+            DUPLICATE_BEARER_ID,
+            ESM_COLLISION_SCENARIOS,
+            ESM_BEARER_DEACTIVATED_TO_SYNC_WITH_NETWORK,
+            ESM_NW_ACTIVATED_DED_BEARER_WITH_ID_OF_DEF_BEARER,
+            ESM_BAD_OTA_MESSAGE,
+            ESM_DOWNLOAD_SERVER_REJECTED_THE_CALL,
+            ESM_CONTEXT_TRANSFERRED_DUE_TO_IRAT,
+            DS_EXPLICIT_DEACTIVATION,
+            ESM_LOCAL_CAUSE_NONE,
+            LTE_THROTTLING_NOT_REQUIRED,
+            ACCESS_CONTROL_LIST_CHECK_FAILURE,
+            SERVICE_NOT_ALLOWED_ON_PLMN,
+            EMM_T3417_EXPIRED,
+            EMM_T3417_EXT_EXPIRED,
+            RRC_UPLINK_DATA_TRANSMISSION_FAILURE,
+            RRC_UPLINK_DELIVERY_FAILED_DUE_TO_HANDOVER,
+            RRC_UPLINK_CONNECTION_RELEASE,
+            RRC_UPLINK_RADIO_LINK_FAILURE,
+            RRC_UPLINK_ERROR_REQUEST_FROM_NAS,
+            RRC_CONNECTION_ACCESS_STRATUM_FAILURE,
+            RRC_CONNECTION_ANOTHER_PROCEDURE_IN_PROGRESS,
+            RRC_CONNECTION_ACCESS_BARRED,
+            RRC_CONNECTION_CELL_RESELECTION,
+            RRC_CONNECTION_CONFIG_FAILURE,
+            RRC_CONNECTION_TIMER_EXPIRED,
+            RRC_CONNECTION_LINK_FAILURE,
+            RRC_CONNECTION_CELL_NOT_CAMPED,
+            RRC_CONNECTION_SYSTEM_INTERVAL_FAILURE,
+            RRC_CONNECTION_REJECT_BY_NETWORK,
+            RRC_CONNECTION_NORMAL_RELEASE,
+            RRC_CONNECTION_RADIO_LINK_FAILURE,
+            RRC_CONNECTION_REESTABLISHMENT_FAILURE,
+            RRC_CONNECTION_OUT_OF_SERVICE_DURING_CELL_REGISTER,
+            RRC_CONNECTION_ABORT_REQUEST,
+            RRC_CONNECTION_SYSTEM_INFORMATION_BLOCK_READ_ERROR,
+            NETWORK_INITIATED_DETACH_WITH_AUTO_REATTACH,
+            NETWORK_INITIATED_DETACH_NO_AUTO_REATTACH,
+            ESM_PROCEDURE_TIME_OUT,
+            INVALID_CONNECTION_ID,
+            MAXIMIUM_NSAPIS_EXCEEDED,
+            INVALID_PRIMARY_NSAPI,
+            CANNOT_ENCODE_OTA_MESSAGE,
+            RADIO_ACCESS_BEARER_SETUP_FAILURE,
+            PDP_ESTABLISH_TIMEOUT_EXPIRED,
+            PDP_MODIFY_TIMEOUT_EXPIRED,
+            PDP_INACTIVE_TIMEOUT_EXPIRED,
+            PDP_LOWERLAYER_ERROR,
+            PDP_MODIFY_COLLISION,
+            MAXINUM_SIZE_OF_L2_MESSAGE_EXCEEDED,
+            NAS_REQUEST_REJECTED_BY_NETWORK,
+            RRC_CONNECTION_INVALID_REQUEST,
+            RRC_CONNECTION_TRACKING_AREA_ID_CHANGED,
+            RRC_CONNECTION_RF_UNAVAILABLE,
+            RRC_CONNECTION_ABORTED_DUE_TO_IRAT_CHANGE,
+            RRC_CONNECTION_RELEASED_SECURITY_NOT_ACTIVE,
+            RRC_CONNECTION_ABORTED_AFTER_HANDOVER,
+            RRC_CONNECTION_ABORTED_AFTER_IRAT_CELL_CHANGE,
+            RRC_CONNECTION_ABORTED_DURING_IRAT_CELL_CHANGE,
+            IMSI_UNKNOWN_IN_HOME_SUBSCRIBER_SERVER,
+            IMEI_NOT_ACCEPTED,
+            EPS_SERVICES_AND_NON_EPS_SERVICES_NOT_ALLOWED,
+            EPS_SERVICES_NOT_ALLOWED_IN_PLMN,
+            MSC_TEMPORARILY_NOT_REACHABLE,
+            CS_DOMAIN_NOT_AVAILABLE,
+            ESM_FAILURE,
+            MAC_FAILURE,
+            SYNCHRONIZATION_FAILURE,
+            UE_SECURITY_CAPABILITIES_MISMATCH,
+            SECURITY_MODE_REJECTED,
+            UNACCEPTABLE_NON_EPS_AUTHENTICATION,
+            CS_FALLBACK_CALL_ESTABLISHMENT_NOT_ALLOWED,
+            NO_EPS_BEARER_CONTEXT_ACTIVATED,
+            INVALID_EMM_STATE,
+            NAS_LAYER_FAILURE,
+            MULTIPLE_PDP_CALL_NOT_ALLOWED,
+            EMBMS_NOT_ENABLED,
+            IRAT_HANDOVER_FAILED,
+            EMBMS_REGULAR_DEACTIVATION,
+            TEST_LOOPBACK_REGULAR_DEACTIVATION,
+            LOWER_LAYER_REGISTRATION_FAILURE,
+            DATA_PLAN_EXPIRED,
+            UMTS_HANDOVER_TO_IWLAN,
+            EVDO_CONNECTION_DENY_BY_GENERAL_OR_NETWORK_BUSY,
+            EVDO_CONNECTION_DENY_BY_BILLING_OR_AUTHENTICATION_FAILURE,
+            EVDO_HDR_CHANGED,
+            EVDO_HDR_EXITED,
+            EVDO_HDR_NO_SESSION,
+            EVDO_USING_GPS_FIX_INSTEAD_OF_HDR_CALL,
+            EVDO_HDR_CONNECTION_SETUP_TIMEOUT,
+            FAILED_TO_ACQUIRE_COLOCATED_HDR,
+            OTASP_COMMIT_IN_PROGRESS,
+            NO_HYBRID_HDR_SERVICE,
+            HDR_NO_LOCK_GRANTED,
+            DBM_OR_SMS_IN_PROGRESS,
+            HDR_FADE,
+            HDR_ACCESS_FAILURE,
+            UNSUPPORTED_1X_PREV,
+            LOCAL_END,
+            NO_SERVICE,
+            FADE,
+            NORMAL_RELEASE,
+            ACCESS_ATTEMPT_ALREADY_IN_PROGRESS,
+            REDIRECTION_OR_HANDOFF_IN_PROGRESS,
+            EMERGENCY_MODE,
+            PHONE_IN_USE,
+            INVALID_MODE,
+            INVALID_SIM_STATE,
+            NO_COLLOCATED_HDR,
+            UE_IS_ENTERING_POWERSAVE_MODE,
+            DUAL_SWITCH,
+            PPP_TIMEOUT,
+            PPP_AUTH_FAILURE,
+            PPP_OPTION_MISMATCH,
+            PPP_PAP_FAILURE,
+            PPP_CHAP_FAILURE,
+            PPP_CLOSE_IN_PROGRESS,
+            LIMITED_TO_IPV4,
+            LIMITED_TO_IPV6,
+            VSNCP_TIMEOUT,
+            VSNCP_GEN_ERROR,
+            VSNCP_APN_UNATHORIZED,
+            VSNCP_PDN_LIMIT_EXCEEDED,
+            VSNCP_NO_PDN_GATEWAY_ADDRESS,
+            VSNCP_PDN_GATEWAY_UNREACHABLE,
+            VSNCP_PDN_GATEWAY_REJECT,
+            VSNCP_INSUFFICIENT_PARAMETERS,
+            VSNCP_RESOURCE_UNAVAILABLE,
+            VSNCP_ADMINISTRATIVELY_PROHIBITED,
+            VSNCP_PDN_ID_IN_USE,
+            VSNCP_SUBSCRIBER_LIMITATION,
+            VSNCP_PDN_EXISTS_FOR_THIS_APN,
+            VSNCP_RECONNECT_NOT_ALLOWED,
+            IPV6_PREFIX_UNAVAILABLE,
+            HANDOFF_PREFERENCE_CHANGED,
             OEM_DCFAILCAUSE_1,
             OEM_DCFAILCAUSE_2,
             OEM_DCFAILCAUSE_3,
@@ -317,6 +1348,7 @@
         sFailCauseMap.put(FILTER_SEMANTIC_ERROR, "FILTER_SEMANTIC_ERROR");
         sFailCauseMap.put(FILTER_SYTAX_ERROR, "FILTER_SYTAX_ERROR");
         sFailCauseMap.put(PDP_WITHOUT_ACTIVE_TFT, "PDP_WITHOUT_ACTIVE_TFT");
+        sFailCauseMap.put(ACTIVATION_REJECTED_BCM_VIOLATION, "ACTIVATION_REJECTED_BCM_VIOLATION");
         sFailCauseMap.put(ONLY_IPV4_ALLOWED, "ONLY_IPV4_ALLOWED");
         sFailCauseMap.put(ONLY_IPV6_ALLOWED, "ONLY_IPV6_ALLOWED");
         sFailCauseMap.put(ONLY_SINGLE_BEARER_ALLOWED, "ONLY_SINGLE_BEARER_ALLOWED");
@@ -324,6 +1356,12 @@
         sFailCauseMap.put(PDN_CONN_DOES_NOT_EXIST, "PDN_CONN_DOES_NOT_EXIST");
         sFailCauseMap.put(MULTI_CONN_TO_SAME_PDN_NOT_ALLOWED,
                 "MULTI_CONN_TO_SAME_PDN_NOT_ALLOWED");
+        sFailCauseMap.put(COLLISION_WITH_NETWORK_INITIATED_REQUEST,
+                "COLLISION_WITH_NETWORK_INITIATED_REQUEST");
+        sFailCauseMap.put(ONLY_IPV4V6_ALLOWED, "ONLY_IPV4V6_ALLOWED");
+        sFailCauseMap.put(ONLY_NON_IP_ALLOWED, "ONLY_NON_IP_ALLOWED");
+        sFailCauseMap.put(UNSUPPORTED_QCI_VALUE, "UNSUPPORTED_QCI_VALUE");
+        sFailCauseMap.put(BEARER_HANDLING_NOT_SUPPORTED, "BEARER_HANDLING_NOT_SUPPORTED");
         sFailCauseMap.put(ACTIVE_PDP_CONTEXT_MAX_NUMBER_REACHED,
                 "ACTIVE_PDP_CONTEXT_MAX_NUMBER_REACHED");
         sFailCauseMap.put(UNSUPPORTED_APN_IN_CURRENT_PLMN,
@@ -353,6 +1391,301 @@
                 "EMM_ACCESS_BARRED_INFINITE_RETRY");
         sFailCauseMap.put(AUTH_FAILURE_ON_EMERGENCY_CALL,
                 "AUTH_FAILURE_ON_EMERGENCY_CALL");
+        sFailCauseMap.put(INVALID_DNS_ADDR, "INVALID_DNS_ADDR");
+        sFailCauseMap.put(INVALID_PCSCF_OR_DNS_ADDRESS, "INVALID_PCSCF_OR_DNS_ADDRESS");
+        sFailCauseMap.put(CALL_PREEMPT_BY_EMERGENCY_APN, "CALL_PREEMPT_BY_EMERGENCY_APN");
+        sFailCauseMap.put(UE_INITIATED_DETACH_OR_DISCONNECT, "UE_INITIATED_DETACH_OR_DISCONNECT");
+        sFailCauseMap.put(MIP_FA_REASON_UNSPECIFIED, "MIP_FA_REASON_UNSPECIFIED");
+        sFailCauseMap.put(MIP_FA_ADMIN_PROHIBITED, "MIP_FA_ADMIN_PROHIBITED");
+        sFailCauseMap.put(MIP_FA_INSUFFICIENT_RESOURCES, "MIP_FA_INSUFFICIENT_RESOURCES");
+        sFailCauseMap.put(MIP_FA_MOBILE_NODE_AUTHENTICATION_FAILURE,
+                "MIP_FA_MOBILE_NODE_AUTHENTICATION_FAILURE");
+        sFailCauseMap.put(MIP_FA_HOME_AGENT_AUTHENTICATION_FAILURE,
+                "MIP_FA_HOME_AGENT_AUTHENTICATION_FAILURE");
+        sFailCauseMap.put(MIP_FA_REQUESTED_LIFETIME_TOO_LONG, "MIP_FA_REQUESTED_LIFETIME_TOO_LONG");
+        sFailCauseMap.put(MIP_FA_MALFORMED_REQUEST, "MIP_FA_MALFORMED_REQUEST");
+        sFailCauseMap.put(MIP_FA_MALFORMED_REPLY, "MIP_FA_MALFORMED_REPLY");
+        sFailCauseMap.put(MIP_FA_ENCAPSULATION_UNAVAILABLE, "MIP_FA_ENCAPSULATION_UNAVAILABLE");
+        sFailCauseMap.put(MIP_FA_VJ_HEADER_COMPRESSION_UNAVAILABLE,
+                "MIP_FA_VJ_HEADER_COMPRESSION_UNAVAILABLE");
+        sFailCauseMap.put(MIP_FA_REVERSE_TUNNEL_UNAVAILABLE, "MIP_FA_REVERSE_TUNNEL_UNAVAILABLE");
+        sFailCauseMap.put(MIP_FA_REVERSE_TUNNEL_IS_MANDATORY, "MIP_FA_REVERSE_TUNNEL_IS_MANDATORY");
+        sFailCauseMap.put(MIP_FA_DELIVERY_STYLE_NOT_SUPPORTED,
+                "MIP_FA_DELIVERY_STYLE_NOT_SUPPORTED");
+        sFailCauseMap.put(MIP_FA_MISSING_NAI, "MIP_FA_MISSING_NAI");
+        sFailCauseMap.put(MIP_FA_MISSING_HOME_AGENT, "MIP_FA_MISSING_HOME_AGENT");
+        sFailCauseMap.put(MIP_FA_MISSING_HOME_ADDRESS, "MIP_FA_MISSING_HOME_ADDRESS");
+        sFailCauseMap.put(MIP_FA_UNKNOWN_CHALLENGE, "MIP_FA_UNKNOWN_CHALLENGE");
+        sFailCauseMap.put(MIP_FA_MISSING_CHALLENGE, "MIP_FA_MISSING_CHALLENGE");
+        sFailCauseMap.put(MIP_FA_STALE_CHALLENGE, "MIP_FA_STALE_CHALLENGE");
+        sFailCauseMap.put(MIP_HA_REASON_UNSPECIFIED, "MIP_HA_REASON_UNSPECIFIED");
+        sFailCauseMap.put(MIP_HA_ADMIN_PROHIBITED, "MIP_HA_ADMIN_PROHIBITED");
+        sFailCauseMap.put(MIP_HA_INSUFFICIENT_RESOURCES, "MIP_HA_INSUFFICIENT_RESOURCES");
+        sFailCauseMap.put(MIP_HA_MOBILE_NODE_AUTHENTICATION_FAILURE,
+                "MIP_HA_MOBILE_NODE_AUTHENTICATION_FAILURE");
+        sFailCauseMap.put(MIP_HA_FOREIGN_AGENT_AUTHENTICATION_FAILURE,
+                "MIP_HA_FOREIGN_AGENT_AUTHENTICATION_FAILURE");
+        sFailCauseMap.put(MIP_HA_REGISTRATION_ID_MISMATCH, "MIP_HA_REGISTRATION_ID_MISMATCH");
+        sFailCauseMap.put(MIP_HA_MALFORMED_REQUEST, "MIP_HA_MALFORMED_REQUEST");
+        sFailCauseMap.put(MIP_HA_UNKNOWN_HOME_AGENT_ADDRESS, "MIP_HA_UNKNOWN_HOME_AGENT_ADDRESS");
+        sFailCauseMap.put(MIP_HA_REVERSE_TUNNEL_UNAVAILABLE, "MIP_HA_REVERSE_TUNNEL_UNAVAILABLE");
+        sFailCauseMap.put(MIP_HA_REVERSE_TUNNEL_IS_MANDATORY, "MIP_HA_REVERSE_TUNNEL_IS_MANDATORY");
+        sFailCauseMap.put(MIP_HA_ENCAPSULATION_UNAVAILABLE, "MIP_HA_ENCAPSULATION_UNAVAILABLE");
+        sFailCauseMap.put(CLOSE_IN_PROGRESS, "CLOSE_IN_PROGRESS");
+        sFailCauseMap.put(NETWORK_INITIATED_TERMINATION, "NETWORK_INITIATED_TERMINATION");
+        sFailCauseMap.put(MODEM_APP_PREEMPTED, "MODEM_APP_PREEMPTED");
+        sFailCauseMap.put(PDN_IPV4_CALL_DISALLOWED, "PDN_IPV4_CALL_DISALLOWED");
+        sFailCauseMap.put(PDN_IPV4_CALL_THROTTLED, "PDN_IPV4_CALL_THROTTLED");
+        sFailCauseMap.put(PDN_IPV6_CALL_DISALLOWED, "PDN_IPV6_CALL_DISALLOWED");
+        sFailCauseMap.put(PDN_IPV6_CALL_THROTTLED, "PDN_IPV6_CALL_THROTTLED");
+        sFailCauseMap.put(MODEM_RESTART, "MODEM_RESTART");
+        sFailCauseMap.put(PDP_PPP_NOT_SUPPORTED, "PDP_PPP_NOT_SUPPORTED");
+        sFailCauseMap.put(UNPREFERRED_RAT, "UNPREFERRED_RAT");
+        sFailCauseMap.put(PHYSICAL_LINK_CLOSE_IN_PROGRESS, "PHYSICAL_LINK_CLOSE_IN_PROGRESS");
+        sFailCauseMap.put(APN_PENDING_HANDOVER, "APN_PENDING_HANDOVER");
+        sFailCauseMap.put(PROFILE_BEARER_INCOMPATIBLE, "PROFILE_BEARER_INCOMPATIBLE");
+        sFailCauseMap.put(SIM_CARD_CHANGED, "SIM_CARD_CHANGED");
+        sFailCauseMap.put(LOW_POWER_MODE_OR_POWERING_DOWN, "LOW_POWER_MODE_OR_POWERING_DOWN");
+        sFailCauseMap.put(APN_DISABLED, "APN_DISABLED");
+        sFailCauseMap.put(MAX_PPP_INACTIVITY_TIMER_EXPIRED, "MAX_PPP_INACTIVITY_TIMER_EXPIRED");
+        sFailCauseMap.put(IPV6_ADDRESS_TRANSFER_FAILED, "IPV6_ADDRESS_TRANSFER_FAILED");
+        sFailCauseMap.put(TRAT_SWAP_FAILED, "TRAT_SWAP_FAILED");
+        sFailCauseMap.put(EHRPD_TO_HRPD_FALLBACK, "EHRPD_TO_HRPD_FALLBACK");
+        sFailCauseMap.put(MIP_CONFIG_FAILURE, "MIP_CONFIG_FAILURE");
+        sFailCauseMap.put(PDN_INACTIVITY_TIMER_EXPIRED, "PDN_INACTIVITY_TIMER_EXPIRED");
+        sFailCauseMap.put(MAX_IPV4_CONNECTIONS, "MAX_IPV4_CONNECTIONS");
+        sFailCauseMap.put(MAX_IPV6_CONNECTIONS, "MAX_IPV6_CONNECTIONS");
+        sFailCauseMap.put(APN_MISMATCH, "APN_MISMATCH");
+        sFailCauseMap.put(IP_VERSION_MISMATCH, "IP_VERSION_MISMATCH");
+        sFailCauseMap.put(DUN_CALL_DISALLOWED, "DUN_CALL_DISALLOWED");
+        sFailCauseMap.put(INTERNAL_EPC_NONEPC_TRANSITION, "INTERNAL_EPC_NONEPC_TRANSITION");
+        sFailCauseMap.put(INTERFACE_IN_USE, "INTERFACE_IN_USE");
+        sFailCauseMap.put(APN_DISALLOWED_ON_ROAMING, "APN_DISALLOWED_ON_ROAMING");
+        sFailCauseMap.put(APN_PARAMETERS_CHANGED, "APN_PARAMETERS_CHANGED");
+        sFailCauseMap.put(NULL_APN_DISALLOWED, "NULL_APN_DISALLOWED");
+        sFailCauseMap.put(THERMAL_MITIGATION, "THERMAL_MITIGATION");
+        sFailCauseMap.put(DATA_SETTINGS_DISABLED, "DATA_SETTINGS_DISABLED");
+        sFailCauseMap.put(DATA_ROAMING_SETTINGS_DISABLED, "DATA_ROAMING_SETTINGS_DISABLED");
+        sFailCauseMap.put(DDS_SWITCHED, "DDS_SWITCHED");
+        sFailCauseMap.put(FORBIDDEN_APN_NAME, "FORBIDDEN_APN_NAME");
+        sFailCauseMap.put(DDS_SWITCH_IN_PROGRESS, "DDS_SWITCH_IN_PROGRESS");
+        sFailCauseMap.put(CALL_DISALLOWED_IN_ROAMING, "CALL_DISALLOWED_IN_ROAMING");
+        sFailCauseMap.put(NON_IP_NOT_SUPPORTED, "NON_IP_NOT_SUPPORTED");
+        sFailCauseMap.put(PDN_NON_IP_CALL_THROTTLED, "PDN_NON_IP_CALL_THROTTLED");
+        sFailCauseMap.put(PDN_NON_IP_CALL_DISALLOWED, "PDN_NON_IP_CALL_DISALLOWED");
+        sFailCauseMap.put(CDMA_LOCK, "CDMA_LOCK");
+        sFailCauseMap.put(CDMA_INTERCEPT, "CDMA_INTERCEPT");
+        sFailCauseMap.put(CDMA_REORDER, "CDMA_REORDER");
+        sFailCauseMap.put(CDMA_RELEASE_DUE_TO_SO_REJECTION, "CDMA_RELEASE_DUE_TO_SO_REJECTION");
+        sFailCauseMap.put(CDMA_INCOMING_CALL, "CDMA_INCOMING_CALL");
+        sFailCauseMap.put(CDMA_ALERT_STOP, "CDMA_ALERT_STOP");
+        sFailCauseMap.put(CHANNEL_ACQUISITION_FAILURE, "CHANNEL_ACQUISITION_FAILURE");
+        sFailCauseMap.put(MAX_ACCESS_PROBE, "MAX_ACCESS_PROBE");
+        sFailCauseMap.put(CONCURRENT_SERVICE_NOT_SUPPORTED_BY_BASE_STATION,
+                "CONCURRENT_SERVICE_NOT_SUPPORTED_BY_BASE_STATION");
+        sFailCauseMap.put(NO_RESPONSE_FROM_BASE_STATION, "NO_RESPONSE_FROM_BASE_STATION");
+        sFailCauseMap.put(REJECTED_BY_BASE_STATION, "REJECTED_BY_BASE_STATION");
+        sFailCauseMap.put(CONCURRENT_SERVICES_INCOMPATIBLE, "CONCURRENT_SERVICES_INCOMPATIBLE");
+        sFailCauseMap.put(NO_CDMA_SERVICE, "NO_CDMA_SERVICE");
+        sFailCauseMap.put(RUIM_NOT_PRESENT, "RUIM_NOT_PRESENT");
+        sFailCauseMap.put(CDMA_RETRY_ORDER, "CDMA_RETRY_ORDER");
+        sFailCauseMap.put(ACCESS_BLOCK, "ACCESS_BLOCK");
+        sFailCauseMap.put(ACCESS_BLOCK_ALL, "ACCESS_BLOCK_ALL");
+        sFailCauseMap.put(IS707B_MAX_ACCESS_PROBES, "IS707B_MAX_ACCESS_PROBES");
+        sFailCauseMap.put(THERMAL_EMERGENCY, "THERMAL_EMERGENCY");
+        sFailCauseMap.put(CONCURRENT_SERVICES_NOT_ALLOWED, "CONCURRENT_SERVICES_NOT_ALLOWED");
+        sFailCauseMap.put(INCOMING_CALL_REJECTED, "INCOMING_CALL_REJECTED");
+        sFailCauseMap.put(NO_SERVICE_ON_GATEWAY, "NO_SERVICE_ON_GATEWAY");
+        sFailCauseMap.put(NO_GPRS_CONTEXT, "NO_GPRS_CONTEXT");
+        sFailCauseMap.put(ILLEGAL_MS, "ILLEGAL_MS");
+        sFailCauseMap.put(ILLEGAL_ME, "ILLEGAL_ME");
+        sFailCauseMap.put(GPRS_SERVICES_AND_NON_GPRS_SERVICES_NOT_ALLOWED,
+                "GPRS_SERVICES_AND_NON_GPRS_SERVICES_NOT_ALLOWED");
+        sFailCauseMap.put(GPRS_SERVICES_NOT_ALLOWED, "GPRS_SERVICES_NOT_ALLOWED");
+        sFailCauseMap.put(MS_IDENTITY_CANNOT_BE_DERIVED_BY_THE_NETWORK,
+                "MS_IDENTITY_CANNOT_BE_DERIVED_BY_THE_NETWORK");
+        sFailCauseMap.put(IMPLICITLY_DETACHED, "IMPLICITLY_DETACHED");
+        sFailCauseMap.put(PLMN_NOT_ALLOWED, "PLMN_NOT_ALLOWED");
+        sFailCauseMap.put(LOCATION_AREA_NOT_ALLOWED, "LOCATION_AREA_NOT_ALLOWED");
+        sFailCauseMap.put(GPRS_SERVICES_NOT_ALLOWED_IN_THIS_PLMN,
+                "GPRS_SERVICES_NOT_ALLOWED_IN_THIS_PLMN");
+        sFailCauseMap.put(PDP_DUPLICATE, "PDP_DUPLICATE");
+        sFailCauseMap.put(UE_RAT_CHANGE, "UE_RAT_CHANGE");
+        sFailCauseMap.put(CONGESTION, "CONGESTION");
+        sFailCauseMap.put(NO_PDP_CONTEXT_ACTIVATED, "NO_PDP_CONTEXT_ACTIVATED");
+        sFailCauseMap.put(ACCESS_CLASS_DSAC_REJECTION, "ACCESS_CLASS_DSAC_REJECTION");
+        sFailCauseMap.put(PDP_ACTIVATE_MAX_RETRY_FAILED, "PDP_ACTIVATE_MAX_RETRY_FAILED");
+        sFailCauseMap.put(RADIO_ACCESS_BEARER_FAILURE, "RADIO_ACCESS_BEARER_FAILURE");
+        sFailCauseMap.put(ESM_UNKNOWN_EPS_BEARER_CONTEXT, "ESM_UNKNOWN_EPS_BEARER_CONTEXT");
+        sFailCauseMap.put(DRB_RELEASED_BY_RRC, "DRB_RELEASED_BY_RRC");
+        sFailCauseMap.put(CONNECTION_RELEASED, "CONNECTION_RELEASED");
+        sFailCauseMap.put(EMM_DETACHED, "EMM_DETACHED");
+        sFailCauseMap.put(EMM_ATTACH_FAILED, "EMM_ATTACH_FAILED");
+        sFailCauseMap.put(EMM_ATTACH_STARTED, "EMM_ATTACH_STARTED");
+        sFailCauseMap.put(LTE_NAS_SERVICE_REQUEST_FAILED, "LTE_NAS_SERVICE_REQUEST_FAILED");
+        sFailCauseMap.put(DUPLICATE_BEARER_ID, "DUPLICATE_BEARER_ID");
+        sFailCauseMap.put(ESM_COLLISION_SCENARIOS, "ESM_COLLISION_SCENARIOS");
+        sFailCauseMap.put(ESM_BEARER_DEACTIVATED_TO_SYNC_WITH_NETWORK,
+                "ESM_BEARER_DEACTIVATED_TO_SYNC_WITH_NETWORK");
+        sFailCauseMap.put(ESM_NW_ACTIVATED_DED_BEARER_WITH_ID_OF_DEF_BEARER,
+                "ESM_NW_ACTIVATED_DED_BEARER_WITH_ID_OF_DEF_BEARER");
+        sFailCauseMap.put(ESM_BAD_OTA_MESSAGE, "ESM_BAD_OTA_MESSAGE");
+        sFailCauseMap.put(ESM_DOWNLOAD_SERVER_REJECTED_THE_CALL,
+                "ESM_DOWNLOAD_SERVER_REJECTED_THE_CALL");
+        sFailCauseMap.put(ESM_CONTEXT_TRANSFERRED_DUE_TO_IRAT,
+                "ESM_CONTEXT_TRANSFERRED_DUE_TO_IRAT");
+        sFailCauseMap.put(DS_EXPLICIT_DEACTIVATION, "DS_EXPLICIT_DEACTIVATION");
+        sFailCauseMap.put(ESM_LOCAL_CAUSE_NONE, "ESM_LOCAL_CAUSE_NONE");
+        sFailCauseMap.put(LTE_THROTTLING_NOT_REQUIRED, "LTE_THROTTLING_NOT_REQUIRED");
+        sFailCauseMap.put(ACCESS_CONTROL_LIST_CHECK_FAILURE,
+                "ACCESS_CONTROL_LIST_CHECK_FAILURE");
+        sFailCauseMap.put(SERVICE_NOT_ALLOWED_ON_PLMN, "SERVICE_NOT_ALLOWED_ON_PLMN");
+        sFailCauseMap.put(EMM_T3417_EXPIRED, "EMM_T3417_EXPIRED");
+        sFailCauseMap.put(EMM_T3417_EXT_EXPIRED, "EMM_T3417_EXT_EXPIRED");
+        sFailCauseMap.put(RRC_UPLINK_DATA_TRANSMISSION_FAILURE,
+                "RRC_UPLINK_DATA_TRANSMISSION_FAILURE");
+        sFailCauseMap.put(RRC_UPLINK_DELIVERY_FAILED_DUE_TO_HANDOVER,
+                "RRC_UPLINK_DELIVERY_FAILED_DUE_TO_HANDOVER");
+        sFailCauseMap.put(RRC_UPLINK_CONNECTION_RELEASE, "RRC_UPLINK_CONNECTION_RELEASE");
+        sFailCauseMap.put(RRC_UPLINK_RADIO_LINK_FAILURE, "RRC_UPLINK_RADIO_LINK_FAILURE");
+        sFailCauseMap.put(RRC_UPLINK_ERROR_REQUEST_FROM_NAS, "RRC_UPLINK_ERROR_REQUEST_FROM_NAS");
+        sFailCauseMap.put(RRC_CONNECTION_ACCESS_STRATUM_FAILURE,
+                "RRC_CONNECTION_ACCESS_STRATUM_FAILURE");
+        sFailCauseMap.put(RRC_CONNECTION_ANOTHER_PROCEDURE_IN_PROGRESS,
+                "RRC_CONNECTION_ANOTHER_PROCEDURE_IN_PROGRESS");
+        sFailCauseMap.put(RRC_CONNECTION_ACCESS_BARRED, "RRC_CONNECTION_ACCESS_BARRED");
+        sFailCauseMap.put(RRC_CONNECTION_CELL_RESELECTION, "RRC_CONNECTION_CELL_RESELECTION");
+        sFailCauseMap.put(RRC_CONNECTION_CONFIG_FAILURE, "RRC_CONNECTION_CONFIG_FAILURE");
+        sFailCauseMap.put(RRC_CONNECTION_TIMER_EXPIRED, "RRC_CONNECTION_TIMER_EXPIRED");
+        sFailCauseMap.put(RRC_CONNECTION_LINK_FAILURE, "RRC_CONNECTION_LINK_FAILURE");
+        sFailCauseMap.put(RRC_CONNECTION_CELL_NOT_CAMPED, "RRC_CONNECTION_CELL_NOT_CAMPED");
+        sFailCauseMap.put(RRC_CONNECTION_SYSTEM_INTERVAL_FAILURE,
+                "RRC_CONNECTION_SYSTEM_INTERVAL_FAILURE");
+        sFailCauseMap.put(RRC_CONNECTION_REJECT_BY_NETWORK, "RRC_CONNECTION_REJECT_BY_NETWORK");
+        sFailCauseMap.put(RRC_CONNECTION_NORMAL_RELEASE, "RRC_CONNECTION_NORMAL_RELEASE");
+        sFailCauseMap.put(RRC_CONNECTION_RADIO_LINK_FAILURE, "RRC_CONNECTION_RADIO_LINK_FAILURE");
+        sFailCauseMap.put(RRC_CONNECTION_REESTABLISHMENT_FAILURE,
+                "RRC_CONNECTION_REESTABLISHMENT_FAILURE");
+        sFailCauseMap.put(RRC_CONNECTION_OUT_OF_SERVICE_DURING_CELL_REGISTER,
+                "RRC_CONNECTION_OUT_OF_SERVICE_DURING_CELL_REGISTER");
+        sFailCauseMap.put(RRC_CONNECTION_ABORT_REQUEST, "RRC_CONNECTION_ABORT_REQUEST");
+        sFailCauseMap.put(RRC_CONNECTION_SYSTEM_INFORMATION_BLOCK_READ_ERROR,
+                "RRC_CONNECTION_SYSTEM_INFORMATION_BLOCK_READ_ERROR");
+        sFailCauseMap.put(NETWORK_INITIATED_DETACH_WITH_AUTO_REATTACH,
+                "NETWORK_INITIATED_DETACH_WITH_AUTO_REATTACH");
+        sFailCauseMap.put(NETWORK_INITIATED_DETACH_NO_AUTO_REATTACH,
+                "NETWORK_INITIATED_DETACH_NO_AUTO_REATTACH");
+        sFailCauseMap.put(ESM_PROCEDURE_TIME_OUT, "ESM_PROCEDURE_TIME_OUT");
+        sFailCauseMap.put(INVALID_CONNECTION_ID, "INVALID_CONNECTION_ID");
+        sFailCauseMap.put(MAXIMIUM_NSAPIS_EXCEEDED, "MAXIMIUM_NSAPIS_EXCEEDED");
+        sFailCauseMap.put(INVALID_PRIMARY_NSAPI, "INVALID_PRIMARY_NSAPI");
+        sFailCauseMap.put(CANNOT_ENCODE_OTA_MESSAGE, "CANNOT_ENCODE_OTA_MESSAGE");
+        sFailCauseMap.put(RADIO_ACCESS_BEARER_SETUP_FAILURE, "RADIO_ACCESS_BEARER_SETUP_FAILURE");
+        sFailCauseMap.put(PDP_ESTABLISH_TIMEOUT_EXPIRED, "PDP_ESTABLISH_TIMEOUT_EXPIRED");
+        sFailCauseMap.put(PDP_MODIFY_TIMEOUT_EXPIRED, "PDP_MODIFY_TIMEOUT_EXPIRED");
+        sFailCauseMap.put(PDP_INACTIVE_TIMEOUT_EXPIRED, "PDP_INACTIVE_TIMEOUT_EXPIRED");
+        sFailCauseMap.put(PDP_LOWERLAYER_ERROR, "PDP_LOWERLAYER_ERROR");
+        sFailCauseMap.put(PDP_MODIFY_COLLISION, "PDP_MODIFY_COLLISION");
+        sFailCauseMap.put(MAXINUM_SIZE_OF_L2_MESSAGE_EXCEEDED,
+                "MAXINUM_SIZE_OF_L2_MESSAGE_EXCEEDED");
+        sFailCauseMap.put(NAS_REQUEST_REJECTED_BY_NETWORK, "NAS_REQUEST_REJECTED_BY_NETWORK");
+        sFailCauseMap.put(RRC_CONNECTION_INVALID_REQUEST, "RRC_CONNECTION_INVALID_REQUEST");
+        sFailCauseMap.put(RRC_CONNECTION_TRACKING_AREA_ID_CHANGED,
+                "RRC_CONNECTION_TRACKING_AREA_ID_CHANGED");
+        sFailCauseMap.put(RRC_CONNECTION_RF_UNAVAILABLE, "RRC_CONNECTION_RF_UNAVAILABLE");
+        sFailCauseMap.put(RRC_CONNECTION_ABORTED_DUE_TO_IRAT_CHANGE,
+                "RRC_CONNECTION_ABORTED_DUE_TO_IRAT_CHANGE");
+        sFailCauseMap.put(RRC_CONNECTION_RELEASED_SECURITY_NOT_ACTIVE,
+                "RRC_CONNECTION_RELEASED_SECURITY_NOT_ACTIVE");
+        sFailCauseMap.put(RRC_CONNECTION_ABORTED_AFTER_HANDOVER,
+                "RRC_CONNECTION_ABORTED_AFTER_HANDOVER");
+        sFailCauseMap.put(RRC_CONNECTION_ABORTED_AFTER_IRAT_CELL_CHANGE,
+                "RRC_CONNECTION_ABORTED_AFTER_IRAT_CELL_CHANGE");
+        sFailCauseMap.put(RRC_CONNECTION_ABORTED_DURING_IRAT_CELL_CHANGE,
+                "RRC_CONNECTION_ABORTED_DURING_IRAT_CELL_CHANGE");
+        sFailCauseMap.put(IMSI_UNKNOWN_IN_HOME_SUBSCRIBER_SERVER,
+                "IMSI_UNKNOWN_IN_HOME_SUBSCRIBER_SERVER");
+        sFailCauseMap.put(IMEI_NOT_ACCEPTED, "IMEI_NOT_ACCEPTED");
+        sFailCauseMap.put(EPS_SERVICES_AND_NON_EPS_SERVICES_NOT_ALLOWED,
+                "EPS_SERVICES_AND_NON_EPS_SERVICES_NOT_ALLOWED");
+        sFailCauseMap.put(EPS_SERVICES_NOT_ALLOWED_IN_PLMN, "EPS_SERVICES_NOT_ALLOWED_IN_PLMN");
+        sFailCauseMap.put(MSC_TEMPORARILY_NOT_REACHABLE, "MSC_TEMPORARILY_NOT_REACHABLE");
+        sFailCauseMap.put(CS_DOMAIN_NOT_AVAILABLE, "CS_DOMAIN_NOT_AVAILABLE");
+        sFailCauseMap.put(ESM_FAILURE, "ESM_FAILURE");
+        sFailCauseMap.put(MAC_FAILURE, "MAC_FAILURE");
+        sFailCauseMap.put(SYNCHRONIZATION_FAILURE, "SYNCHRONIZATION_FAILURE");
+        sFailCauseMap.put(UE_SECURITY_CAPABILITIES_MISMATCH, "UE_SECURITY_CAPABILITIES_MISMATCH");
+        sFailCauseMap.put(SECURITY_MODE_REJECTED, "SECURITY_MODE_REJECTED");
+        sFailCauseMap.put(UNACCEPTABLE_NON_EPS_AUTHENTICATION,
+                "UNACCEPTABLE_NON_EPS_AUTHENTICATION");
+        sFailCauseMap.put(CS_FALLBACK_CALL_ESTABLISHMENT_NOT_ALLOWED,
+                "CS_FALLBACK_CALL_ESTABLISHMENT_NOT_ALLOWED");
+        sFailCauseMap.put(NO_EPS_BEARER_CONTEXT_ACTIVATED, "NO_EPS_BEARER_CONTEXT_ACTIVATED");
+        sFailCauseMap.put(INVALID_EMM_STATE, "INVALID_EMM_STATE");
+        sFailCauseMap.put(NAS_LAYER_FAILURE, "NAS_LAYER_FAILURE");
+        sFailCauseMap.put(MULTIPLE_PDP_CALL_NOT_ALLOWED, "MULTIPLE_PDP_CALL_NOT_ALLOWED");
+        sFailCauseMap.put(EMBMS_NOT_ENABLED, "EMBMS_NOT_ENABLED");
+        sFailCauseMap.put(IRAT_HANDOVER_FAILED, "IRAT_HANDOVER_FAILED");
+        sFailCauseMap.put(EMBMS_REGULAR_DEACTIVATION, "EMBMS_REGULAR_DEACTIVATION");
+        sFailCauseMap.put(TEST_LOOPBACK_REGULAR_DEACTIVATION, "TEST_LOOPBACK_REGULAR_DEACTIVATION");
+        sFailCauseMap.put(LOWER_LAYER_REGISTRATION_FAILURE, "LOWER_LAYER_REGISTRATION_FAILURE");
+        sFailCauseMap.put(DATA_PLAN_EXPIRED, "DATA_PLAN_EXPIRED");
+        sFailCauseMap.put(UMTS_HANDOVER_TO_IWLAN, "UMTS_HANDOVER_TO_IWLAN");
+        sFailCauseMap.put(EVDO_CONNECTION_DENY_BY_GENERAL_OR_NETWORK_BUSY,
+                "EVDO_CONNECTION_DENY_BY_GENERAL_OR_NETWORK_BUSY");
+        sFailCauseMap.put(EVDO_CONNECTION_DENY_BY_BILLING_OR_AUTHENTICATION_FAILURE,
+                "EVDO_CONNECTION_DENY_BY_BILLING_OR_AUTHENTICATION_FAILURE");
+        sFailCauseMap.put(EVDO_HDR_CHANGED, "EVDO_HDR_CHANGED");
+        sFailCauseMap.put(EVDO_HDR_EXITED, "EVDO_HDR_EXITED");
+        sFailCauseMap.put(EVDO_HDR_NO_SESSION, "EVDO_HDR_NO_SESSION");
+        sFailCauseMap.put(EVDO_USING_GPS_FIX_INSTEAD_OF_HDR_CALL,
+                "EVDO_USING_GPS_FIX_INSTEAD_OF_HDR_CALL");
+        sFailCauseMap.put(EVDO_HDR_CONNECTION_SETUP_TIMEOUT, "EVDO_HDR_CONNECTION_SETUP_TIMEOUT");
+        sFailCauseMap.put(FAILED_TO_ACQUIRE_COLOCATED_HDR, "FAILED_TO_ACQUIRE_COLOCATED_HDR");
+        sFailCauseMap.put(OTASP_COMMIT_IN_PROGRESS, "OTASP_COMMIT_IN_PROGRESS");
+        sFailCauseMap.put(NO_HYBRID_HDR_SERVICE, "NO_HYBRID_HDR_SERVICE");
+        sFailCauseMap.put(HDR_NO_LOCK_GRANTED, "HDR_NO_LOCK_GRANTED");
+        sFailCauseMap.put(DBM_OR_SMS_IN_PROGRESS, "DBM_OR_SMS_IN_PROGRESS");
+        sFailCauseMap.put(HDR_FADE, "HDR_FADE");
+        sFailCauseMap.put(HDR_ACCESS_FAILURE, "HDR_ACCESS_FAILURE");
+        sFailCauseMap.put(UNSUPPORTED_1X_PREV, "UNSUPPORTED_1X_PREV");
+        sFailCauseMap.put(LOCAL_END, "LOCAL_END");
+        sFailCauseMap.put(NO_SERVICE, "NO_SERVICE");
+        sFailCauseMap.put(FADE, "FADE");
+        sFailCauseMap.put(NORMAL_RELEASE, "NORMAL_RELEASE");
+        sFailCauseMap.put(ACCESS_ATTEMPT_ALREADY_IN_PROGRESS, "ACCESS_ATTEMPT_ALREADY_IN_PROGRESS");
+        sFailCauseMap.put(REDIRECTION_OR_HANDOFF_IN_PROGRESS, "REDIRECTION_OR_HANDOFF_IN_PROGRESS");
+        sFailCauseMap.put(EMERGENCY_MODE, "EMERGENCY_MODE");
+        sFailCauseMap.put(PHONE_IN_USE, "PHONE_IN_USE");
+        sFailCauseMap.put(INVALID_MODE, "INVALID_MODE");
+        sFailCauseMap.put(INVALID_SIM_STATE, "INVALID_SIM_STATE");
+        sFailCauseMap.put(NO_COLLOCATED_HDR, "NO_COLLOCATED_HDR");
+        sFailCauseMap.put(UE_IS_ENTERING_POWERSAVE_MODE, "UE_IS_ENTERING_POWERSAVE_MODE");
+        sFailCauseMap.put(DUAL_SWITCH, "DUAL_SWITCH");
+        sFailCauseMap.put(PPP_TIMEOUT, "PPP_TIMEOUT");
+        sFailCauseMap.put(PPP_AUTH_FAILURE, "PPP_AUTH_FAILURE");
+        sFailCauseMap.put(PPP_OPTION_MISMATCH, "PPP_OPTION_MISMATCH");
+        sFailCauseMap.put(PPP_PAP_FAILURE, "PPP_PAP_FAILURE");
+        sFailCauseMap.put(PPP_CHAP_FAILURE, "PPP_CHAP_FAILURE");
+        sFailCauseMap.put(PPP_CLOSE_IN_PROGRESS, "PPP_CLOSE_IN_PROGRESS");
+        sFailCauseMap.put(LIMITED_TO_IPV4, "LIMITED_TO_IPV4");
+        sFailCauseMap.put(LIMITED_TO_IPV6, "LIMITED_TO_IPV6");
+        sFailCauseMap.put(VSNCP_TIMEOUT, "VSNCP_TIMEOUT");
+        sFailCauseMap.put(VSNCP_GEN_ERROR, "VSNCP_GEN_ERROR");
+        sFailCauseMap.put(VSNCP_APN_UNATHORIZED, "VSNCP_APN_UNATHORIZED");
+        sFailCauseMap.put(VSNCP_PDN_LIMIT_EXCEEDED, "VSNCP_PDN_LIMIT_EXCEEDED");
+        sFailCauseMap.put(VSNCP_NO_PDN_GATEWAY_ADDRESS, "VSNCP_NO_PDN_GATEWAY_ADDRESS");
+        sFailCauseMap.put(VSNCP_PDN_GATEWAY_UNREACHABLE, "VSNCP_PDN_GATEWAY_UNREACHABLE");
+        sFailCauseMap.put(VSNCP_PDN_GATEWAY_REJECT, "VSNCP_PDN_GATEWAY_REJECT");
+        sFailCauseMap.put(VSNCP_INSUFFICIENT_PARAMETERS, "VSNCP_INSUFFICIENT_PARAMETERS");
+        sFailCauseMap.put(VSNCP_RESOURCE_UNAVAILABLE, "VSNCP_RESOURCE_UNAVAILABLE");
+        sFailCauseMap.put(VSNCP_ADMINISTRATIVELY_PROHIBITED, "VSNCP_ADMINISTRATIVELY_PROHIBITED");
+        sFailCauseMap.put(VSNCP_PDN_ID_IN_USE, "VSNCP_PDN_ID_IN_USE");
+        sFailCauseMap.put(VSNCP_SUBSCRIBER_LIMITATION, "VSNCP_SUBSCRIBER_LIMITATION");
+        sFailCauseMap.put(VSNCP_PDN_EXISTS_FOR_THIS_APN, "VSNCP_PDN_EXISTS_FOR_THIS_APN");
+        sFailCauseMap.put(VSNCP_RECONNECT_NOT_ALLOWED, "VSNCP_RECONNECT_NOT_ALLOWED");
+        sFailCauseMap.put(IPV6_PREFIX_UNAVAILABLE, "IPV6_PREFIX_UNAVAILABLE");
+        sFailCauseMap.put(HANDOFF_PREFERENCE_CHANGED, "HANDOFF_PREFERENCE_CHANGED");
         sFailCauseMap.put(OEM_DCFAILCAUSE_1, "OEM_DCFAILCAUSE_1");
         sFailCauseMap.put(OEM_DCFAILCAUSE_2, "OEM_DCFAILCAUSE_2");
         sFailCauseMap.put(OEM_DCFAILCAUSE_3, "OEM_DCFAILCAUSE_3");
diff --git a/telephony/java/android/telephony/SignalStrength.java b/telephony/java/android/telephony/SignalStrength.java
index ad3ca6d..91375bc 100644
--- a/telephony/java/android/telephony/SignalStrength.java
+++ b/telephony/java/android/telephony/SignalStrength.java
@@ -292,14 +292,27 @@
      * Asu is calculated based on 3GPP RSRP. Refer to 3GPP 27.007 (Ver 10.3.0) Sec 8.69
      *
      * @return RSSI in ASU 0..31, 99, or UNAVAILABLE
+     *
+     * @deprecated this information should be retrieved from
+     *             {@link CellSignalStrengthGsm#getAsuLevel}.
+     * @see android.telephony#CellSignalStrengthGsm
+     * @see android.telephony.SignalStrength#getCellSignalStrengths
      */
+    @Deprecated
     public int getGsmSignalStrength() {
         return mGsm.getAsuLevel();
     }
 
     /**
      * Get the GSM bit error rate (0-7, 99) as defined in TS 27.007 8.5
+     *
+     * @deprecated this information should be retrieved from
+     *             {@link CellSignalStrengthGsm#getBitErrorRate}.
+     *
+     * @see android.telephony#CellSignalStrengthGsm
+     * @see android.telephony.SignalStrength#getCellSignalStrengths()
      */
+    @Deprecated
     public int getGsmBitErrorRate() {
         return mGsm.getBitErrorRate();
     }
@@ -308,14 +321,28 @@
      * Get the CDMA RSSI value in dBm
      *
      * @return the CDMA RSSI value or {@link #INVALID} if invalid
+     *
+     * @deprecated this information should be retrieved from
+     *             {@link CellSignalStrengthCdma#getCdmaDbm}.
+     *
+     * @see android.telephony#CellSignalStrengthCdma
+     * @see android.telephony.SignalStrength#getCellSignalStrengths()
      */
+    @Deprecated
     public int getCdmaDbm() {
         return mCdma.getCdmaDbm();
     }
 
     /**
      * Get the CDMA Ec/Io value in dB*10
+     *
+     * @deprecated this information should be retrieved from
+     *             {@link CellSignalStrengthCdma#getCdmaEcio}.
+     *
+     * @see android.telephony#CellSignalStrengthCdma
+     * @see android.telephony.SignalStrength#getCellSignalStrengths()
      */
+    @Deprecated
     public int getCdmaEcio() {
         return mCdma.getCdmaEcio();
     }
@@ -324,51 +351,112 @@
      * Get the EVDO RSSI value in dBm
      *
      * @return the EVDO RSSI value or {@link #INVALID} if invalid
+     *
+     * @deprecated this information should be retrieved from
+     *             {@link CellSignalStrengthCdma#getEvdoDbm}.
+     *
+     * @see android.telephony#CellSignalStrengthCdma
+     * @see android.telephony.SignalStrength#getCellSignalStrengths()
      */
+    @Deprecated
     public int getEvdoDbm() {
         return mCdma.getEvdoDbm();
     }
 
     /**
      * Get the EVDO Ec/Io value in dB*10
+     *
+     * @deprecated this information should be retrieved from
+     *             {@link CellSignalStrengthCdma#getEvdoEcio}.
+     *
+     * @see android.telephony#CellSignalStrengthCdma
+     * @see android.telephony.SignalStrength#getCellSignalStrengths()
      */
+    @Deprecated
     public int getEvdoEcio() {
         return mCdma.getEvdoEcio();
     }
 
     /**
      * Get the signal to noise ratio. Valid values are 0-8. 8 is the highest.
+     *
+     * @deprecated this information should be retrieved from
+     *             {@link CellSignalStrengthCdma#getEvdoSnr}.
+     *
+     * @see android.telephony#CellSignalStrengthCdma
+     * @see android.telephony.SignalStrength#getCellSignalStrengths()
      */
+    @Deprecated
     public int getEvdoSnr() {
         return mCdma.getEvdoSnr();
     }
 
-    /** @hide */
-    @UnsupportedAppUsage
+    /**
+     * @deprecated this information should be retrieved from
+     *             {@link CellSignalStrengthLte#getRssi}.
+     *
+     * @see android.telephony#CellSignalStrengthLte
+     * @see android.telephony.SignalStrength#getCellSignalStrengths()
+     * @hide
+     */
+    @Deprecated
+    @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.P)
     public int getLteSignalStrength() {
         return mLte.getRssi();
     }
 
-    /** @hide */
-    @UnsupportedAppUsage
+    /**
+     * @deprecated this information should be retrieved from
+     *             {@link CellSignalStrengthLte#getRsrp}.
+     *
+     * @see android.telephony#CellSignalStrengthLte
+     * @see android.telephony.SignalStrength#getCellSignalStrengths()
+     * @hide
+     */
+    @Deprecated
+    @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.P)
     public int getLteRsrp() {
         return mLte.getRsrp();
     }
 
-    /** @hide */
-    @UnsupportedAppUsage
+    /**
+     * @deprecated this information should be retrieved from
+     *             {@link CellSignalStrengthLte#getRsrq}.
+     *
+     * @see android.telephony#CellSignalStrengthLte
+     * @see android.telephony.SignalStrength#getCellSignalStrengths()
+     * @hide
+     */
+    @Deprecated
+    @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.P)
     public int getLteRsrq() {
         return mLte.getRsrq();
     }
 
-    /** @hide */
-    @UnsupportedAppUsage
+    /**
+     * @deprecated this information should be retrieved from
+     *             {@link CellSignalStrengthLte#getRssnr}.
+     *
+     * @see android.telephony#CellSignalStrengthLte
+     * @see android.telephony.SignalStrength#getCellSignalStrengths()
+     * @hide
+     */
+    @Deprecated
+    @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.P)
     public int getLteRssnr() {
         return mLte.getRssnr();
     }
 
-    /** @hide */
-    @UnsupportedAppUsage
+    /**
+     * @deprecated this information should be retrieved from
+     *             {@link CellSignalStrengthLte#getCqi}.
+     *
+     * @see android.telephony#CellSignalStrengthLte
+     * @see android.telephony.SignalStrength#getCellSignalStrengths()
+     * @hide
+     */
+    @Deprecated
+    @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.P)
     public int getLteCqi() {
         return mLte.getCqi();
     }
@@ -391,11 +479,17 @@
     }
 
     /**
-     * Get the signal level as an asu value between 0..31, 99 is unknown
+     * Get the signal level as an asu value with a range dependent on the underlying technology.
      *
+     * @deprecated this information should be retrieved from
+     *             {@link CellSignalStrength#getAsuLevel}. Because the levels vary by technology,
+     *             this method is misleading and should not be used.
+     * @see android.telephony#CellSignalStrength
+     * @see android.telephony.SignalStrength#getCellSignalStrengths
      * @hide
      */
-    @UnsupportedAppUsage
+    @Deprecated
+    @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.P)
     public int getAsuLevel() {
         return getPrimary().getAsuLevel();
     }
@@ -403,9 +497,15 @@
     /**
      * Get the signal strength as dBm
      *
+     * @deprecated this information should be retrieved from
+     *             {@link CellSignalStrength#getDbm()}. Because the levels vary by technology,
+     *             this method is misleading and should not be used.
+     * @see android.telephony#CellSignalStrength
+     * @see android.telephony.SignalStrength#getCellSignalStrengths
      * @hide
      */
-    @UnsupportedAppUsage
+    @Deprecated
+    @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.P)
     public int getDbm() {
         return getPrimary().getDbm();
     }
@@ -413,9 +513,15 @@
     /**
      * Get Gsm signal strength as dBm
      *
+     * @deprecated this information should be retrieved from
+     *             {@link CellSignalStrengthGsm#getDbm}.
+     *
+     * @see android.telephony#CellSignalStrengthGsm
+     * @see android.telephony.SignalStrength#getCellSignalStrengths()
      * @hide
      */
-    @UnsupportedAppUsage
+    @Deprecated
+    @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.P)
     public int getGsmDbm() {
         return mGsm.getDbm();
     }
@@ -423,9 +529,15 @@
     /**
      * Get gsm as level 0..4
      *
+     * @deprecated this information should be retrieved from
+     *             {@link CellSignalStrengthGsm#getLevel}.
+     *
+     * @see android.telephony#CellSignalStrengthGsm
+     * @see android.telephony.SignalStrength#getCellSignalStrengths()
      * @hide
      */
-    @UnsupportedAppUsage
+    @Deprecated
+    @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.P)
     public int getGsmLevel() {
         return mGsm.getLevel();
     }
@@ -433,9 +545,15 @@
     /**
      * Get the gsm signal level as an asu value between 0..31, 99 is unknown
      *
+     * @deprecated this information should be retrieved from
+     *             {@link CellSignalStrengthGsm#getAsuLevel}.
+     *
+     * @see android.telephony#CellSignalStrengthGsm
+     * @see android.telephony.SignalStrength#getCellSignalStrengths()
      * @hide
      */
-    @UnsupportedAppUsage
+    @Deprecated
+    @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.P)
     public int getGsmAsuLevel() {
         return mGsm.getAsuLevel();
     }
@@ -443,9 +561,15 @@
     /**
      * Get cdma as level 0..4
      *
+     * @deprecated this information should be retrieved from
+     *             {@link CellSignalStrengthCdma#getLevel}.
+     *
+     * @see android.telephony#CellSignalStrengthCdma
+     * @see android.telephony.SignalStrength#getCellSignalStrengths()
      * @hide
      */
-    @UnsupportedAppUsage
+    @Deprecated
+    @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.P)
     public int getCdmaLevel() {
         return mCdma.getLevel();
     }
@@ -453,9 +577,17 @@
     /**
      * Get the cdma signal level as an asu value between 0..31, 99 is unknown
      *
+     * @deprecated this information should be retrieved from
+     *             {@link CellSignalStrengthCdma#getAsuLevel}. Since there is no definition of
+     *             ASU for CDMA, the resultant value is Android-specific and is not recommended
+     *             for use.
+     *
+     * @see android.telephony#CellSignalStrengthCdma
+     * @see android.telephony.SignalStrength#getCellSignalStrengths()
      * @hide
      */
-    @UnsupportedAppUsage
+    @Deprecated
+    @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.P)
     public int getCdmaAsuLevel() {
         return mCdma.getAsuLevel();
     }
@@ -463,9 +595,15 @@
     /**
      * Get Evdo as level 0..4
      *
+     * @deprecated this information should be retrieved from
+     *             {@link CellSignalStrengthCdma#getEvdoLevel}.
+     *
+     * @see android.telephony#CellSignalStrengthCdma
+     * @see android.telephony.SignalStrength#getCellSignalStrengths()
      * @hide
      */
-    @UnsupportedAppUsage
+    @Deprecated
+    @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.P)
     public int getEvdoLevel() {
         return mCdma.getEvdoLevel();
     }
@@ -473,9 +611,17 @@
     /**
      * Get the evdo signal level as an asu value between 0..31, 99 is unknown
      *
+     * @deprecated this information should be retrieved from
+     *             {@link CellSignalStrengthCdma#getEvdoAsuLevel}. Since there is no definition of
+     *             ASU for EvDO, the resultant value is Android-specific and is not recommended
+     *             for use.
+     *
+     * @see android.telephony#CellSignalStrengthCdma
+     * @see android.telephony.SignalStrength#getCellSignalStrengths()
      * @hide
      */
-    @UnsupportedAppUsage
+    @Deprecated
+    @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.P)
     public int getEvdoAsuLevel() {
         return mCdma.getEvdoAsuLevel();
     }
@@ -483,9 +629,15 @@
     /**
      * Get LTE as dBm
      *
+     * @deprecated this information should be retrieved from
+     *             {@link CellSignalStrengthLte#getDbm}.
+     *
+     * @see android.telephony#CellSignalStrengthLte
+     * @see android.telephony.SignalStrength#getCellSignalStrengths()
      * @hide
      */
-    @UnsupportedAppUsage
+    @Deprecated
+    @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.P)
     public int getLteDbm() {
         return mLte.getRsrp();
     }
@@ -493,9 +645,15 @@
     /**
      * Get LTE as level 0..4
      *
+     * @deprecated this information should be retrieved from
+     *             {@link CellSignalStrengthLte#getLevel}.
+     *
+     * @see android.telephony#CellSignalStrengthLte
+     * @see android.telephony.SignalStrength#getCellSignalStrengths()
      * @hide
      */
-    @UnsupportedAppUsage
+    @Deprecated
+    @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.P)
     public int getLteLevel() {
         return mLte.getLevel();
     }
@@ -504,26 +662,46 @@
      * Get the LTE signal level as an asu value between 0..97, 99 is unknown
      * Asu is calculated based on 3GPP RSRP. Refer to 3GPP 27.007 (Ver 10.3.0) Sec 8.69
      *
+     * @deprecated this information should be retrieved from
+     *             {@link CellSignalStrengthLte#getAsuLevel}.
+     *
+     * @see android.telephony#CellSignalStrengthLte
+     * @see android.telephony.SignalStrength#getCellSignalStrengths()
      * @hide
      */
-    @UnsupportedAppUsage
+    @Deprecated
+    @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.P)
     public int getLteAsuLevel() {
         return mLte.getAsuLevel();
     }
 
     /**
      * @return true if this is for GSM
+     *
+     * @deprecated This method returns true if there are any 3gpp type SignalStrength elements in
+     *             this SignalStrength report or if the report contains no valid SignalStrength
+     *             information. Instead callers should use
+     *             {@link android.telephony.SignalStrength#getCellSignalStrengths
+     *             getCellSignalStrengths()} to determine which types of information are contained
+     *             in the SignalStrength report.
      */
+    @Deprecated
     public boolean isGsm() {
         return !(getPrimary() instanceof CellSignalStrengthCdma);
     }
 
     /**
-     * @return get TD_SCDMA dbm
+     * @return get TD-SCDMA dBm
      *
+     * @deprecated this information should be retrieved from
+     *             {@link CellSignalStrengthTdscdma#getDbm}.
+     *
+     * @see android.telephony#CellSignalStrengthTdscdma
+     * @see android.telephony.SignalStrength#getCellSignalStrengths()
      * @hide
      */
-    @UnsupportedAppUsage
+    @Deprecated
+    @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.P)
     public int getTdScdmaDbm() {
         return mTdscdma.getRscp();
     }
@@ -534,9 +712,15 @@
      * INT_MAX: 0x7FFFFFFF denotes invalid value
      * Reference: 3GPP TS 25.123, section 9.1.1.1
      *
+     * @deprecated this information should be retrieved from
+     *             {@link CellSignalStrengthTdscdma#getLevel}.
+     *
+     * @see android.telephony#CellSignalStrengthTdscdma
+     * @see android.telephony.SignalStrength#getCellSignalStrengths()
      * @hide
      */
-    @UnsupportedAppUsage
+    @Deprecated
+    @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.P)
     public int getTdScdmaLevel() {
         return mTdscdma.getLevel();
      }
@@ -544,18 +728,30 @@
     /**
      * Get the TD-SCDMA signal level as an asu value.
      *
+     * @deprecated this information should be retrieved from
+     *             {@link CellSignalStrengthTdscdma#getAsuLevel}.
+     *
+     * @see android.telephony#CellSignalStrengthTdscdma
+     * @see android.telephony.SignalStrength#getCellSignalStrengths()
      * @hide
      */
-    @UnsupportedAppUsage
+    @Deprecated
+    @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.P)
     public int getTdScdmaAsuLevel() {
         return mTdscdma.getAsuLevel();
     }
 
     /**
-     * Gets WCDMA RSCP as a dbm value between -120 and -24, as defined in TS 27.007 8.69.
+     * Gets WCDMA RSCP as a dBm value between -120 and -24, as defined in TS 27.007 8.69.
      *
+     * @deprecated this information should be retrieved from
+     *             {@link CellSignalStrengthWcdma#getRscp}.
+     *
+     * @see android.telephony#CellSignalStrengthWcdma
+     * @see android.telephony.SignalStrength#getCellSignalStrengths()
      * @hide
      */
+    @Deprecated
     public int getWcdmaRscp() {
         return mWcdma.getRscp();
     }
@@ -563,8 +759,14 @@
     /**
      * Get the WCDMA signal level as an ASU value between 0-96, 255 is unknown
      *
+     * @deprecated this information should be retrieved from
+     *             {@link CellSignalStrengthWcdma#getAsuLevel}.
+     *
+     * @see android.telephony#CellSignalStrengthWcdma
+     * @see android.telephony.SignalStrength#getCellSignalStrengths()
      * @hide
      */
+    @Deprecated
     public int getWcdmaAsuLevel() {
         /*
          * 3GPP 27.007 (Ver 10.3.0) Sec 8.69
@@ -578,10 +780,16 @@
     }
 
     /**
-     * Gets WCDMA signal strength as a dbm value between -120 and -24, as defined in TS 27.007 8.69.
+     * Gets WCDMA signal strength as a dBm value between -120 and -24, as defined in TS 27.007 8.69.
      *
+     * @deprecated this information should be retrieved from
+     *             {@link CellSignalStrengthWcdma#getDbm}.
+     *
+     * @see android.telephony#CellSignalStrengthWcdma
+     * @see android.telephony.SignalStrength#getCellSignalStrengths()
      * @hide
      */
+    @Deprecated
     public int getWcdmaDbm() {
         return mWcdma.getDbm();
     }
@@ -589,13 +797,19 @@
     /**
      * Get WCDMA as level 0..4
      *
+     * @deprecated this information should be retrieved from
+     *             {@link CellSignalStrengthWcdma#getDbm}.
+     *
+     * @see android.telephony#CellSignalStrengthWcdma
+     * @see android.telephony.SignalStrength#getCellSignalStrengths()
      * @hide
      */
+    @Deprecated
     public int getWcdmaLevel() {
         return mWcdma.getLevel();
     }
 
-   /**
+    /**
      * @return hash code
      */
     @Override
@@ -639,9 +853,13 @@
      * Set SignalStrength based on intent notifier map
      *
      * @param m intent notifier map
+     *
+     * @deprecated this method relies on non-stable implementation details, and full access to
+     *             internal storage is available via {@link getCellSignalStrengths()}.
      * @hide
      */
-    @UnsupportedAppUsage
+    @Deprecated
+    @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.P)
     private void setFromNotifierBundle(Bundle m) {
         mCdma = m.getParcelable("Cdma");
         mGsm = m.getParcelable("Gsm");
@@ -654,9 +872,13 @@
      * Set intent notifier Bundle based on SignalStrength
      *
      * @param m intent notifier Bundle
+     *
+     * @deprecated this method relies on non-stable implementation details, and full access to
+     *             internal storage is available via {@link getCellSignalStrengths()}.
      * @hide
      */
-    @UnsupportedAppUsage
+    @Deprecated
+    @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.P)
     public void fillInNotifierBundle(Bundle m) {
         m.putParcelable("Cdma", mCdma);
         m.putParcelable("Gsm", mGsm);
diff --git a/telephony/java/android/telephony/SmsManager.java b/telephony/java/android/telephony/SmsManager.java
index 1378bb0..d777bf1 100644
--- a/telephony/java/android/telephony/SmsManager.java
+++ b/telephony/java/android/telephony/SmsManager.java
@@ -22,6 +22,10 @@
 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;
@@ -32,6 +36,7 @@
 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;
@@ -61,6 +66,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 +346,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 +395,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 +1006,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.
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 8053353..e710e0e 100644
--- a/telephony/java/android/telephony/TelephonyManager.java
+++ b/telephony/java/android/telephony/TelephonyManager.java
@@ -93,6 +93,7 @@
 import java.util.ArrayList;
 import java.util.Collections;
 import java.util.List;
+import java.util.Locale;
 import java.util.Map;
 import java.util.concurrent.Executor;
 import java.util.regex.Matcher;
@@ -8585,12 +8586,25 @@
     }
 
 
-    /** @hide */
-    public String getLocaleFromDefaultSim() {
+    /**
+     * Returns a well-formed IETF BCP 47 language tag representing the locale from the SIM, e.g,
+     * en-US. Returns {@code null} if no locale could be derived from subscriptions.
+     *
+     * <p>Requires Permission:
+     * {@link android.Manifest.permission#READ_PRIVILEGED_PHONE_STATE READ_PRIVILEGED_PHONE_STATE}
+     *
+     * @see Locale#toLanguageTag()
+     * @see Locale#forLanguageTag(String)
+     *
+     * @hide
+     */
+    @SystemApi
+    @RequiresPermission(android.Manifest.permission.READ_PRIVILEGED_PHONE_STATE)
+    @Nullable public String getSimLocale() {
         try {
             final ITelephony telephony = getITelephony();
             if (telephony != null) {
-                return telephony.getLocaleFromDefaultSim();
+                return telephony.getSimLocaleForSubscriber(getSubId());
             }
         } catch (RemoteException ex) {
         }
@@ -8598,6 +8612,22 @@
     }
 
     /**
+     * TODO delete after SuW migrates to new API.
+     * @hide
+     */
+    public String getLocaleFromDefaultSim() {
+        try {
+            final ITelephony telephony = getITelephony();
+            if (telephony != null) {
+                return telephony.getSimLocaleForSubscriber(getSubId());
+            }
+        } catch (RemoteException ex) {
+        }
+        return null;
+    }
+
+
+    /**
      * Requests the modem activity info. The recipient will place the result
      * in `result`.
      * @param result The object on which the recipient will send the resulting
diff --git a/telephony/java/android/telephony/data/ApnSetting.java b/telephony/java/android/telephony/data/ApnSetting.java
index 8d148c3..0e69530 100644
--- a/telephony/java/android/telephony/data/ApnSetting.java
+++ b/telephony/java/android/telephony/data/ApnSetting.java
@@ -140,15 +140,19 @@
     @Retention(RetentionPolicy.SOURCE)
     public @interface AuthType {}
 
-    // Possible values for protocol.
-    /** Protocol type for IP. */
+    // Possible values for protocol which is defined in TS 27.007 section 10.1.1.
+    /** Internet protocol. */
     public static final int PROTOCOL_IP = 0;
-    /** Protocol type for IPV6. */
+    /** Internet protocol, version 6. */
     public static final int PROTOCOL_IPV6 = 1;
-    /** Protocol type for IPV4V6. */
+    /** Virtual PDP type introduced to handle dual IP stack UE capability. */
     public static final int PROTOCOL_IPV4V6 = 2;
-    /** Protocol type for PPP. */
+    /** Point to point protocol. */
     public static final int PROTOCOL_PPP = 3;
+    /** Transfer of Non-IP data to external packet data network. */
+    public static final int PROTOCOL_NON_IP = 4;
+    /** Transfer of Unstructured data to the Data Network via N6. */
+    public static final int PROTOCOL_UNSTRUCTURED = 5;
 
     /** @hide */
     @IntDef(prefix = { "PROTOCOL_" }, value = {
@@ -156,6 +160,8 @@
         PROTOCOL_IPV6,
         PROTOCOL_IPV4V6,
         PROTOCOL_PPP,
+        PROTOCOL_NON_IP,
+        PROTOCOL_UNSTRUCTURED,
     })
     @Retention(RetentionPolicy.SOURCE)
     public @interface ProtocolType {}
@@ -217,11 +223,15 @@
         PROTOCOL_STRING_MAP.put("IPV6", PROTOCOL_IPV6);
         PROTOCOL_STRING_MAP.put("IPV4V6", PROTOCOL_IPV4V6);
         PROTOCOL_STRING_MAP.put("PPP", PROTOCOL_PPP);
+        PROTOCOL_STRING_MAP.put("NON-IP", PROTOCOL_NON_IP);
+        PROTOCOL_STRING_MAP.put("UNSTRUCTURED", PROTOCOL_UNSTRUCTURED);
         PROTOCOL_INT_MAP = new ArrayMap<Integer, String>();
         PROTOCOL_INT_MAP.put(PROTOCOL_IP, "IP");
         PROTOCOL_INT_MAP.put(PROTOCOL_IPV6, "IPV6");
         PROTOCOL_INT_MAP.put(PROTOCOL_IPV4V6, "IPV4V6");
         PROTOCOL_INT_MAP.put(PROTOCOL_PPP, "PPP");
+        PROTOCOL_INT_MAP.put(PROTOCOL_NON_IP, "NON-IP");
+        PROTOCOL_INT_MAP.put(PROTOCOL_UNSTRUCTURED, "UNSTRUCTURED");
 
         MVNO_TYPE_STRING_MAP = new ArrayMap<String, Integer>();
         MVNO_TYPE_STRING_MAP.put("spn", MVNO_TYPE_SPN);
diff --git a/telephony/java/android/telephony/data/DataCallResponse.java b/telephony/java/android/telephony/data/DataCallResponse.java
index 25f5133..294c79b 100644
--- a/telephony/java/android/telephony/data/DataCallResponse.java
+++ b/telephony/java/android/telephony/data/DataCallResponse.java
@@ -52,8 +52,7 @@
      * @param status Data call fail cause. 0 indicates no error.
      * @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 = active/physical link down,
-     *               2 = active/physical link up.
+     * @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 ifname The network interface name.
@@ -124,7 +123,7 @@
     public int getCallId() { return mCid; }
 
     /**
-     * @return 0 = inactive, 1 = active/physical link down, 2 = active/physical link up.
+     * @return 0 = inactive, 1 = dormant, 2 = active.
      */
     public int getActive() { return mActive; }
 
diff --git a/telephony/java/android/telephony/euicc/EuiccManager.java b/telephony/java/android/telephony/euicc/EuiccManager.java
index 0e5c71d..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
@@ -129,6 +115,66 @@
             "android.telephony.euicc.action.RESOLVE_ERROR";
 
     /**
+     * Intent action sent by system apps (such as the Settings app) to the Telephony framework to
+     * enable or disable a subscription. Must be accompanied with {@link #EXTRA_SUBSCRIPTION_ID} and
+     * {@link #EXTRA_ENABLE_SUBSCRIPTION}.
+     *
+     * <p>Unlike {@link #switchToSubscription(int, PendingIntent)}, using this action allows the
+     * underlying eUICC service (i.e. the LPA app) to control the UI experience during this
+     * operation. The action is received by the Telephony framework, which in turn selects and
+     * launches an appropriate LPA activity to present UI to the user. For example, the activity may
+     * show a confirmation dialog, a progress dialog, or an error dialog when necessary.
+     *
+     * <p>The launched activity will immediately finish with
+     * {@link android.app.Activity#RESULT_CANCELED} if {@link #isEnabled} is false.
+     *
+     * @hide
+     */
+    @SystemApi
+    public static final String ACTION_TOGGLE_SUBSCRIPTION_PRIVILEGED =
+            "android.telephony.euicc.action.TOGGLE_SUBSCRIPTION_PRIVILEGED";
+
+    /**
+     * Intent action sent by system apps (such as the Settings app) to the Telephony framework to
+     * delete a subscription. Must be accompanied with {@link #EXTRA_SUBSCRIPTION_ID}.
+     *
+     * <p>Unlike {@link #deleteSubscription(int, PendingIntent)}, using this action allows the
+     * underlying eUICC service (i.e. the LPA app) to control the UI experience during this
+     * operation. The action is received by the Telephony framework, which in turn selects and
+     * launches an appropriate LPA activity to present UI to the user. For example, the activity may
+     * show a confirmation dialog, a progress dialog, or an error dialog when necessary.
+     *
+     * <p>The launched activity will immediately finish with
+     * {@link android.app.Activity#RESULT_CANCELED} if {@link #isEnabled} is false.
+     *
+     * @hide
+     */
+    @SystemApi
+    public static final String ACTION_DELETE_SUBSCRIPTION_PRIVILEGED =
+            "android.telephony.euicc.action.DELETE_SUBSCRIPTION_PRIVILEGED";
+
+    /**
+     * Intent action sent by system apps (such as the Settings app) to the Telephony framework to
+     * rename a subscription. Must be accompanied with {@link #EXTRA_SUBSCRIPTION_ID} and
+     * {@link #EXTRA_SUBSCRIPTION_NICKNAME}.
+     *
+     * <p>Unlike {@link #updateSubscriptionNickname(int, String, PendingIntent)}, using this action
+     * allows the the underlying eUICC service (i.e. the LPA app) to control the UI experience
+     * during this operation. The action is received by the Telephony framework, which in turn
+     * selects and launches an appropriate LPA activity to present UI to the user. For example, the
+     * activity may show a confirmation dialog, a progress dialog, or an error dialog when
+     * necessary.
+     *
+     * <p>The launched activity will immediately finish with
+     * {@link android.app.Activity#RESULT_CANCELED} if {@link #isEnabled} is false.
+     *
+     * @hide
+     */
+    @SystemApi
+    public static final String ACTION_RENAME_SUBSCRIPTION_PRIVILEGED =
+            "android.telephony.euicc.action.RENAME_SUBSCRIPTION_PRIVILEGED";
+
+    /**
      * Result code for an operation indicating that the operation succeeded.
      */
     public static final int EMBEDDED_SUBSCRIPTION_RESULT_OK = 0;
@@ -219,6 +265,37 @@
             "android.telephony.euicc.extra.FORCE_PROVISION";
 
     /**
+     * Key for an extra set on privileged actions {@link #ACTION_TOGGLE_SUBSCRIPTION_PRIVILEGED},
+     * {@link #ACTION_DELETE_SUBSCRIPTION_PRIVILEGED}, and
+     * {@link #ACTION_RENAME_SUBSCRIPTION_PRIVILEGED} providing the ID of the targeted subscription.
+     *
+     * @hide
+     */
+    @SystemApi
+    public static final String EXTRA_SUBSCRIPTION_ID =
+            "android.telephony.euicc.extra.SUBSCRIPTION_ID";
+
+    /**
+     * Key for an extra set on {@link #ACTION_TOGGLE_SUBSCRIPTION_PRIVILEGED} providing a boolean
+     * value of whether to enable or disable the targeted subscription.
+     *
+     * @hide
+     */
+    @SystemApi
+    public static final String EXTRA_ENABLE_SUBSCRIPTION =
+            "android.telephony.euicc.extra.ENABLE_SUBSCRIPTION";
+
+    /**
+     * Key for an extra set on {@link #ACTION_RENAME_SUBSCRIPTION_PRIVILEGED} providing a new
+     * nickname for the targeted subscription.
+     *
+     * @hide
+     */
+    @SystemApi
+    public static final String EXTRA_SUBSCRIPTION_NICKNAME =
+            "android.telephony.euicc.extra.SUBSCRIPTION_NICKNAME";
+
+    /**
      * Optional meta-data attribute for a carrier app providing an icon to use to represent the
      * carrier. If not provided, the app's launcher icon will be used as a fallback.
      */
@@ -234,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{}
 
@@ -269,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/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 5736a46..8237d39 100644
--- a/telephony/java/com/android/internal/telephony/ITelephony.aidl
+++ b/telephony/java/com/android/internal/telephony/ITelephony.aidl
@@ -1178,12 +1178,12 @@
     void factoryReset(int subId);
 
     /**
-     * An estimate of the users's current locale based on the default SIM.
+     * Returns users's current locale based on the SIM.
      *
      * The returned string will be a well formed BCP-47 language tag, or {@code null}
      * if no locale could be derived.
      */
-    String getLocaleFromDefaultSim();
+    String getSimLocaleForSubscriber(int subId);
 
     /**
      * Requests the modem activity info asynchronously.
@@ -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/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/api/current.txt b/test-mock/api/current.txt
index a181bc38..1110790 100644
--- a/test-mock/api/current.txt
+++ b/test-mock/api/current.txt
@@ -32,7 +32,6 @@
 
   public class MockContext extends android.content.Context {
     ctor public MockContext();
-    method public boolean bindIsolatedService(android.content.Intent, android.content.ServiceConnection, int, String);
     method public boolean bindService(android.content.Intent, android.content.ServiceConnection, int);
     method public int checkCallingOrSelfPermission(String);
     method public int checkCallingOrSelfUriPermission(android.net.Uri, int);
@@ -81,7 +80,6 @@
     method public java.io.File getNoBackupFilesDir();
     method public java.io.File getObbDir();
     method public java.io.File[] getObbDirs();
-    method public String getOpPackageName();
     method public String getPackageCodePath();
     method public android.content.pm.PackageManager getPackageManager();
     method public String getPackageName();
@@ -137,7 +135,6 @@
     method public boolean stopService(android.content.Intent);
     method public void unbindService(android.content.ServiceConnection);
     method public void unregisterReceiver(android.content.BroadcastReceiver);
-    method public void updateServiceGroup(android.content.ServiceConnection, int, int);
   }
 
   @Deprecated public class MockCursor implements android.database.Cursor {
diff --git a/tests/DexLoggerIntegrationTests/Android.mk b/tests/DexLoggerIntegrationTests/Android.mk
index ee2ec0a..979d13a 100644
--- a/tests/DexLoggerIntegrationTests/Android.mk
+++ b/tests/DexLoggerIntegrationTests/Android.mk
@@ -29,6 +29,35 @@
 dexloggertest_jar := $(LOCAL_BUILT_MODULE)
 
 
+# Also build a native library that the test app can dynamically load
+
+include $(CLEAR_VARS)
+
+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)
+LOCAL_SDK_VERSION := 28
+LOCAL_NDK_STL_VARIANT := c++_static
+
+include $(BUILD_SHARED_LIBRARY)
+
+dexloggertest_so := $(LOCAL_BUILT_MODULE)
+
+# And a standalone native executable that we can exec.
+
+include $(CLEAR_VARS)
+
+LOCAL_MODULE_TAGS := tests
+LOCAL_MODULE := DexLoggerNativeExecutable
+LOCAL_SRC_FILES := src/cpp/test_executable.cpp
+
+include $(BUILD_EXECUTABLE)
+
+dexloggertest_executable := $(LOCAL_BUILT_MODULE)
+
 # Build the test app itself
 
 include $(CLEAR_VARS)
@@ -37,14 +66,18 @@
 LOCAL_PACKAGE_NAME := DexLoggerIntegrationTests
 LOCAL_SDK_VERSION := current
 LOCAL_COMPATIBILITY_SUITE := device-tests
-LOCAL_CERTIFICATE := platform
+LOCAL_CERTIFICATE := shared
 LOCAL_SRC_FILES := $(call all-java-files-under, src/com/android/server/pm)
 
 LOCAL_STATIC_JAVA_LIBRARIES := \
     android-support-test \
     truth-prebuilt \
 
-# This gets us the javalib.jar built by DexLoggerTestLibrary above.
-LOCAL_JAVA_RESOURCE_FILES := $(dexloggertest_jar)
+# 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)
 
 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 75ee089..d68769b 100644
--- a/tests/DexLoggerIntegrationTests/src/com/android/server/pm/dex/DexLoggerIntegrationTests.java
+++ b/tests/DexLoggerIntegrationTests/src/com/android/server/pm/dex/DexLoggerIntegrationTests.java
@@ -17,6 +17,7 @@
 package com.android.server.pm.dex;
 
 import static com.google.common.truth.Truth.assertThat;
+import static com.google.common.truth.Truth.assertWithMessage;
 
 import android.app.UiAutomation;
 import android.content.Context;
@@ -25,6 +26,7 @@
 import android.support.test.InstrumentationRegistry;
 import android.support.test.filters.LargeTest;
 import android.util.EventLog;
+import android.util.EventLog.Event;
 
 import dalvik.system.DexClassLoader;
 
@@ -65,14 +67,13 @@
     // Event log tag used for SNET related events
     private static final int SNET_TAG = 0x534e4554;
 
-    // Subtag used to distinguish dynamic code loading events
-    private static final String DCL_SUBTAG = "dcl";
+    // Subtags used to distinguish dynamic code loading events
+    private static final String DCL_DEX_SUBTAG = "dcl";
+    private static final String DCL_NATIVE_SUBTAG = "dcln";
 
-    // All the tags we care about
-    private static final int[] TAG_LIST = new int[] { SNET_TAG };
-
-    // This is {@code DynamicCodeLoggingService#JOB_ID}
-    private static final int DYNAMIC_CODE_LOGGING_JOB_ID = 2030028;
+    // These are job IDs from DynamicCodeLoggingService
+    private static final int IDLE_LOGGING_JOB_ID = 2030028;
+    private static final int AUDIT_WATCHING_JOB_ID = 203142925;
 
     private static Context sContext;
     private static int sMyUid;
@@ -89,15 +90,20 @@
         // Without this the first test passes and others don't - we don't see new events in the
         // log. The exact reason is unclear.
         EventLog.writeEvent(SNET_TAG, "Dummy event");
+
+        // Audit log messages are throttled by the kernel (at the request of logd) to 5 per
+        // second, so running the tests too quickly in sequence means we lose some and get
+        // spurious failures. Sigh.
+        SystemClock.sleep(1000);
     }
 
     @Test
-    public void testDexLoggerGeneratesEvents() throws Exception {
-        File privateCopyFile = fileForJar("copied.jar");
+    public void testDexLoggerGeneratesEvents_standardClassLoader() throws Exception {
+        File privateCopyFile = privateFile("copied.jar");
         // Obtained via "echo -n copied.jar | sha256sum"
         String expectedNameHash =
                 "1B6C71DB26F36582867432CCA12FB6A517470C9F9AABE9198DD4C5C030D6DC0C";
-        String expectedContentHash = copyAndHashJar(privateCopyFile);
+        String expectedContentHash = copyAndHashResource("/javalib.jar", privateCopyFile);
 
         // Feed the jar to a class loader and make sure it contains what we expect.
         ClassLoader parentClassLoader = sContext.getClass().getClassLoader();
@@ -107,18 +113,18 @@
 
         // And make sure we log events about it
         long previousEventNanos = mostRecentEventTimeNanos();
-        runDexLogger();
+        runDynamicCodeLoggingJob(IDLE_LOGGING_JOB_ID);
 
-        assertDclLoggedSince(previousEventNanos, expectedNameHash, expectedContentHash);
+        assertDclLoggedSince(previousEventNanos, DCL_DEX_SUBTAG,
+                expectedNameHash, expectedContentHash);
     }
 
     @Test
-
     public void testDexLoggerGeneratesEvents_unknownClassLoader() throws Exception {
-        File privateCopyFile = fileForJar("copied2.jar");
+        File privateCopyFile = privateFile("copied2.jar");
         String expectedNameHash =
                 "202158B6A3169D78F1722487205A6B036B3F2F5653FDCFB4E74710611AC7EB93";
-        String expectedContentHash = copyAndHashJar(privateCopyFile);
+        String expectedContentHash = copyAndHashResource("/javalib.jar", privateCopyFile);
 
         // This time make sure an unknown class loader is an ancestor of the class loader we use.
         ClassLoader knownClassLoader = sContext.getClass().getClassLoader();
@@ -129,22 +135,185 @@
 
         // And make sure we log events about it
         long previousEventNanos = mostRecentEventTimeNanos();
-        runDexLogger();
+        runDynamicCodeLoggingJob(IDLE_LOGGING_JOB_ID);
 
-        assertDclLoggedSince(previousEventNanos, expectedNameHash, expectedContentHash);
+        assertDclLoggedSince(previousEventNanos, DCL_DEX_SUBTAG,
+                expectedNameHash, expectedContentHash);
     }
 
-    private static File fileForJar(String name) {
-        return new File(sContext.getDir("jars", Context.MODE_PRIVATE), name);
+    @Test
+    public void testDexLoggerGeneratesEvents_nativeLibrary() throws Exception {
+        File privateCopyFile = privateFile("copied.so");
+        String expectedNameHash =
+                "996223BAD4B4FE75C57A3DEC61DB9C0B38E0A7AD479FC95F33494F4BC55A0F0E";
+        String expectedContentHash =
+                copyAndHashResource("/DexLoggerNativeTestLibrary.so", privateCopyFile);
+
+        System.load(privateCopyFile.toString());
+
+        // Run the job to scan generated audit log entries
+        runDynamicCodeLoggingJob(AUDIT_WATCHING_JOB_ID);
+
+        // And then make sure we log events about it
+        long previousEventNanos = mostRecentEventTimeNanos();
+        runDynamicCodeLoggingJob(IDLE_LOGGING_JOB_ID);
+
+        assertDclLoggedSince(previousEventNanos, DCL_NATIVE_SUBTAG,
+                expectedNameHash, expectedContentHash);
     }
 
-    private static String copyAndHashJar(File copyTo) throws Exception {
+    @Test
+    public void testDexLoggerGeneratesEvents_nativeLibrary_escapedName() throws Exception {
+        // A file name with a space will be escaped in the audit log; verify we un-escape it
+        // correctly.
+        File privateCopyFile = privateFile("second copy.so");
+        String expectedNameHash =
+                "8C39990C560B4F36F83E208E279F678746FE23A790E4C50F92686584EA2041CA";
+        String expectedContentHash =
+                copyAndHashResource("/DexLoggerNativeTestLibrary.so", privateCopyFile);
+
+        System.load(privateCopyFile.toString());
+
+        // Run the job to scan generated audit log entries
+        runDynamicCodeLoggingJob(AUDIT_WATCHING_JOB_ID);
+
+        // And then make sure we log events about it
+        long previousEventNanos = mostRecentEventTimeNanos();
+        runDynamicCodeLoggingJob(IDLE_LOGGING_JOB_ID);
+
+        assertDclLoggedSince(previousEventNanos, DCL_NATIVE_SUBTAG,
+                expectedNameHash, expectedContentHash);
+    }
+
+    @Test
+    public void testDexLoggerGeneratesEvents_nativeExecutable() throws Exception {
+        File privateCopyFile = privateFile("test_executable");
+        String expectedNameHash =
+                "3FBEC3F925A132D18F347F11AE9A5BB8DE1238828F8B4E064AA86EB68BD46DCF";
+        String expectedContentHash =
+                copyAndHashResource("/DexLoggerNativeExecutable", privateCopyFile);
+        assertThat(privateCopyFile.setExecutable(true)).isTrue();
+
+        Process process = Runtime.getRuntime().exec(privateCopyFile.toString());
+        int exitCode = process.waitFor();
+        assertThat(exitCode).isEqualTo(0);
+
+        // Run the job to scan generated audit log entries
+        runDynamicCodeLoggingJob(AUDIT_WATCHING_JOB_ID);
+
+        // And then make sure we log events about it
+        long previousEventNanos = mostRecentEventTimeNanos();
+        runDynamicCodeLoggingJob(IDLE_LOGGING_JOB_ID);
+
+        assertDclLoggedSince(previousEventNanos, DCL_NATIVE_SUBTAG,
+                expectedNameHash, expectedContentHash);
+    }
+
+    @Test
+    public void testDexLoggerGeneratesEvents_spoofed_validFile() throws Exception {
+        File privateCopyFile = privateFile("spoofed");
+
+        String expectedContentHash =
+                copyAndHashResource("/DexLoggerNativeExecutable", privateCopyFile);
+
+        EventLog.writeEvent(EventLog.getTagCode("auditd"),
+                "type=1400 avc: granted { execute_no_trans } "
+                        + "path=\"" + privateCopyFile + "\" "
+                        + "scontext=u:r:untrusted_app_27: "
+                        + "tcontext=u:object_r:app_data_file: "
+                        + "tclass=file ");
+
+        String expectedNameHash =
+                "1CF36F503A02877BB775DC23C1C5A47A95F2684B6A1A83B11795B856D88861E3";
+
+        // Run the job to scan generated audit log entries
+        runDynamicCodeLoggingJob(AUDIT_WATCHING_JOB_ID);
+
+        // And then make sure we log events about it
+        long previousEventNanos = mostRecentEventTimeNanos();
+        runDynamicCodeLoggingJob(IDLE_LOGGING_JOB_ID);
+
+        assertDclLoggedSince(previousEventNanos, DCL_NATIVE_SUBTAG,
+                expectedNameHash, expectedContentHash);
+    }
+
+    @Test
+    public void testDexLoggerGeneratesEvents_spoofed_pathTraversal() throws Exception {
+        File privateDir = privateFile("x").getParentFile();
+
+        // Transform /a/b/c -> /a/b/c/../../.. so we get back to the root
+        File pathTraversalToRoot = privateDir;
+        File root = new File("/");
+        while (!privateDir.equals(root)) {
+            pathTraversalToRoot = new File(pathTraversalToRoot, "..");
+            privateDir = privateDir.getParentFile();
+        }
+
+        File spoofedFile = new File(pathTraversalToRoot, "dev/urandom");
+
+        assertWithMessage("Expected " + spoofedFile + " to be readable")
+                .that(spoofedFile.canRead()).isTrue();
+
+        EventLog.writeEvent(EventLog.getTagCode("auditd"),
+                "type=1400 avc: granted { execute_no_trans } "
+                        + "path=\"" + spoofedFile + "\" "
+                        + "scontext=u:r:untrusted_app_27: "
+                        + "tcontext=u:object_r:app_data_file: "
+                        + "tclass=file ");
+
+        String expectedNameHash =
+                "65528FE876BD676B0DFCC9A8ACA8988E026766F99EEC1E1FB48F46B2F635E225";
+
+        // Run the job to scan generated audit log entries
+        runDynamicCodeLoggingJob(AUDIT_WATCHING_JOB_ID);
+
+        // And then trigger generating DCL events
+        long previousEventNanos = mostRecentEventTimeNanos();
+        runDynamicCodeLoggingJob(IDLE_LOGGING_JOB_ID);
+
+        assertNoDclLoggedSince(previousEventNanos, DCL_NATIVE_SUBTAG, expectedNameHash);
+    }
+
+    @Test
+    public void testDexLoggerGeneratesEvents_spoofed_otherAppFile() throws Exception {
+        File ourPath = sContext.getDatabasePath("android_pay");
+        File targetPath = new File(ourPath.toString()
+                .replace("com.android.frameworks.dexloggertest", "com.google.android.gms"));
+
+        assertWithMessage("Expected " + targetPath + " to not be readable")
+                .that(targetPath.canRead()).isFalse();
+
+        EventLog.writeEvent(EventLog.getTagCode("auditd"),
+                "type=1400 avc: granted { execute_no_trans } "
+                        + "path=\"" + targetPath + "\" "
+                        + "scontext=u:r:untrusted_app_27: "
+                        + "tcontext=u:object_r:app_data_file: "
+                        + "tclass=file ");
+
+        String expectedNameHash =
+                "CBE04E8AB9E7199FC19CBAAF9C774B88E56B3B19E823F2251693380AD6F515E6";
+
+        // Run the job to scan generated audit log entries
+        runDynamicCodeLoggingJob(AUDIT_WATCHING_JOB_ID);
+
+        // And then trigger generating DCL events
+        long previousEventNanos = mostRecentEventTimeNanos();
+        runDynamicCodeLoggingJob(IDLE_LOGGING_JOB_ID);
+
+        assertNoDclLoggedSince(previousEventNanos, DCL_NATIVE_SUBTAG, expectedNameHash);
+    }
+
+    private static File privateFile(String name) {
+        return new File(sContext.getDir("dcl", Context.MODE_PRIVATE), name);
+    }
+
+    private static String copyAndHashResource(String resourcePath, File copyTo) throws Exception {
         MessageDigest hasher = MessageDigest.getInstance("SHA-256");
 
         // Copy the jar from our Java resources to a private data directory
         Class<?> thisClass = DexLoggerIntegrationTests.class;
-        try (InputStream input = thisClass.getResourceAsStream("/javalib.jar");
-                OutputStream output = new FileOutputStream(copyTo)) {
+        try (InputStream input = thisClass.getResourceAsStream(resourcePath);
+             OutputStream output = new FileOutputStream(copyTo)) {
             byte[] buffer = new byte[1024];
             while (true) {
                 int numRead = input.read(buffer);
@@ -166,24 +335,18 @@
         return formatter.toString();
     }
 
-    private static long mostRecentEventTimeNanos() throws Exception {
-        List<EventLog.Event> events = new ArrayList<>();
-
-        EventLog.readEvents(TAG_LIST, events);
-        return events.isEmpty() ? 0 : events.get(events.size() - 1).getTimeNanos();
-    }
-
-    private static void runDexLogger() throws Exception {
-        // This forces {@code DynamicCodeLoggingService} to start now.
-        runCommand("cmd jobscheduler run -f android " + DYNAMIC_CODE_LOGGING_JOB_ID);
+    private static void runDynamicCodeLoggingJob(int jobId) throws Exception {
+        // This forces the DynamicCodeLoggingService job to start now.
+        runCommand("cmd jobscheduler run -f android " + jobId);
         // Wait for the job to have run.
         long startTime = SystemClock.elapsedRealtime();
         while (true) {
             String response = runCommand(
-                    "cmd jobscheduler get-job-state android " + DYNAMIC_CODE_LOGGING_JOB_ID);
+                    "cmd jobscheduler get-job-state android " + jobId);
             if (!response.contains("pending") && !response.contains("active")) {
                 break;
             }
+            // Don't wait forever - if it's taken > 10s then something is very wrong.
             if (SystemClock.elapsedRealtime() - startTime > TimeUnit.SECONDS.toMillis(10)) {
                 throw new AssertionError("Job has not completed: " + response);
             }
@@ -208,37 +371,68 @@
         return response.toString("UTF-8");
     }
 
-    private static void assertDclLoggedSince(long previousEventNanos, String expectedNameHash,
-            String expectedContentHash) throws Exception {
-        List<EventLog.Event> events = new ArrayList<>();
-        EventLog.readEvents(TAG_LIST, events);
-        int found = 0;
-        for (EventLog.Event event : events) {
+    private static long mostRecentEventTimeNanos() throws Exception {
+        List<Event> events = readSnetEvents();
+        return events.isEmpty() ? 0 : events.get(events.size() - 1).getTimeNanos();
+    }
+
+    private static void assertDclLoggedSince(long previousEventNanos, String expectedSubTag,
+            String expectedNameHash, String expectedContentHash) throws Exception {
+        List<String> messages =
+                findMatchingEvents(previousEventNanos, expectedSubTag, expectedNameHash);
+
+        assertWithMessage("Expected exactly one matching log entry").that(messages).hasSize(1);
+        assertThat(messages.get(0)).endsWith(expectedContentHash);
+    }
+
+    private static void assertNoDclLoggedSince(long previousEventNanos, String expectedSubTag,
+            String expectedNameHash) throws Exception {
+        List<String> messages =
+                findMatchingEvents(previousEventNanos, expectedSubTag, expectedNameHash);
+
+        assertWithMessage("Expected no matching log entries").that(messages).isEmpty();
+    }
+
+    private static List<String> findMatchingEvents(long previousEventNanos, String expectedSubTag,
+            String expectedNameHash) throws Exception {
+        List<String> messages = new ArrayList<>();
+
+        for (Event event : readSnetEvents()) {
             if (event.getTimeNanos() <= previousEventNanos) {
                 continue;
             }
-            Object[] data = (Object[]) event.getData();
 
-            // We only care about DCL events that we generated.
-            String subTag = (String) data[0];
-            if (!DCL_SUBTAG.equals(subTag)) {
+            Object data = event.getData();
+            if (!(data instanceof Object[])) {
                 continue;
             }
-            int uid = (int) data[1];
+            Object[] fields = (Object[]) data;
+
+            // We only care about DCL events that we generated.
+            String subTag = (String) fields[0];
+            if (!expectedSubTag.equals(subTag)) {
+                continue;
+            }
+            int uid = (int) fields[1];
             if (uid != sMyUid) {
                 continue;
             }
 
-            String message = (String) data[2];
+            String message = (String) fields[2];
             if (!message.startsWith(expectedNameHash)) {
                 continue;
             }
 
-            assertThat(message).endsWith(expectedContentHash);
-            ++found;
+            messages.add(message);
+            //assertThat(message).endsWith(expectedContentHash);
         }
+        return messages;
+    }
 
-        assertThat(found).isEqualTo(1);
+    private static List<Event> readSnetEvents() throws Exception {
+        List<Event> events = new ArrayList<>();
+        EventLog.readEvents(new int[] { SNET_TAG }, events);
+        return events;
     }
 
     /**
diff --git a/tests/utils/SleepUtils/AlarmService/src/com/android/testing/alarmservice/Alarm.aidl b/tests/DexLoggerIntegrationTests/src/cpp/com_android_dcl_Jni.cpp
similarity index 73%
copy from tests/utils/SleepUtils/AlarmService/src/com/android/testing/alarmservice/Alarm.aidl
copy to tests/DexLoggerIntegrationTests/src/cpp/com_android_dcl_Jni.cpp
index 62a8c48..0608883 100644
--- a/tests/utils/SleepUtils/AlarmService/src/com/android/testing/alarmservice/Alarm.aidl
+++ b/tests/DexLoggerIntegrationTests/src/cpp/com_android_dcl_Jni.cpp
@@ -1,5 +1,5 @@
 /*
- * Copyright (C) 2013 The Android Open Source Project
+ * 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.
@@ -14,10 +14,9 @@
  * limitations under the License.
  */
 
-package com.android.testing.alarmservice;
+#include "jni.h"
 
-interface Alarm {
-    int prepare();
-    int setAlarmAndWait(long timeoutMills);
-    int done();
-}
\ No newline at end of file
+extern "C" jint JNI_OnLoad(JavaVM* /* vm */, void* /* reserved */)
+{
+    return JNI_VERSION_1_6;
+}
diff --git a/tests/utils/SleepUtils/AlarmService/src/com/android/testing/alarmservice/Alarm.aidl b/tests/DexLoggerIntegrationTests/src/cpp/test_executable.cpp
similarity index 73%
copy from tests/utils/SleepUtils/AlarmService/src/com/android/testing/alarmservice/Alarm.aidl
copy to tests/DexLoggerIntegrationTests/src/cpp/test_executable.cpp
index 62a8c48..ad025e6 100644
--- a/tests/utils/SleepUtils/AlarmService/src/com/android/testing/alarmservice/Alarm.aidl
+++ b/tests/DexLoggerIntegrationTests/src/cpp/test_executable.cpp
@@ -1,5 +1,5 @@
 /*
- * Copyright (C) 2013 The Android Open Source Project
+ * 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.
@@ -14,10 +14,7 @@
  * limitations under the License.
  */
 
-package com.android.testing.alarmservice;
-
-interface Alarm {
-    int prepare();
-    int setAlarmAndWait(long timeoutMills);
-    int done();
-}
\ No newline at end of file
+int main() {
+    // This program just has to run, it doesn't need to do anything. So we don't.
+    return 0;
+}
diff --git a/tests/HwAccelerationTest/Android.mk b/tests/HwAccelerationTest/Android.mk
index 11ea954..79072fa 100644
--- a/tests/HwAccelerationTest/Android.mk
+++ b/tests/HwAccelerationTest/Android.mk
@@ -21,6 +21,7 @@
 
 LOCAL_PACKAGE_NAME := HwAccelerationTest
 LOCAL_PRIVATE_PLATFORM_APIS := true
+LOCAL_CERTIFICATE := platform
 
 LOCAL_MODULE_TAGS := tests
 
diff --git a/tests/HwAccelerationTest/AndroidManifest.xml b/tests/HwAccelerationTest/AndroidManifest.xml
index c8f96c9..f330b83 100644
--- a/tests/HwAccelerationTest/AndroidManifest.xml
+++ b/tests/HwAccelerationTest/AndroidManifest.xml
@@ -310,6 +310,15 @@
                 <category android:name="com.android.test.hwui.TEST" />
             </intent-filter>
         </activity>
+
+        <activity
+            android:name="PictureCaptureDemo"
+            android:label="Debug/Picture Capture">
+            <intent-filter>
+                <action android:name="android.intent.action.MAIN" />
+                <category android:name="com.android.test.hwui.TEST" />
+            </intent-filter>
+        </activity>
         
         <activity
                 android:name="SmallCircleActivity"
diff --git a/tests/HwAccelerationTest/src/com/android/test/hwui/PictureCaptureDemo.java b/tests/HwAccelerationTest/src/com/android/test/hwui/PictureCaptureDemo.java
new file mode 100644
index 0000000..029e302
--- /dev/null
+++ b/tests/HwAccelerationTest/src/com/android/test/hwui/PictureCaptureDemo.java
@@ -0,0 +1,153 @@
+/*
+ * 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.test.hwui;
+
+import android.app.Activity;
+import android.graphics.Bitmap;
+import android.graphics.Canvas;
+import android.graphics.Color;
+import android.graphics.Paint;
+import android.graphics.Picture;
+import android.os.Bundle;
+import android.view.SurfaceHolder;
+import android.view.SurfaceView;
+import android.view.View;
+import android.view.ViewDebug;
+import android.webkit.WebChromeClient;
+import android.webkit.WebView;
+import android.webkit.WebViewClient;
+import android.widget.ImageView;
+import android.widget.LinearLayout;
+import android.widget.LinearLayout.LayoutParams;
+import android.widget.ProgressBar;
+
+import java.io.PipedInputStream;
+import java.io.PipedOutputStream;
+import java.util.Random;
+import java.util.concurrent.ExecutorService;
+import java.util.concurrent.Executors;
+import java.util.concurrent.Future;
+
+public class PictureCaptureDemo extends Activity {
+    @Override
+    protected void onCreate(Bundle savedInstanceState) {
+        super.onCreate(savedInstanceState);
+
+        final LinearLayout layout = new LinearLayout(this);
+        layout.setOrientation(LinearLayout.VERTICAL);
+
+        final LinearLayout inner = new LinearLayout(this);
+        inner.setOrientation(LinearLayout.HORIZONTAL);
+        ProgressBar spinner = new ProgressBar(this, null, android.R.attr.progressBarStyleLarge);
+        inner.addView(spinner,
+                new LayoutParams(LayoutParams.WRAP_CONTENT, LayoutParams.WRAP_CONTENT));
+
+        inner.addView(new View(this), new LayoutParams(50, 1));
+
+        Picture picture = new Picture();
+        Canvas canvas = picture.beginRecording(100, 100);
+        canvas.drawColor(Color.RED);
+        Paint paint = new Paint();
+        paint.setTextSize(32);
+        paint.setColor(Color.BLACK);
+        canvas.drawText("Hello", 0, 50, paint);
+        picture.endRecording();
+
+        ImageView iv1 = new ImageView(this);
+        iv1.setImageBitmap(Bitmap.createBitmap(picture, 100, 100, Bitmap.Config.ARGB_8888));
+        inner.addView(iv1, new LayoutParams(LayoutParams.WRAP_CONTENT, LayoutParams.WRAP_CONTENT));
+
+        inner.addView(new View(this), new LayoutParams(50, 1));
+
+        ImageView iv2 = new ImageView(this);
+        iv2.setImageBitmap(Bitmap.createBitmap(picture, 100, 100, Bitmap.Config.HARDWARE));
+        inner.addView(iv2, new LayoutParams(LayoutParams.WRAP_CONTENT, LayoutParams.WRAP_CONTENT));
+
+        layout.addView(inner,
+                new LayoutParams(LayoutParams.MATCH_PARENT, LayoutParams.WRAP_CONTENT));
+        // For testing with a functor in the tree
+        WebView wv = new WebView(this);
+        wv.setWebViewClient(new WebViewClient());
+        wv.setWebChromeClient(new WebChromeClient());
+        wv.loadUrl("https://google.com");
+        layout.addView(wv, new LayoutParams(LayoutParams.MATCH_PARENT, 400));
+
+        SurfaceView mySurfaceView = new SurfaceView(this);
+        layout.addView(mySurfaceView,
+                new LayoutParams(LayoutParams.MATCH_PARENT, 600));
+
+        setContentView(layout);
+
+        mySurfaceView.getHolder().addCallback(new SurfaceHolder.Callback() {
+            private AutoCloseable mStopCapture;
+
+            @Override
+            public void surfaceCreated(SurfaceHolder holder) {
+                final Random rand = new Random();
+                mStopCapture = ViewDebug.startRenderingCommandsCapture(mySurfaceView,
+                        mCaptureThread, (picture) -> {
+                            if (rand.nextInt(20) == 0) {
+                                try {
+                                    Thread.sleep(100);
+                                } catch (InterruptedException e) {
+                                }
+                            }
+                            Canvas canvas = holder.lockCanvas();
+                            if (canvas == null) {
+                                return false;
+                            }
+                            canvas.drawPicture(picture);
+                            holder.unlockCanvasAndPost(canvas);
+                            picture.close();
+                            return true;
+                        });
+            }
+
+            @Override
+            public void surfaceChanged(SurfaceHolder holder, int format, int width, int height) {
+
+            }
+
+            @Override
+            public void surfaceDestroyed(SurfaceHolder holder) {
+                if (mStopCapture != null) {
+                    try {
+                        mStopCapture.close();
+                    } catch (Exception e) {
+                    }
+                    mStopCapture = null;
+                }
+            }
+        });
+    }
+
+    ExecutorService mCaptureThread = Executors.newSingleThreadExecutor();
+    ExecutorService mExecutor = Executors.newSingleThreadExecutor();
+
+    Picture deepCopy(Picture src) {
+        try {
+            PipedInputStream inputStream = new PipedInputStream();
+            PipedOutputStream outputStream = new PipedOutputStream(inputStream);
+            Future<Picture> future = mExecutor.submit(() -> Picture.createFromStream(inputStream));
+            src.writeToStream(outputStream);
+            outputStream.close();
+            return future.get();
+        } catch (Exception ex) {
+            throw new RuntimeException(ex);
+        }
+    }
+}
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/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/tests/RollbackTest/TestApp/src/com/android/tests/rollback/testapp/CrashingMainActivity.java b/tests/RollbackTest/TestApp/src/com/android/tests/rollback/testapp/CrashingMainActivity.java
new file mode 100644
index 0000000..02a439b
--- /dev/null
+++ b/tests/RollbackTest/TestApp/src/com/android/tests/rollback/testapp/CrashingMainActivity.java
@@ -0,0 +1,33 @@
+/*
+ * 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.tests.rollback.testapp;
+
+import android.app.Activity;
+import android.os.Bundle;
+
+/**
+ * A crashing test app for testing apk rollback support.
+ */
+public class CrashingMainActivity extends Activity {
+
+    @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 d3c39f0..030641b 100644
--- a/tests/RollbackTest/src/com/android/tests/rollback/RollbackBroadcastReceiver.java
+++ b/tests/RollbackTest/src/com/android/tests/rollback/RollbackBroadcastReceiver.java
@@ -44,7 +44,6 @@
     RollbackBroadcastReceiver() {
         IntentFilter filter = new IntentFilter();
         filter.addAction(Intent.ACTION_PACKAGE_ROLLBACK_EXECUTED);
-        filter.addDataScheme("package");
         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 c2e735e..13ac4f0 100644
--- a/tests/RollbackTest/src/com/android/tests/rollback/RollbackTest.java
+++ b/tests/RollbackTest/src/com/android/tests/rollback/RollbackTest.java
@@ -22,9 +22,9 @@
 import android.content.Context;
 import android.content.Intent;
 import android.content.IntentFilter;
+import android.content.rollback.PackageRollbackInfo;
 import android.content.rollback.RollbackInfo;
 import android.content.rollback.RollbackManager;
-import android.net.Uri;
 import android.os.Handler;
 import android.os.HandlerThread;
 import android.support.test.InstrumentationRegistry;
@@ -58,6 +58,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.
@@ -99,7 +100,7 @@
             // 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.packageName)) {
+                    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;
@@ -117,7 +118,7 @@
 
             // There should be no recently executed rollbacks for this package.
             for (RollbackInfo info : rm.getRecentlyExecutedRollbacks()) {
-                assertNotEquals(TEST_APP_A, info.targetPackage.packageName);
+                assertNotEquals(TEST_APP_A, info.targetPackage.getPackageName());
             }
 
             // Install v1 of the app (without rollbacks enabled).
@@ -136,9 +137,7 @@
             assertTrue(rm.getPackagesWithAvailableRollbacks().contains(TEST_APP_A));
             RollbackInfo rollback = rm.getAvailableRollback(TEST_APP_A);
             assertNotNull(rollback);
-            assertEquals(TEST_APP_A, rollback.targetPackage.packageName);
-            assertEquals(2, rollback.targetPackage.higherVersion.versionCode);
-            assertEquals(1, rollback.targetPackage.lowerVersion.versionCode);
+            assertPackageRollbackInfoEquals(TEST_APP_A, 2, 1, rollback.targetPackage);
 
             // We should not have received any rollback requests yet.
             // TODO: Possibly flaky if, by chance, some other app on device
@@ -154,21 +153,18 @@
             // received could lead to test flakiness.
             Intent broadcast = broadcastReceiver.poll(5, TimeUnit.SECONDS);
             assertNotNull(broadcast);
-            assertEquals(TEST_APP_A, broadcast.getData().getSchemeSpecificPart());
             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.packageName)) {
+                if (TEST_APP_A.equals(r.targetPackage.getPackageName())) {
                     assertNull(rollback);
                     rollback = r;
                 }
             }
             assertNotNull(rollback);
-            assertEquals(TEST_APP_A, rollback.targetPackage.packageName);
-            assertEquals(2, rollback.targetPackage.higherVersion.versionCode);
-            assertEquals(1, rollback.targetPackage.lowerVersion.versionCode);
+            assertPackageRollbackInfoEquals(TEST_APP_A, 2, 1, rollback.targetPackage);
 
             broadcastReceiver.unregister();
             context.unregisterReceiver(enableRollbackReceiver);
@@ -209,16 +205,12 @@
             assertTrue(rm.getPackagesWithAvailableRollbacks().contains(TEST_APP_A));
             RollbackInfo rollbackA = rm.getAvailableRollback(TEST_APP_A);
             assertNotNull(rollbackA);
-            assertEquals(TEST_APP_A, rollbackA.targetPackage.packageName);
-            assertEquals(2, rollbackA.targetPackage.higherVersion.versionCode);
-            assertEquals(1, rollbackA.targetPackage.lowerVersion.versionCode);
+            assertPackageRollbackInfoEquals(TEST_APP_A, 2, 1, rollbackA.targetPackage);
 
             assertTrue(rm.getPackagesWithAvailableRollbacks().contains(TEST_APP_B));
             RollbackInfo rollbackB = rm.getAvailableRollback(TEST_APP_B);
             assertNotNull(rollbackB);
-            assertEquals(TEST_APP_B, rollbackB.targetPackage.packageName);
-            assertEquals(2, rollbackB.targetPackage.higherVersion.versionCode);
-            assertEquals(1, rollbackB.targetPackage.lowerVersion.versionCode);
+            assertPackageRollbackInfoEquals(TEST_APP_B, 2, 1, rollbackB.targetPackage);
 
             // Reload the persisted data.
             rm.reloadPersistedData();
@@ -226,16 +218,12 @@
             // The apps should still be available for rollback.
             rollbackA = rm.getAvailableRollback(TEST_APP_A);
             assertNotNull(rollbackA);
-            assertEquals(TEST_APP_A, rollbackA.targetPackage.packageName);
-            assertEquals(2, rollbackA.targetPackage.higherVersion.versionCode);
-            assertEquals(1, rollbackA.targetPackage.lowerVersion.versionCode);
+            assertPackageRollbackInfoEquals(TEST_APP_A, 2, 1, rollbackA.targetPackage);
 
             assertTrue(rm.getPackagesWithAvailableRollbacks().contains(TEST_APP_B));
             rollbackB = rm.getAvailableRollback(TEST_APP_B);
             assertNotNull(rollbackB);
-            assertEquals(TEST_APP_B, rollbackB.targetPackage.packageName);
-            assertEquals(2, rollbackB.targetPackage.higherVersion.versionCode);
-            assertEquals(1, rollbackB.targetPackage.lowerVersion.versionCode);
+            assertPackageRollbackInfoEquals(TEST_APP_B, 2, 1, rollbackB.targetPackage);
 
             // Rollback of B should not rollback A
             RollbackTestUtils.rollback(rollbackB);
@@ -279,16 +267,12 @@
             assertTrue(rm.getPackagesWithAvailableRollbacks().contains(TEST_APP_A));
             RollbackInfo rollbackA = rm.getAvailableRollback(TEST_APP_A);
             assertNotNull(rollbackA);
-            assertEquals(TEST_APP_A, rollbackA.targetPackage.packageName);
-            assertEquals(2, rollbackA.targetPackage.higherVersion.versionCode);
-            assertEquals(1, rollbackA.targetPackage.lowerVersion.versionCode);
+            assertPackageRollbackInfoEquals(TEST_APP_A, 2, 1, rollbackA.targetPackage);
 
             assertTrue(rm.getPackagesWithAvailableRollbacks().contains(TEST_APP_B));
             RollbackInfo rollbackB = rm.getAvailableRollback(TEST_APP_B);
             assertNotNull(rollbackB);
-            assertEquals(TEST_APP_B, rollbackB.targetPackage.packageName);
-            assertEquals(2, rollbackB.targetPackage.higherVersion.versionCode);
-            assertEquals(1, rollbackB.targetPackage.lowerVersion.versionCode);
+            assertPackageRollbackInfoEquals(TEST_APP_B, 2, 1, rollbackB.targetPackage);
 
             // Reload the persisted data.
             rm.reloadPersistedData();
@@ -296,16 +280,12 @@
             // The apps should still be available for rollback.
             rollbackA = rm.getAvailableRollback(TEST_APP_A);
             assertNotNull(rollbackA);
-            assertEquals(TEST_APP_A, rollbackA.targetPackage.packageName);
-            assertEquals(2, rollbackA.targetPackage.higherVersion.versionCode);
-            assertEquals(1, rollbackA.targetPackage.lowerVersion.versionCode);
+            assertPackageRollbackInfoEquals(TEST_APP_A, 2, 1, rollbackA.targetPackage);
 
             assertTrue(rm.getPackagesWithAvailableRollbacks().contains(TEST_APP_B));
             rollbackB = rm.getAvailableRollback(TEST_APP_B);
             assertNotNull(rollbackB);
-            assertEquals(TEST_APP_B, rollbackB.targetPackage.packageName);
-            assertEquals(2, rollbackB.targetPackage.higherVersion.versionCode);
-            assertEquals(1, rollbackB.targetPackage.lowerVersion.versionCode);
+            assertPackageRollbackInfoEquals(TEST_APP_B, 2, 1, rollbackB.targetPackage);
 
             // Rollback of B should rollback A as well
             RollbackTestUtils.rollback(rollbackB);
@@ -349,15 +329,13 @@
             // Verify the recent rollback has been recorded.
             rollback = null;
             for (RollbackInfo r : rm.getRecentlyExecutedRollbacks()) {
-                if (TEST_APP_A.equals(r.targetPackage.packageName)) {
+                if (TEST_APP_A.equals(r.targetPackage.getPackageName())) {
                     assertNull(rollback);
                     rollback = r;
                 }
             }
             assertNotNull(rollback);
-            assertEquals(TEST_APP_A, rollback.targetPackage.packageName);
-            assertEquals(2, rollback.targetPackage.higherVersion.versionCode);
-            assertEquals(1, rollback.targetPackage.lowerVersion.versionCode);
+            assertPackageRollbackInfoEquals(TEST_APP_A, 2, 1, rollback.targetPackage);
 
             // Reload the persisted data.
             rm.reloadPersistedData();
@@ -365,15 +343,13 @@
             // Verify the recent rollback is still recorded.
             rollback = null;
             for (RollbackInfo r : rm.getRecentlyExecutedRollbacks()) {
-                if (TEST_APP_A.equals(r.targetPackage.packageName)) {
+                if (TEST_APP_A.equals(r.targetPackage.getPackageName())) {
                     assertNull(rollback);
                     rollback = r;
                 }
             }
             assertNotNull(rollback);
-            assertEquals(TEST_APP_A, rollback.targetPackage.packageName);
-            assertEquals(2, rollback.targetPackage.higherVersion.versionCode);
-            assertEquals(1, rollback.targetPackage.lowerVersion.versionCode);
+            assertPackageRollbackInfoEquals(TEST_APP_A, 2, 1, rollback.targetPackage);
         } finally {
             RollbackTestUtils.dropShellPermissionIdentity();
         }
@@ -405,9 +381,7 @@
             assertTrue(rm.getPackagesWithAvailableRollbacks().contains(TEST_APP_A));
             RollbackInfo rollback = rm.getAvailableRollback(TEST_APP_A);
             assertNotNull(rollback);
-            assertEquals(TEST_APP_A, rollback.targetPackage.packageName);
-            assertEquals(2, rollback.targetPackage.higherVersion.versionCode);
-            assertEquals(1, rollback.targetPackage.lowerVersion.versionCode);
+            assertPackageRollbackInfoEquals(TEST_APP_A, 2, 1, rollback.targetPackage);
 
             // Expire the rollback.
             rm.expireRollbackForPackage(TEST_APP_A);
@@ -470,7 +444,7 @@
      * Test that app user data is rolled back.
      * TODO: Stop ignoring this test once user data rollback is supported.
      */
-    @Ignore @Test
+    @Test
     public void testUserDataRollback() throws Exception {
         try {
             RollbackTestUtils.adoptShellPermissionIdentity(
@@ -479,9 +453,9 @@
                     Manifest.permission.MANAGE_ROLLBACKS);
 
             RollbackTestUtils.uninstall(TEST_APP_A);
-            RollbackTestUtils.install("RollbackTestAppV1.apk", false);
+            RollbackTestUtils.install("RollbackTestAppAv1.apk", false);
             processUserData(TEST_APP_A);
-            RollbackTestUtils.install("RollbackTestAppV2.apk", true);
+            RollbackTestUtils.install("RollbackTestAppAv2.apk", true);
             processUserData(TEST_APP_A);
 
             RollbackManager rm = RollbackTestUtils.getRollbackManager();
@@ -500,8 +474,7 @@
     @Test
     public void testRollbackBroadcastRestrictions() throws Exception {
         RollbackBroadcastReceiver broadcastReceiver = new RollbackBroadcastReceiver();
-        Intent broadcast = new Intent(Intent.ACTION_PACKAGE_ROLLBACK_EXECUTED,
-                Uri.fromParts("package", "com.android.tests.rollback.bogus", null));
+        Intent broadcast = new Intent(Intent.ACTION_PACKAGE_ROLLBACK_EXECUTED);
         try {
             InstrumentationRegistry.getContext().sendBroadcast(broadcast);
             fail("Succeeded in sending restricted broadcast from app context.");
@@ -550,11 +523,11 @@
             Thread.sleep(1000);
             RollbackInfo rollbackA = rm.getAvailableRollback(TEST_APP_A);
             assertNotNull(rollbackA);
-            assertEquals(TEST_APP_A, rollbackA.targetPackage.packageName);
+            assertEquals(TEST_APP_A, rollbackA.targetPackage.getPackageName());
 
             RollbackInfo rollbackB = rm.getAvailableRollback(TEST_APP_B);
             assertNotNull(rollbackB);
-            assertEquals(TEST_APP_B, rollbackB.targetPackage.packageName);
+            assertEquals(TEST_APP_B, rollbackB.targetPackage.getPackageName());
 
             // Executing rollback should roll back the correct package.
             RollbackTestUtils.rollback(rollbackA);
@@ -671,7 +644,7 @@
 
             // We should not see a recent rollback listed for TEST_APP_B
             for (RollbackInfo r : rm.getRecentlyExecutedRollbacks()) {
-                assertNotEquals(TEST_APP_B, r.targetPackage.packageName);
+                assertNotEquals(TEST_APP_B, r.targetPackage.getPackageName());
             }
 
             // TODO: Test the listed dependent apps for the recently executed
@@ -681,4 +654,74 @@
             RollbackTestUtils.dropShellPermissionIdentity();
         }
     }
+
+    // Helper function to test the value of a PackageRollbackInfo
+    private void assertPackageRollbackInfoEquals(String packageName,
+            long versionRolledBackFrom, long versionRolledBackTo,
+            PackageRollbackInfo info) {
+        assertEquals(packageName, info.getPackageName());
+        assertEquals(packageName, info.getVersionRolledBackFrom().getPackageName());
+        assertEquals(versionRolledBackFrom, info.getVersionRolledBackFrom().getLongVersionCode());
+        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 = rm.getAvailableRollback(TEST_APP_A);
+            assertNotNull(rollbackA);
+            assertEquals(TEST_APP_A, rollbackA.targetPackage.getPackageName());
+
+            RollbackInfo rollbackB = rm.getAvailableRollback(TEST_APP_B);
+            assertNotNull(rollbackB);
+            assertEquals(TEST_APP_B, rollbackB.targetPackage.getPackageName());
+
+            // 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();
+        }
+    }
 }
diff --git a/tests/RollbackTest/src/com/android/tests/rollback/RollbackTestUtils.java b/tests/RollbackTest/src/com/android/tests/rollback/RollbackTestUtils.java
index fbc3d8f..edb1355 100644
--- a/tests/RollbackTest/src/com/android/tests/rollback/RollbackTestUtils.java
+++ b/tests/RollbackTest/src/com/android/tests/rollback/RollbackTestUtils.java
@@ -135,6 +135,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/Android.mk b/tests/net/Android.mk
index 6850673..7e1b400 100644
--- a/tests/net/Android.mk
+++ b/tests/net/Android.mk
@@ -32,74 +32,6 @@
 
 LOCAL_CERTIFICATE := platform
 
-# These are not normally accessible from apps so they must be explicitly included.
-LOCAL_JNI_SHARED_LIBRARIES := \
-    android.hidl.token@1.0 \
-    libartbase \
-    libbacktrace \
-    libbase \
-    libbinder \
-    libbinderthreadstate \
-    libc++ \
-    libcrypto \
-    libcutils \
-    libdexfile \
-    libframeworksnettestsjni \
-    libhidl-gen-utils \
-    libhidlbase \
-    libhidltransport \
-    libhwbinder \
-    liblog \
-    liblzma \
-    libnativehelper \
-    libpackagelistparser \
-    libpcre2 \
-    libprocessgroup \
-    libselinux \
-    libui \
-    libutils \
-    libvintf \
-    libvndksupport \
-    libtinyxml2 \
-    libunwindstack \
-    libutilscallstack \
-    libziparchive \
-    libz \
-    netd_aidl_interface-cpp
-
 LOCAL_ADDITIONAL_DEPENDENCIES := $(LOCAL_PATH)/Android.mk
 
 include $(BUILD_PACKAGE)
-
-#########################################################################
-# Build JNI Shared Library
-#########################################################################
-
-LOCAL_PATH:= $(LOCAL_PATH)/jni
-
-include $(CLEAR_VARS)
-
-LOCAL_MODULE_TAGS := tests
-
-LOCAL_CFLAGS := -Wall -Wextra -Werror
-
-LOCAL_C_INCLUDES := \
-  libpcap \
-  hardware/google/apf
-
-LOCAL_SRC_FILES := $(call all-cpp-files-under)
-
-LOCAL_SHARED_LIBRARIES := \
-  libbinder \
-  liblog \
-  libcutils \
-  libnativehelper \
-  netd_aidl_interface-cpp
-
-LOCAL_STATIC_LIBRARIES := \
-  libpcap \
-  libapf
-
-LOCAL_MODULE := libframeworksnettestsjni
-
-include $(BUILD_SHARED_LIBRARY)
diff --git a/tests/net/java/android/net/DnsPacketTest.java b/tests/net/java/android/net/DnsPacketTest.java
new file mode 100644
index 0000000..032e526
--- /dev/null
+++ b/tests/net/java/android/net/DnsPacketTest.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 android.net;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertTrue;
+import static org.junit.Assert.fail;
+
+import android.support.test.filters.SmallTest;
+import android.support.test.runner.AndroidJUnit4;
+
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+import java.util.Arrays;
+import java.util.List;
+
+@RunWith(AndroidJUnit4.class)
+@SmallTest
+public class DnsPacketTest {
+    private void assertHeaderParses(DnsPacket.DnsHeader header, int id, int flag,
+            int qCount, int aCount, int nsCount, int arCount) {
+        assertEquals(header.id, id);
+        assertEquals(header.flags, flag);
+        assertEquals(header.getSectionCount(DnsPacket.QDSECTION), qCount);
+        assertEquals(header.getSectionCount(DnsPacket.ANSECTION), aCount);
+        assertEquals(header.getSectionCount(DnsPacket.NSSECTION), nsCount);
+        assertEquals(header.getSectionCount(DnsPacket.ARSECTION), arCount);
+    }
+
+    private void assertSectionParses(DnsPacket.DnsSection section, String dname,
+            int dtype, int dclass, int ttl, byte[] rr) {
+        assertEquals(section.dName, dname);
+        assertEquals(section.nsType, dtype);
+        assertEquals(section.nsClass, dclass);
+        assertEquals(section.ttl, ttl);
+        assertTrue(Arrays.equals(section.getRR(), rr));
+    }
+
+    class TestDnsPacket extends DnsPacket {
+        TestDnsPacket(byte[] data) throws ParseException {
+            super(data);
+        }
+
+        public DnsHeader getHeader() {
+            return mHeader;
+        }
+        public List<DnsSection> getSectionList(int secType) {
+            return mSections[secType];
+        }
+    }
+
+    @Test
+    public void testNullDisallowed() {
+        try {
+            new TestDnsPacket(null);
+            fail("Exception not thrown for null byte array");
+        } catch (DnsPacket.ParseException e) {
+        }
+    }
+
+    @Test
+    public void testV4Answer() throws Exception {
+        final byte[] v4blob = new byte[] {
+            /* Header */
+            0x55, 0x66, /* Transaction ID */
+            (byte) 0x81, (byte) 0x80, /* Flags */
+            0x00, 0x01, /* Questions */
+            0x00, 0x01, /* Answer RRs */
+            0x00, 0x00, /* Authority RRs */
+            0x00, 0x00, /* Additional RRs */
+            /* Queries */
+            0x03, 0x77, 0x77, 0x77, 0x06, 0x67, 0x6F, 0x6F, 0x67, 0x6c, 0x65,
+            0x03, 0x63, 0x6f, 0x6d, 0x00, /* Name */
+            0x00, 0x01, /* Type */
+            0x00, 0x01, /* Class */
+            /* Answers */
+            (byte) 0xc0, 0x0c, /* Name */
+            0x00, 0x01, /* Type */
+            0x00, 0x01, /* Class */
+            0x00, 0x00, 0x01, 0x2b, /* TTL */
+            0x00, 0x04, /* Data length */
+            (byte) 0xac, (byte) 0xd9, (byte) 0xa1, (byte) 0x84 /* Address */
+        };
+        TestDnsPacket packet = new TestDnsPacket(v4blob);
+
+        // Header part
+        assertHeaderParses(packet.getHeader(), 0x5566, 0x8180, 1, 1, 0, 0);
+
+        // Section part
+        List<DnsPacket.DnsSection> qdSectionList =
+                packet.getSectionList(DnsPacket.QDSECTION);
+        assertEquals(qdSectionList.size(), 1);
+        assertSectionParses(qdSectionList.get(0), "www.google.com", 1, 1, 0, null);
+
+        List<DnsPacket.DnsSection> anSectionList =
+                packet.getSectionList(DnsPacket.ANSECTION);
+        assertEquals(anSectionList.size(), 1);
+        assertSectionParses(anSectionList.get(0), "www.google.com", 1, 1, 0x12b,
+                new byte[]{ (byte) 0xac, (byte) 0xd9, (byte) 0xa1, (byte) 0x84 });
+    }
+
+    @Test
+    public void testV6Answer() throws Exception {
+        final byte[] v6blob = new byte[] {
+            /* Header */
+            0x77, 0x22, /* Transaction ID */
+            (byte) 0x81, (byte) 0x80, /* Flags */
+            0x00, 0x01, /* Questions */
+            0x00, 0x01, /* Answer RRs */
+            0x00, 0x00, /* Authority RRs */
+            0x00, 0x00, /* Additional RRs */
+            /* Queries */
+            0x03, 0x77, 0x77, 0x77, 0x06, 0x67, 0x6F, 0x6F, 0x67, 0x6c, 0x65,
+            0x03, 0x63, 0x6f, 0x6d, 0x00, /* Name */
+            0x00, 0x1c, /* Type */
+            0x00, 0x01, /* Class */
+            /* Answers */
+            (byte) 0xc0, 0x0c, /* Name */
+            0x00, 0x1c, /* Type */
+            0x00, 0x01, /* Class */
+            0x00, 0x00, 0x00, 0x37, /* TTL */
+            0x00, 0x10, /* Data length */
+            0x24, 0x04, 0x68, 0x00, 0x40, 0x05, 0x08, 0x0d,
+            0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x20, 0x04 /* Address */
+        };
+        TestDnsPacket packet = new TestDnsPacket(v6blob);
+
+        // Header part
+        assertHeaderParses(packet.getHeader(), 0x7722, 0x8180, 1, 1, 0, 0);
+
+        // Section part
+        List<DnsPacket.DnsSection> qdSectionList =
+                packet.getSectionList(DnsPacket.QDSECTION);
+        assertEquals(qdSectionList.size(), 1);
+        assertSectionParses(qdSectionList.get(0), "www.google.com", 28, 1, 0, null);
+
+        List<DnsPacket.DnsSection> anSectionList =
+                packet.getSectionList(DnsPacket.ANSECTION);
+        assertEquals(anSectionList.size(), 1);
+        assertSectionParses(anSectionList.get(0), "www.google.com", 28, 1, 0x37,
+                new byte[]{ 0x24, 0x04, 0x68, 0x00, 0x40, 0x05, 0x08, 0x0d,
+                    0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x20, 0x04 });
+    }
+}
diff --git a/tests/net/java/com/android/server/ConnectivityServiceTest.java b/tests/net/java/com/android/server/ConnectivityServiceTest.java
index 1c26418..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;
         }
@@ -1508,6 +1525,12 @@
         verifyActiveNetwork(TRANSPORT_WIFI);
     }
 
+    @Test
+    public void testRequiresValidation() {
+        assertTrue(NetworkMonitorUtils.isValidationRequired(
+                mCm.getDefaultRequest().networkCapabilities));
+    }
+
     enum CallbackState {
         NONE,
         AVAILABLE,
@@ -3542,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) {
@@ -3689,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);
@@ -4404,8 +4640,7 @@
         mMockVpn.setUids(ranges);
         // VPN networks do not satisfy the default request and are automatically validated
         // by NetworkMonitor
-        assertFalse(NetworkMonitorUtils.isValidationRequired(
-                mCm.getDefaultRequest().networkCapabilities, vpnNetworkAgent.mNetworkCapabilities));
+        assertFalse(NetworkMonitorUtils.isValidationRequired(vpnNetworkAgent.mNetworkCapabilities));
         vpnNetworkAgent.setNetworkValid();
 
         vpnNetworkAgent.connect(false);
@@ -4909,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/connectivity/VpnTest.java b/tests/net/java/com/android/server/connectivity/VpnTest.java
index 9bf7587..0b74d87 100644
--- a/tests/net/java/com/android/server/connectivity/VpnTest.java
+++ b/tests/net/java/com/android/server/connectivity/VpnTest.java
@@ -57,7 +57,6 @@
 import android.content.pm.UserInfo;
 import android.content.res.Resources;
 import android.net.ConnectivityManager;
-import android.net.IConnectivityManager;
 import android.net.IpPrefix;
 import android.net.LinkProperties;
 import android.net.Network;
@@ -97,7 +96,6 @@
 import java.util.HashMap;
 import java.util.Map;
 import java.util.Set;
-import java.util.stream.Collectors;
 import java.util.stream.Stream;
 
 /**
@@ -240,6 +238,30 @@
     }
 
     @Test
+    public void testGetAlwaysAndOnGetLockDown() throws Exception {
+        final Vpn vpn = createVpn(primaryUser.id);
+
+        // Default state.
+        assertFalse(vpn.getAlwaysOn());
+        assertFalse(vpn.getLockdown());
+
+        // Set always-on without lockdown.
+        assertTrue(vpn.setAlwaysOnPackage(PKGS[1], false));
+        assertTrue(vpn.getAlwaysOn());
+        assertFalse(vpn.getLockdown());
+
+        // Set always-on with lockdown.
+        assertTrue(vpn.setAlwaysOnPackage(PKGS[1], true));
+        assertTrue(vpn.getAlwaysOn());
+        assertTrue(vpn.getLockdown());
+
+        // Remove always-on configuration.
+        assertTrue(vpn.setAlwaysOnPackage(null, false));
+        assertFalse(vpn.getAlwaysOn());
+        assertFalse(vpn.getLockdown());
+    }
+
+    @Test
     public void testLockdownChangingPackage() throws Exception {
         final Vpn vpn = createVpn(primaryUser.id);
         final UidRange user = UidRange.createForUser(primaryUser.id);
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 94bcd28..e57433a 100644
--- a/tests/net/java/com/android/server/net/ipmemorystore/IpMemoryStoreServiceTest.java
+++ b/tests/net/java/com/android/server/net/ipmemorystore/IpMemoryStoreServiceTest.java
@@ -27,10 +27,14 @@
 import android.content.Context;
 import android.net.ipmemorystore.Blob;
 import android.net.ipmemorystore.IOnBlobRetrievedListener;
+import android.net.ipmemorystore.IOnL2KeyResponseListener;
 import android.net.ipmemorystore.IOnNetworkAttributesRetrieved;
+import android.net.ipmemorystore.IOnSameNetworkResponseListener;
 import android.net.ipmemorystore.IOnStatusListener;
 import android.net.ipmemorystore.NetworkAttributes;
 import android.net.ipmemorystore.NetworkAttributesParcelable;
+import android.net.ipmemorystore.SameL3NetworkResponse;
+import android.net.ipmemorystore.SameL3NetworkResponseParcelable;
 import android.net.ipmemorystore.Status;
 import android.net.ipmemorystore.StatusParcelable;
 import android.os.IBinder;
@@ -53,7 +57,6 @@
 import java.net.InetAddress;
 import java.net.UnknownHostException;
 import java.util.Arrays;
-import java.util.UUID;
 import java.util.concurrent.CountDownLatch;
 import java.util.concurrent.TimeUnit;
 import java.util.function.Consumer;
@@ -65,6 +68,15 @@
     private static final String TEST_CLIENT_ID = "testClientId";
     private static final String TEST_DATA_NAME = "testData";
 
+    private static final int FAKE_KEY_COUNT = 20;
+    private static final String[] FAKE_KEYS;
+    static {
+        FAKE_KEYS = new String[FAKE_KEY_COUNT];
+        for (int i = 0; i < FAKE_KEYS.length; ++i) {
+            FAKE_KEYS[i] = "fakeKey" + i;
+        }
+    }
+
     @Mock
     private Context mMockContext;
     private File mDbFile;
@@ -130,8 +142,8 @@
             final OnNetworkAttributesRetrievedListener functor) {
         return new IOnNetworkAttributesRetrieved() {
             @Override
-            public void onL2KeyResponse(final StatusParcelable status, final String l2Key,
-                    final NetworkAttributesParcelable attributes)
+            public void onNetworkAttributesRetrieved(final StatusParcelable status,
+                    final String l2Key, final NetworkAttributesParcelable attributes)
                     throws RemoteException {
                 functor.onNetworkAttributesRetrieved(new Status(status), l2Key,
                         null == attributes ? null : new NetworkAttributes(attributes));
@@ -144,35 +156,82 @@
         };
     }
 
+    /** Helper method to make an IOnSameNetworkResponseListener */
+    private interface OnSameNetworkResponseListener {
+        void onSameNetworkResponse(Status status, SameL3NetworkResponse answer);
+    }
+    private IOnSameNetworkResponseListener onSameResponse(
+            final OnSameNetworkResponseListener functor) {
+        return new IOnSameNetworkResponseListener() {
+            @Override
+            public void onSameNetworkResponse(final StatusParcelable status,
+                    final SameL3NetworkResponseParcelable sameL3Network)
+                    throws RemoteException {
+                functor.onSameNetworkResponse(new Status(status),
+                        null == sameL3Network ? null : new SameL3NetworkResponse(sameL3Network));
+            }
+
+            @Override
+            public IBinder asBinder() {
+                return null;
+            }
+        };
+    }
+
+    /** Helper method to make an IOnL2KeyResponseListener */
+    private interface OnL2KeyResponseListener {
+        void onL2KeyResponse(Status status, String key);
+    }
+    private IOnL2KeyResponseListener onL2KeyResponse(final OnL2KeyResponseListener functor) {
+        return new IOnL2KeyResponseListener() {
+            @Override
+            public void onL2KeyResponse(final StatusParcelable status, final String key)
+                    throws RemoteException {
+                functor.onL2KeyResponse(new Status(status), key);
+            }
+
+            @Override
+            public IBinder asBinder() {
+                return null;
+            }
+        };
+    }
+
     // Helper method to factorize some boilerplate
     private void doLatched(final String timeoutMessage, final Consumer<CountDownLatch> functor) {
         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");
         }
     }
 
+    // Helper methods to factorize more boilerplate
+    private void storeAttributes(final String l2Key, final NetworkAttributes na) {
+        storeAttributes("Did not complete storing attributes", l2Key, na);
+    }
+    private void storeAttributes(final String timeoutMessage, final String l2Key,
+            final NetworkAttributes na) {
+        doLatched(timeoutMessage, latch -> mService.storeNetworkAttributes(l2Key, na.toParcelable(),
+                onStatus(status -> {
+                    assertTrue("Store not successful : " + status.resultCode, status.isSuccess());
+                    latch.countDown();
+                })));
+    }
+
     @Test
-    public void testNetworkAttributes() {
+    public void testNetworkAttributes() throws UnknownHostException {
         final NetworkAttributes.Builder na = new NetworkAttributes.Builder();
-        try {
-            na.setAssignedV4Address(
-                    (Inet4Address) Inet4Address.getByAddress(new byte[]{1, 2, 3, 4}));
-        } catch (UnknownHostException e) { /* Can't happen */ }
+        na.setAssignedV4Address((Inet4Address) Inet4Address.getByName("1.2.3.4"));
         na.setGroupHint("hint1");
         na.setMtu(219);
-        final String l2Key = UUID.randomUUID().toString();
+        final String l2Key = FAKE_KEYS[0];
         NetworkAttributes attributes = na.build();
-        doLatched("Did not complete storing attributes", latch ->
-                mService.storeNetworkAttributes(l2Key, attributes.toParcelable(),
-                        onStatus(status -> {
-                            assertTrue("Store status not successful : " + status.resultCode,
-                                    status.isSuccess());
-                            latch.countDown();
-                        })));
+        storeAttributes(l2Key, attributes);
 
         doLatched("Did not complete retrieving attributes", latch ->
                 mService.retrieveNetworkAttributes(l2Key, onNetworkAttributesRetrieved(
@@ -185,14 +244,10 @@
                         })));
 
         final NetworkAttributes.Builder na2 = new NetworkAttributes.Builder();
-        try {
-            na.setDnsAddresses(Arrays.asList(
-                    new InetAddress[] {Inet6Address.getByName("0A1C:2E40:480A::1CA6")}));
-        } catch (UnknownHostException e) { /* Still can't happen */ }
+        na.setDnsAddresses(Arrays.asList(
+                new InetAddress[] {Inet6Address.getByName("0A1C:2E40:480A::1CA6")}));
         final NetworkAttributes attributes2 = na2.build();
-        doLatched("Did not complete storing attributes 2", latch ->
-                mService.storeNetworkAttributes(l2Key, attributes2.toParcelable(),
-                        onStatus(status -> latch.countDown())));
+        storeAttributes("Did not complete storing attributes 2", l2Key, attributes2);
 
         doLatched("Did not complete retrieving attributes 2", latch ->
                 mService.retrieveNetworkAttributes(l2Key, onNetworkAttributesRetrieved(
@@ -261,6 +316,7 @@
                             assertEquals(Status.ERROR_ILLEGAL_ARGUMENT, status.resultCode);
                             assertNull(key);
                             assertNull(attr);
+                            latch.countDown();
                         })));
     }
 
@@ -268,7 +324,7 @@
     public void testPrivateData() {
         final Blob b = new Blob();
         b.data = new byte[] { -3, 6, 8, -9, 12, -128, 0, 89, 112, 91, -34 };
-        final String l2Key = UUID.randomUUID().toString();
+        final String l2Key = FAKE_KEYS[0];
         doLatched("Did not complete storing private data", latch ->
                 mService.storeBlob(l2Key, TEST_CLIENT_ID, TEST_DATA_NAME, b,
                         onStatus(status -> {
@@ -302,12 +358,148 @@
     }
 
     @Test
-    public void testFindL2Key() {
-        // TODO : implement this
+    public void testFindL2Key() throws UnknownHostException {
+        final NetworkAttributes.Builder na = new NetworkAttributes.Builder();
+        na.setGroupHint("hint0");
+        storeAttributes(FAKE_KEYS[0], na.build());
+
+        na.setDnsAddresses(Arrays.asList(
+                new InetAddress[] {Inet6Address.getByName("8D56:9AF1::08EE:20F1")}));
+        na.setMtu(219);
+        storeAttributes(FAKE_KEYS[1], na.build());
+        na.setMtu(null);
+        na.setAssignedV4Address((Inet4Address) Inet4Address.getByName("1.2.3.4"));
+        na.setDnsAddresses(Arrays.asList(
+                new InetAddress[] {Inet6Address.getByName("0A1C:2E40:480A::1CA6")}));
+        na.setGroupHint("hint1");
+        storeAttributes(FAKE_KEYS[2], na.build());
+        na.setMtu(219);
+        storeAttributes(FAKE_KEYS[3], na.build());
+        na.setMtu(240);
+        storeAttributes(FAKE_KEYS[4], na.build());
+        na.setAssignedV4Address((Inet4Address) Inet4Address.getByName("5.6.7.8"));
+        storeAttributes(FAKE_KEYS[5], na.build());
+
+        // Matches key 5 exactly
+        doLatched("Did not finish finding L2Key", latch ->
+                mService.findL2Key(na.build().toParcelable(), onL2KeyResponse((status, key) -> {
+                    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.
+        na.setMtu(240);
+        doLatched("Did not finish finding L2Key", latch ->
+                mService.findL2Key(na.build().toParcelable(), onL2KeyResponse((status, key) -> {
+                    assertTrue("Retrieve network sameness not successful : " + status.resultCode,
+                            status.isSuccess());
+                    assertEquals(FAKE_KEYS[5], key);
+                    latch.countDown();
+                })));
+
+        // Closest to key 3 (indeed, identical)
+        na.setAssignedV4Address((Inet4Address) Inet4Address.getByName("1.2.3.4"));
+        na.setMtu(219);
+        doLatched("Did not finish finding L2Key", latch ->
+                mService.findL2Key(na.build().toParcelable(), onL2KeyResponse((status, key) -> {
+                    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
+        na.setGroupHint("hint0");
+        doLatched("Did not finish finding L2Key", latch ->
+                mService.findL2Key(na.build().toParcelable(), onL2KeyResponse((status, key) -> {
+                    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
+        na.setGroupHint("hint1");
+        na.setDnsAddresses(null);
+        doLatched("Did not finish finding L2Key", latch ->
+                mService.findL2Key(na.build().toParcelable(), onL2KeyResponse((status, key) -> {
+                    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
+        na.setMtu(240);
+        doLatched("Did not finish finding L2Key", latch ->
+                mService.findL2Key(na.build().toParcelable(), onL2KeyResponse((status, key) -> {
+                    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
+        na.setGroupHint(null);
+        na.setDnsAddresses(null);
+        na.setAssignedV4Address(null);
+        doLatched("Did not finish finding L2Key", latch ->
+                mService.findL2Key(na.build().toParcelable(), onL2KeyResponse((status, key) -> {
+                    assertTrue("Retrieve network sameness not successful : " + status.resultCode,
+                            status.isSuccess());
+                    assertNull(key);
+                    latch.countDown();
+                })));
+    }
+
+    private void assertNetworksSameness(final String key1, final String key2, final int sameness) {
+        doLatched("Did not finish evaluating sameness", latch ->
+                mService.isSameNetwork(key1, key2, onSameResponse((status, answer) -> {
+                    assertTrue("Retrieve network sameness not successful : " + status.resultCode,
+                            status.isSuccess());
+                    assertEquals(sameness, answer.getNetworkSameness());
+                    latch.countDown();
+                })));
     }
 
     @Test
-    public void testIsSameNetwork() {
-        // TODO : implement this
+    public void testIsSameNetwork() throws UnknownHostException {
+        final NetworkAttributes.Builder na = new NetworkAttributes.Builder();
+        na.setAssignedV4Address((Inet4Address) Inet4Address.getByName("1.2.3.4"));
+        na.setGroupHint("hint1");
+        na.setMtu(219);
+        na.setDnsAddresses(Arrays.asList(Inet6Address.getByName("0A1C:2E40:480A::1CA6")));
+
+        storeAttributes(FAKE_KEYS[0], na.build());
+        // 0 and 1 have identical attributes
+        storeAttributes(FAKE_KEYS[1], na.build());
+
+        // Hopefully only the MTU being different still means it's the same network
+        na.setMtu(200);
+        storeAttributes(FAKE_KEYS[2], na.build());
+
+        // Hopefully different MTU, assigned V4 address and grouphint make a different network,
+        // even with identical DNS addresses
+        na.setAssignedV4Address(null);
+        na.setGroupHint("hint2");
+        storeAttributes(FAKE_KEYS[3], na.build());
+
+        assertNetworksSameness(FAKE_KEYS[0], FAKE_KEYS[1], SameL3NetworkResponse.NETWORK_SAME);
+        assertNetworksSameness(FAKE_KEYS[0], FAKE_KEYS[2], SameL3NetworkResponse.NETWORK_SAME);
+        assertNetworksSameness(FAKE_KEYS[1], FAKE_KEYS[2], SameL3NetworkResponse.NETWORK_SAME);
+        assertNetworksSameness(FAKE_KEYS[0], FAKE_KEYS[3], SameL3NetworkResponse.NETWORK_DIFFERENT);
+        assertNetworksSameness(FAKE_KEYS[0], "neverInsertedKey",
+                SameL3NetworkResponse.NETWORK_NEVER_CONNECTED);
+
+        doLatched("Did not finish evaluating sameness", latch ->
+                mService.isSameNetwork(null, null, onSameResponse((status, answer) -> {
+                    assertFalse("Retrieve network sameness suspiciously successful : "
+                            + status.resultCode, status.isSuccess());
+                    assertEquals(Status.ERROR_ILLEGAL_ARGUMENT, status.resultCode);
+                    assertNull(answer);
+                    latch.countDown();
+                })));
     }
 }
diff --git a/tests/net/java/com/android/server/net/ipmemorystore/NetworkAttributesTest.java b/tests/net/java/com/android/server/net/ipmemorystore/NetworkAttributesTest.java
new file mode 100644
index 0000000..fe19eee
--- /dev/null
+++ b/tests/net/java/com/android/server/net/ipmemorystore/NetworkAttributesTest.java
@@ -0,0 +1,65 @@
+/*
+ * 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.net.ipmemorystore;
+
+import static org.junit.Assert.assertEquals;
+
+import android.net.ipmemorystore.NetworkAttributes;
+import android.support.test.filters.SmallTest;
+import android.support.test.runner.AndroidJUnit4;
+
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+import java.lang.reflect.Field;
+import java.net.Inet4Address;
+import java.net.UnknownHostException;
+import java.util.Arrays;
+
+/** Unit tests for {@link NetworkAttributes}. */
+@SmallTest
+@RunWith(AndroidJUnit4.class)
+public class NetworkAttributesTest {
+    private static final String WEIGHT_FIELD_NAME_PREFIX = "WEIGHT_";
+    private static final float EPSILON = 0.0001f;
+
+    // This is running two tests to make sure the total weight is the sum of all weights. To be
+    // sure this is not fireproof, but you'd kind of need to do it on purpose to pass.
+    @Test
+    public void testTotalWeight() throws IllegalAccessException, UnknownHostException {
+        // Make sure that TOTAL_WEIGHT is equal to the sum of the fields starting with WEIGHT_
+        float sum = 0f;
+        final Field[] fieldList = NetworkAttributes.class.getDeclaredFields();
+        for (final Field field : fieldList) {
+            if (!field.getName().startsWith(WEIGHT_FIELD_NAME_PREFIX)) continue;
+            field.setAccessible(true);
+            sum += (float) field.get(null);
+        }
+        assertEquals(sum, NetworkAttributes.TOTAL_WEIGHT, EPSILON);
+
+        // Use directly the constructor with all attributes, and make sure that when compared
+        // to itself the score is a clean 1.0f.
+        final NetworkAttributes na =
+                new NetworkAttributes(
+                        (Inet4Address) Inet4Address.getByAddress(new byte[] {1, 2, 3, 4}),
+                        "some hint",
+                        Arrays.asList(Inet4Address.getByAddress(new byte[] {5, 6, 7, 8}),
+                                Inet4Address.getByAddress(new byte[] {9, 0, 1, 2})),
+                        98);
+        assertEquals(1.0f, na.getNetworkGroupSamenessConfidence(na), EPSILON);
+    }
+}
diff --git a/tests/utils/SleepUtils/AlarmService/Android.mk b/tests/utils/SleepUtils/AlarmService/Android.mk
deleted file mode 100644
index 9022f03..0000000
--- a/tests/utils/SleepUtils/AlarmService/Android.mk
+++ /dev/null
@@ -1,26 +0,0 @@
-#
-# Copyright (C) 2013 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.
-#
-
-LOCAL_PATH:= $(call my-dir)
-include $(CLEAR_VARS)
-
-LOCAL_MODULE_TAGS := tests
-LOCAL_SRC_FILES := $(call all-java-files-under, src)
-LOCAL_SRC_FILES += \
-    src/com/android/testing/alarmservice/Alarm.aidl
-LOCAL_PACKAGE_NAME := SleepUtilsAlarmService
-LOCAL_SDK_VERSION := 7
-include $(BUILD_PACKAGE)
diff --git a/tests/utils/SleepUtils/AlarmService/AndroidManifest.xml b/tests/utils/SleepUtils/AlarmService/AndroidManifest.xml
deleted file mode 100644
index 1b6de39..0000000
--- a/tests/utils/SleepUtils/AlarmService/AndroidManifest.xml
+++ /dev/null
@@ -1,31 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<!-- Copyright (C) 2013 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.testing.alarmservice" >
-
-    <uses-sdk android:minSdkVersion="7" />
-    <uses-permission android:name="android.permission.WAKE_LOCK" />
-
-    <application android:label="Sleep Utils Alarm Service">
-        <service android:name=".AlarmService"
-            android:label="Sleep Utils Alarm Service"
-            android:exported="true"
-            android:enabled="true">
-            <intent-filter>
-                <action android:name="com.android.testing.ALARM_SERVICE" />
-            </intent-filter>
-        </service>
-        <receiver android:name=".WakeUpCall">
-            <intent-filter>
-                <action android:name="com.android.testing.alarmservice.WAKEUP" />
-            </intent-filter>
-        </receiver>
-    </application>
-</manifest>
diff --git a/tests/utils/SleepUtils/AlarmService/src/com/android/testing/alarmservice/AlarmImpl.java b/tests/utils/SleepUtils/AlarmService/src/com/android/testing/alarmservice/AlarmImpl.java
deleted file mode 100644
index 122d55d..0000000
--- a/tests/utils/SleepUtils/AlarmService/src/com/android/testing/alarmservice/AlarmImpl.java
+++ /dev/null
@@ -1,77 +0,0 @@
-/*
- * Copyright (C) 2013 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.testing.alarmservice;
-
-import android.app.AlarmManager;
-import android.app.PendingIntent;
-import android.content.Context;
-import android.content.Intent;
-import android.os.RemoteException;
-import android.os.SystemClock;
-import android.util.Log;
-
-import com.android.testing.alarmservice.Alarm.Stub;
-
-public class AlarmImpl extends Stub {
-
-    private static final String LOG_TAG = AlarmImpl.class.getSimpleName();
-
-    private Context mContext;
-
-    public AlarmImpl(Context context) {
-        super();
-        mContext = context;
-    }
-
-    @Override
-    public int prepare() throws RemoteException {
-        WakeUpController.getController().getWakeLock().acquire();
-        Log.d(LOG_TAG, "AlarmService prepared, wake lock acquired");
-        return 0;
-    }
-
-    @Override
-    public int setAlarmAndWait(long timeoutMills) throws RemoteException {
-        // calculate when device should be waken up
-        long atTime = SystemClock.elapsedRealtime() + timeoutMills;
-        AlarmManager am = (AlarmManager) mContext.getSystemService(Context.ALARM_SERVICE);
-        Intent wakupIntent = new Intent(WakeUpCall.WAKEUP_CALL);
-        PendingIntent pi = PendingIntent.getBroadcast(mContext, 0, wakupIntent, 0);
-        // set alarm, which will be delivered in form of the wakeupIntent
-        am.set(AlarmManager.ELAPSED_REALTIME_WAKEUP, atTime, pi);
-        Log.d(LOG_TAG, String.format("Alarm set: %d, giving up wake lock", atTime));
-        Object lock = WakeUpController.getController().getWakeSync();
-        // release wakelock and wait for the lock to be poked from the broadcast receiver
-        WakeUpController.getController().getWakeLock().release();
-        // does not really matter if device enters suspend before we start waiting on lock
-        synchronized (lock) {
-            try {
-                lock.wait();
-            } catch (InterruptedException e) {
-            }
-        }
-        Log.d(LOG_TAG, String.format("Alarm triggered, done waiting"));
-        return 0;
-    }
-
-    @Override
-    public int done() throws RemoteException {
-        WakeUpController.getController().getWakeLock().release();
-        return 0;
-    }
-
-}
diff --git a/tests/utils/SleepUtils/AlarmService/src/com/android/testing/alarmservice/AlarmService.java b/tests/utils/SleepUtils/AlarmService/src/com/android/testing/alarmservice/AlarmService.java
deleted file mode 100644
index 576a1cf..0000000
--- a/tests/utils/SleepUtils/AlarmService/src/com/android/testing/alarmservice/AlarmService.java
+++ /dev/null
@@ -1,52 +0,0 @@
-/*
- * Copyright (C) 2013 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.testing.alarmservice;
-
-import android.app.Service;
-import android.content.Context;
-import android.content.Intent;
-import android.os.IBinder;
-
-public class AlarmService extends Service {
-
-    private AlarmImpl mAlarmImpl = null;
-    static Context sContext;
-
-    @Override
-    public void onCreate() {
-        super.onCreate();
-        sContext = this;
-    }
-
-    @Override
-    public IBinder onBind(Intent intent) {
-        return getAlarmImpl();
-    }
-
-    private AlarmImpl getAlarmImpl() {
-        if (mAlarmImpl == null) {
-            mAlarmImpl = new AlarmImpl(this);
-        }
-        return mAlarmImpl;
-    }
-
-    @Override
-    public void onDestroy() {
-        sContext = null;
-        super.onDestroy();
-    }
-}
diff --git a/tests/utils/SleepUtils/AlarmService/src/com/android/testing/alarmservice/WakeUpCall.java b/tests/utils/SleepUtils/AlarmService/src/com/android/testing/alarmservice/WakeUpCall.java
deleted file mode 100644
index f4bb4db..0000000
--- a/tests/utils/SleepUtils/AlarmService/src/com/android/testing/alarmservice/WakeUpCall.java
+++ /dev/null
@@ -1,42 +0,0 @@
-/*
- * Copyright (C) 2013 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.testing.alarmservice;
-
-import android.content.BroadcastReceiver;
-import android.content.Context;
-import android.content.Intent;
-
-/**
- * The receiver for the alarm we set
- *
- */
-public class WakeUpCall extends BroadcastReceiver {
-
-    public static final String WAKEUP_CALL = "com.android.testing.alarmservice.WAKEUP";
-
-    @Override
-    public void onReceive(Context context, Intent intent) {
-        // we acquire wakelock without release because user is supposed to manually release it
-        WakeUpController.getController().getWakeLock().acquire();
-        Object lock = WakeUpController.getController().getWakeSync();
-        synchronized (lock) {
-            // poke the lock so the service side can be woken from waiting on the lock
-            lock.notifyAll();
-        }
-    }
-
-}
diff --git a/tests/utils/SleepUtils/AlarmService/src/com/android/testing/alarmservice/WakeUpController.java b/tests/utils/SleepUtils/AlarmService/src/com/android/testing/alarmservice/WakeUpController.java
deleted file mode 100644
index 478371f..0000000
--- a/tests/utils/SleepUtils/AlarmService/src/com/android/testing/alarmservice/WakeUpController.java
+++ /dev/null
@@ -1,59 +0,0 @@
-/*
- * Copyright (C) 2013 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.testing.alarmservice;
-
-import android.content.Context;
-import android.os.PowerManager;
-import android.os.PowerManager.WakeLock;
-import android.util.Log;
-
-/**
- * A singleton used for controlling and sharing of states/wakelocks
- *
- */
-public class WakeUpController {
-
-    private static final String LOG_TAG = WakeUpController.class.getName();
-    private static WakeUpController mController = null;
-    private WakeLock mWakeLock = null;
-    private Object mWakeSync = new Object();
-
-    private WakeUpController() {
-        Log.i(LOG_TAG, "Created instance: 0x" + Integer.toHexString(this.hashCode()));
-    }
-
-    public static synchronized WakeUpController getController() {
-        if (mController == null) {
-            mController = new WakeUpController();
-        }
-        return mController;
-    }
-
-    public WakeLock getWakeLock() {
-        if (mWakeLock == null) {
-            PowerManager pm =
-                    (PowerManager) AlarmService.sContext.getSystemService(Context.POWER_SERVICE);
-            mWakeLock = pm.newWakeLock(PowerManager.PARTIAL_WAKE_LOCK, "testing-alarmservice");
-            Log.i(LOG_TAG, "Create wakelock: 0x" + Integer.toHexString(mWakeLock.hashCode()));
-        }
-        return mWakeLock;
-    }
-
-    public Object getWakeSync() {
-        return mWakeSync;
-    }
-}
diff --git a/tests/utils/SleepUtils/Android.mk b/tests/utils/SleepUtils/Android.mk
deleted file mode 100644
index 0e65e22..0000000
--- a/tests/utils/SleepUtils/Android.mk
+++ /dev/null
@@ -1,2 +0,0 @@
-LOCAL_PATH:= $(call my-dir)
-include $(call all-makefiles-under, $(LOCAL_PATH))
diff --git a/tests/utils/SleepUtils/README b/tests/utils/SleepUtils/README
deleted file mode 100644
index bfe07da..0000000
--- a/tests/utils/SleepUtils/README
+++ /dev/null
@@ -1,23 +0,0 @@
-This folder contains utils to properly perform timed suspend and wakeup.
-
-AlarmService - a service that client can bind to and perform:
-1) holding wakelock (singleton to this service)
-2) setting alarm for a specified period and releasing the wakelock; service
-   call will block until alarm has been triggered and the wakelock is held
-3) releasing the wakelock
-
-SleepHelper - a self instrumentation meant as a convenient way to trigger
-the service functions from command line. Corresponding to service function
-above, supported operations are:
-1) holding wakelock
-am instrument -w -e command prepare \
-  com.android.testing.sleephelper/.SetAlarm
-
-2) setting alarm and wait til triggered
-am instrument -w -e command set_wait \
-  -e param <time in ms> com.android.testing.sleephelper/.SetAlarm
-Note: for the function to work properly, "-w" parameter is required
-
-3) releasing wakelock
-am instrument -w -e command done \
-  com.android.testing.sleephelper/.SetAlarm
diff --git a/tests/utils/SleepUtils/SleepHelper/Android.mk b/tests/utils/SleepUtils/SleepHelper/Android.mk
deleted file mode 100644
index f8267fd..0000000
--- a/tests/utils/SleepUtils/SleepHelper/Android.mk
+++ /dev/null
@@ -1,29 +0,0 @@
-#
-# Copyright (C) 2013 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.
-#
-
-LOCAL_PATH:= $(call my-dir)
-include $(CLEAR_VARS)
-
-LOCAL_MODULE_TAGS := tests
-
-# Only compile source java files in this apk.
-LOCAL_SRC_FILES := $(call all-java-files-under, src)
-LOCAL_SRC_FILES += \
-    ../AlarmService/src/com/android/testing/alarmservice/Alarm.aidl
-LOCAL_SDK_VERSION := 7
-LOCAL_PACKAGE_NAME := SleepUtilsSleepHelper
-
-include $(BUILD_PACKAGE)
diff --git a/tests/utils/SleepUtils/SleepHelper/AndroidManifest.xml b/tests/utils/SleepUtils/SleepHelper/AndroidManifest.xml
deleted file mode 100644
index 0f1d491..0000000
--- a/tests/utils/SleepUtils/SleepHelper/AndroidManifest.xml
+++ /dev/null
@@ -1,21 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<!-- Copyright (C) 2013 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.testing.sleephelper">
-
-    <uses-sdk android:minSdkVersion="7" />
-    <instrumentation android:label="Sleep Helper"
-                     android:name="com.android.testing.sleephelper.SetAlarm"
-                     android:targetPackage="com.android.testing.sleephelper" />
-
-    <application android:label="Sleep Utils Sleep Helper">
-        <uses-library android:name="android.test.runner" />
-    </application>
-</manifest>
diff --git a/tests/utils/SleepUtils/SleepHelper/src/com/android/testing/sleephelper/SetAlarm.java b/tests/utils/SleepUtils/SleepHelper/src/com/android/testing/sleephelper/SetAlarm.java
deleted file mode 100644
index b558741..0000000
--- a/tests/utils/SleepUtils/SleepHelper/src/com/android/testing/sleephelper/SetAlarm.java
+++ /dev/null
@@ -1,152 +0,0 @@
-/*
- * Copyright (C) 2013 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.testing.sleephelper;
-
-import android.app.Activity;
-import android.app.Instrumentation;
-import android.content.ComponentName;
-import android.content.Context;
-import android.content.Intent;
-import android.content.ServiceConnection;
-import android.os.Bundle;
-import android.os.Debug;
-import android.os.IBinder;
-import android.os.RemoteException;
-import android.util.Log;
-
-import com.android.testing.alarmservice.Alarm;
-
-public class SetAlarm extends Instrumentation {
-
-    private static final String COMMAND = "command";
-    private static final String PARAM = "param";
-    private static final String CMD_PREPARE = "prepare";
-    private static final String CMD_SET = "set_wait";
-    private static final String CMD_DONE = "done";
-    private static final String SERVICE_ACTION = "com.android.testing.ALARM_SERVICE";
-    private static final String SERVICE_PKG = "com.android.testing.alarmservice";
-    private static final String LOG_TAG = SetAlarm.class.getSimpleName();
-
-    private Alarm mAlarmService = null;
-    private Bundle mArgs = null;
-    private String mCommand = null;
-    private Intent mServceIntent = new Intent(SERVICE_ACTION).setPackage(SERVICE_PKG);
-
-    private ServiceConnection mConn = new ServiceConnection() {
-        @Override
-        public void onServiceDisconnected(ComponentName name) {
-            Log.d(LOG_TAG, "Service disconnected.");
-            mAlarmService = null;
-            errorFinish("service disconnected");
-        }
-
-        @Override
-        public void onServiceConnected(ComponentName name, IBinder service) {
-            Log.d(LOG_TAG, "Service connected.");
-            mAlarmService = Alarm.Stub.asInterface(service);
-            handleCommands();
-        }
-    };
-
-
-    private void handleCommands() {
-        if (CMD_PREPARE.equals(mCommand)) {
-            callPrepare();
-        } else if (CMD_SET.equals(mCommand)) {
-            String paramString = mArgs.getString(PARAM);
-            if (paramString == null) {
-                errorFinish("argument expected for alarm time");
-            }
-            long timeout = -1;
-            try {
-                timeout = Long.parseLong(paramString);
-            } catch (NumberFormatException nfe) {
-                errorFinish("a number argument is expected");
-            }
-            callSetAndWait(timeout);
-        } else if (CMD_DONE.equals(mCommand)) {
-            callDone();
-        } else {
-            errorFinish("Unrecognized command: " + mCommand);
-        }
-        finish(Activity.RESULT_OK, new Bundle());
-    }
-
-    @Override
-    public void onCreate(Bundle arguments) {
-        super.onCreate(arguments);
-        mCommand = arguments.getString(COMMAND);
-        if ("true".equals(arguments.getString("debug"))) {
-            Debug.waitForDebugger();
-        }
-        if (mCommand == null) {
-            errorFinish("No command specified");
-        }
-        mArgs = arguments;
-        connectToAlarmService();
-    }
-
-    private void errorFinish(String msg) {
-        Bundle ret = new Bundle();
-        ret.putString("error", msg);
-        finish(Activity.RESULT_CANCELED, ret);
-    }
-
-    private void connectToAlarmService() {
-        // start the service with an intent, this ensures the service keeps running after unbind
-        ComponentName cn = getContext().startService(mServceIntent);
-        if (cn == null) {
-            errorFinish("failed to start service");
-        }
-        if (!getContext().bindService(mServceIntent, mConn, Context.BIND_AUTO_CREATE)) {
-            errorFinish("failed to bind service");
-        }
-    }
-
-    private void callPrepare() {
-        try {
-            mAlarmService.prepare();
-        } catch (RemoteException e) {
-            errorFinish("RemoteExeption in prepare()");
-        } finally {
-            getContext().unbindService(mConn);
-        }
-    }
-
-    private void callDone() {
-        try {
-            mAlarmService.done();
-        } catch (RemoteException e) {
-            errorFinish("RemoteExeption in prepare()");
-        } finally {
-            getContext().unbindService(mConn);
-        }
-        // explicitly stop the service (started in prepare()) so that the service is now free
-        // to be reclaimed
-        getContext().stopService(mServceIntent);
-    }
-
-    private void callSetAndWait(long timeoutMills) {
-        try {
-            mAlarmService.setAlarmAndWait(timeoutMills);
-        } catch (RemoteException e) {
-            errorFinish("RemoteExeption in setAlarmAndWait()");
-        } finally {
-            getContext().unbindService(mConn);
-        }
-    }
-}
diff --git a/tests/utils/SleepUtils/WakeLoopService/Android.mk b/tests/utils/SleepUtils/WakeLoopService/Android.mk
deleted file mode 100644
index a8a944b..0000000
--- a/tests/utils/SleepUtils/WakeLoopService/Android.mk
+++ /dev/null
@@ -1,24 +0,0 @@
-#
-# Copyright (C) 2014 The Android Open Source Project
-#
-# Licensed under the Apache License, Version 2.0 (the "License");
-# you may not use this file except in compliance with the License.
-# You may obtain a copy of the License at
-#
-#      http://www.apache.org/licenses/LICENSE-2.0
-#
-# Unless required by applicable law or agreed to in writing, software
-# distributed under the License is distributed on an "AS IS" BASIS,
-# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-# See the License for the specific language governing permissions and
-# limitations under the License.
-#
-
-LOCAL_PATH:= $(call my-dir)
-include $(CLEAR_VARS)
-
-LOCAL_MODULE_TAGS := tests
-LOCAL_SRC_FILES := $(call all-java-files-under, src)
-LOCAL_PACKAGE_NAME := WakeupLoopService
-LOCAL_SDK_VERSION := 7
-include $(BUILD_PACKAGE)
diff --git a/tests/utils/SleepUtils/WakeLoopService/AndroidManifest.xml b/tests/utils/SleepUtils/WakeLoopService/AndroidManifest.xml
deleted file mode 100644
index a7028c4..0000000
--- a/tests/utils/SleepUtils/WakeLoopService/AndroidManifest.xml
+++ /dev/null
@@ -1,32 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<!-- Copyright (C) 2014 The Android Open Source Project Licensed under the
-    Apache License, Version 2.0 (the "License"); you may not use this file except
-    in compliance with the License. You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0
-    Unless required by applicable law or agreed to in writing, software distributed
-    under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES
-    OR CONDITIONS OF ANY KIND, either express or implied. See the License for
-    the specific language governing permissions and limitations under the License. -->
-
-<manifest xmlns:android="http://schemas.android.com/apk/res/android"
-    package="android.test.wakeuploop" >
-
-    <uses-sdk android:minSdkVersion="7" />
-    <uses-permission android:name="android.permission.WAKE_LOCK" />
-    <uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />
-
-    <application android:label="Auto Wakeup Loop">
-        <service android:name=".WakeLoopService"
-            android:label="Wakup Loop Service"
-            android:exported="true"
-            android:enabled="true">
-            <intent-filter>
-                <action android:name="android.test.wakeuploop.WAKEUP_SERVICE" />
-            </intent-filter>
-        </service>
-        <receiver android:name=".WakeUpCall">
-            <intent-filter>
-                <action android:name="android.test.wakeuploop.WAKEUP" />
-            </intent-filter>
-        </receiver>
-    </application>
-</manifest>
diff --git a/tests/utils/SleepUtils/WakeLoopService/src/android/test/wakeuploop/FileUtil.java b/tests/utils/SleepUtils/WakeLoopService/src/android/test/wakeuploop/FileUtil.java
deleted file mode 100644
index c8b075b..0000000
--- a/tests/utils/SleepUtils/WakeLoopService/src/android/test/wakeuploop/FileUtil.java
+++ /dev/null
@@ -1,53 +0,0 @@
-/*
- * Copyright (C) 2014 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package android.test.wakeuploop;
-
-import android.util.Log;
-
-import java.io.File;
-import java.io.FileOutputStream;
-import java.io.IOException;
-import java.text.DateFormat;
-import java.text.SimpleDateFormat;
-import java.util.Date;
-
-public class FileUtil {
-
-    private static FileUtil sInst = null;
-    private static DateFormat sDateFormat = new SimpleDateFormat("yyyy/MM/dd HH:mm:ss");
-
-    private FileUtil() {};
-
-    public static FileUtil get() {
-        if (sInst == null) {
-            sInst = new FileUtil();
-        }
-        return sInst;
-    }
-
-    public void writeDateToFile(File file) {
-        try {
-            FileOutputStream fos = new FileOutputStream(file);
-            fos.write(sDateFormat.format(new Date()).getBytes());
-            fos.write('\n');
-            fos.flush();
-            fos.close();
-        } catch (IOException ioe) {
-            Log.e("FileUtil", "exception writing date to file", ioe);
-        }
-    }
-}
diff --git a/tests/utils/SleepUtils/WakeLoopService/src/android/test/wakeuploop/WakeLoopService.java b/tests/utils/SleepUtils/WakeLoopService/src/android/test/wakeuploop/WakeLoopService.java
deleted file mode 100644
index 4f557b8..0000000
--- a/tests/utils/SleepUtils/WakeLoopService/src/android/test/wakeuploop/WakeLoopService.java
+++ /dev/null
@@ -1,98 +0,0 @@
-/*
- * Copyright (C) 2014 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package android.test.wakeuploop;
-
-import android.app.AlarmManager;
-import android.app.PendingIntent;
-import android.app.Service;
-import android.content.Context;
-import android.content.Intent;
-import android.os.Environment;
-import android.os.Handler;
-import android.os.IBinder;
-import android.os.Message;
-import android.os.Messenger;
-import android.os.SystemClock;
-import android.util.Log;
-
-import java.io.File;
-
-public class WakeLoopService extends Service {
-
-    private static final String LOG_TAG = WakeLoopService.class.getSimpleName();
-    static final String WAKEUP_INTERNAL = "WAKEUP_INTERVAL";
-    static final String MAX_LOOP = "MAX_LOOP";
-    static final String STOP_CALLBACK = "STOP_CALLBACK";
-    static final String THIS_LOOP = "THIS_LOOP";
-    static final int MSG_STOP_SERVICE = 0xd1ed1e;
-
-    private final Handler mHandler = new Handler() {
-        public void handleMessage(Message msg) {
-            if (msg.what == MSG_STOP_SERVICE) {
-                stopSelf();
-            } else {
-                super.handleMessage(msg);
-            }
-        };
-    };
-
-    @Override
-    public IBinder onBind(Intent intent) {
-        // no binding, just start via intent
-        return null;
-    }
-
-    @Override
-    public int onStartCommand(Intent intent, int flags, int startId) {
-        // get wakeup interval from intent
-        long wakeupInterval = intent.getLongExtra(WAKEUP_INTERNAL, 0);
-        long maxLoop = intent.getLongExtra(MAX_LOOP, 0);
-
-        if (wakeupInterval == 0) {
-            // stop and error
-            Log.e(LOG_TAG, "No wakeup interval specified, not starting the service");
-            stopSelf();
-            return START_NOT_STICKY;
-        }
-        FileUtil.get().writeDateToFile(new File(Environment.getExternalStorageDirectory(),
-                "wakeup-loop-start.txt"));
-        Log.d(LOG_TAG, String.format("WakeLoop: STARTED interval = %d, total loop = %d",
-                wakeupInterval, maxLoop));
-        // calculate when device should be waken up
-        long atTime = SystemClock.elapsedRealtime() + wakeupInterval;
-        AlarmManager am = (AlarmManager)getSystemService(Context.ALARM_SERVICE);
-        Intent wakupIntent = new Intent(WakeUpCall.WAKEUP_CALL)
-            .putExtra(WAKEUP_INTERNAL, wakeupInterval)
-            .putExtra(MAX_LOOP, maxLoop)
-            .putExtra(THIS_LOOP, 0L)
-            .putExtra(STOP_CALLBACK, new Messenger(mHandler));
-        PendingIntent pi = PendingIntent.getBroadcast(this, 0, wakupIntent,
-                PendingIntent.FLAG_UPDATE_CURRENT);
-        // set alarm, which will be delivered in form of the wakeupIntent
-        am.set(AlarmManager.ELAPSED_REALTIME_WAKEUP, atTime, pi);
-        return START_NOT_STICKY;
-    }
-
-    @Override
-    public void onDestroy() {
-        Log.d(LOG_TAG, "WakeLoop: STOPPED");
-        // cancel alarms first
-        Intent intent = new Intent(WakeUpCall.WAKEUP_CALL)
-            .putExtra(WakeUpCall.CANCEL, "true");
-        sendBroadcast(intent);
-    }
-}
diff --git a/tests/utils/SleepUtils/WakeLoopService/src/android/test/wakeuploop/WakeUpCall.java b/tests/utils/SleepUtils/WakeLoopService/src/android/test/wakeuploop/WakeUpCall.java
deleted file mode 100644
index 8347bbf0..0000000
--- a/tests/utils/SleepUtils/WakeLoopService/src/android/test/wakeuploop/WakeUpCall.java
+++ /dev/null
@@ -1,112 +0,0 @@
-/*
- * Copyright (C) 2014 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package android.test.wakeuploop;
-
-import android.app.AlarmManager;
-import android.app.PendingIntent;
-import android.content.BroadcastReceiver;
-import android.content.Context;
-import android.content.Intent;
-import android.os.Environment;
-import android.os.Message;
-import android.os.Messenger;
-import android.os.PowerManager;
-import android.os.PowerManager.WakeLock;
-import android.os.RemoteException;
-import android.os.SystemClock;
-import android.util.Log;
-
-import java.io.File;
-
-/**
- * The receiver for the alarm we set
- *
- */
-public class WakeUpCall extends BroadcastReceiver {
-    private static final String LOG_TAG = WakeUpCall.class.getSimpleName();
-    static final String WAKEUP_CALL = "android.test.wakeuploop.WAKEUP";
-    static final String CANCEL = "CANCEL";
-
-    @Override
-    public void onReceive(Context context, Intent intent) {
-        AlarmManager am = (AlarmManager) context.getSystemService(Context.ALARM_SERVICE);
-        boolean cancel = intent.hasExtra(CANCEL);
-        if (!cancel) {
-            long maxLoop = intent.getLongExtra(WakeLoopService.MAX_LOOP, 0);
-            long wakeupInterval = intent.getLongExtra(WakeLoopService.WAKEUP_INTERNAL, 0);
-            long thisLoop = intent.getLongExtra(WakeLoopService.THIS_LOOP, -1);
-            Log.d(LOG_TAG, String.format("incoming: interval = %d, max loop = %d, this loop = %d",
-                    wakeupInterval, maxLoop, thisLoop));
-            if (thisLoop == -1) {
-                Log.e(LOG_TAG, "no valid loop count received, trying to stop service");
-                stopService(intent);
-                return;
-            }
-            if (wakeupInterval == 0) {
-                Log.e(LOG_TAG, "no valid wakeup interval received, trying to stop service");
-                stopService(intent);
-                return;
-            }
-            thisLoop++;
-            Log.d(LOG_TAG, String.format("WakeLoop - iteration %d of %d", thisLoop, maxLoop));
-            if (thisLoop == maxLoop) {
-                // when maxLoop is 0, we loop forever, so not checking that case
-                // here
-                Log.d(LOG_TAG, "reached max loop count, stopping service");
-                stopService(intent);
-                return;
-            }
-            screenOn(context);
-            FileUtil.get().writeDateToFile(
-                    new File(Environment.getExternalStorageDirectory(), "wakeup-loop.txt"));
-            // calculate when device should be waken up
-            long atTime = SystemClock.elapsedRealtime() + wakeupInterval;
-            intent.putExtra(WakeLoopService.THIS_LOOP, thisLoop);
-            PendingIntent pi = PendingIntent.getBroadcast(context, 0, intent,
-                    PendingIntent.FLAG_UPDATE_CURRENT);
-            // set alarm, which will be delivered in form of the wakeupIntent
-            am.set(AlarmManager.ELAPSED_REALTIME_WAKEUP, atTime, pi);
-        } else {
-            // cancel alarms
-            Log.d(LOG_TAG, "cancelling future alarms on request");
-            am.cancel(PendingIntent.getBroadcast(context, 0, intent, 0));
-        }
-    }
-
-    private void stopService(Intent i) {
-        Messenger msgr = i.getParcelableExtra(WakeLoopService.STOP_CALLBACK);
-        if (msgr == null) {
-            Log.e(LOG_TAG, "no stop service callback found, cannot stop");
-        } else {
-            Message msg = new Message();
-            msg.what = WakeLoopService.MSG_STOP_SERVICE;
-            try {
-                msgr.send(msg);
-            } catch (RemoteException e) {
-                Log.e(LOG_TAG, "ignored remoted exception while attempting to stop service", e);
-            }
-        }
-    }
-
-    private void screenOn(Context context) {
-        PowerManager pm = (PowerManager)context.getSystemService(Context.POWER_SERVICE);
-        @SuppressWarnings("deprecation")
-        WakeLock wl = pm.newWakeLock(PowerManager.SCREEN_DIM_WAKE_LOCK |
-                PowerManager.ACQUIRE_CAUSES_WAKEUP, LOG_TAG);
-        wl.acquire(500);
-    }
-}
diff --git a/tests/utils/testutils/java/com/android/test/filters/SelectTest.java b/tests/utils/testutils/java/com/android/test/filters/SelectTest.java
new file mode 100644
index 0000000..d0350af
--- /dev/null
+++ b/tests/utils/testutils/java/com/android/test/filters/SelectTest.java
@@ -0,0 +1,338 @@
+/*
+ * 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.test.filters;
+
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.os.Bundle;
+import android.util.Log;
+
+import com.android.internal.annotations.VisibleForTesting;
+
+import org.junit.runner.Description;
+import org.junit.runner.manipulation.Filter;
+
+import java.util.Arrays;
+import java.util.Collection;
+import java.util.LinkedHashMap;
+import java.util.LinkedHashSet;
+import java.util.Map;
+import java.util.Set;
+import java.util.StringJoiner;
+
+/**
+ * JUnit filter to select tests.
+ *
+ * <p>This filter selects tests specified by package name, class name, and method name. With this
+ * filter, the package and the class options of AndroidJUnitRunner can be superseded. Also the
+ * restriction that prevents using the package and the class options can be mitigated.
+ *
+ * <p><b>Select out tests from Java packages:</b> this option supersedes {@code -e package} option.
+ * <pre>
+ * adb shell am instrument -w \
+ *     -e filter com.android.test.filters.SelectTest \
+ *     -e selectTest package1.,package2. \
+ *     com.tests.pkg/androidx.test.runner.AndroidJUnitRunner
+ * </pre>
+ * Note that the ending {@code .} in package name is mandatory.
+ *
+ * <p><b>Select out test classes:</b> this option supersedes {@code -e class} option.
+ * <pre>
+ * adb shell am instrument -w \
+ *     -e filter com.android.test.filters.SelectTest      \
+ *     -e selectTest package1.ClassA,package2.ClassB \
+ *     com.tests.pkg/androidx.test.runner.AndroidJUnitRunner
+ * </pre>
+ *
+ * <p><b>Select out test methods from Java classes:</b>
+ * <pre>
+ * adb shell am instrument -w \
+ *     -e filter com.android.test.filters.SelectTest                      \
+ *     -e selectTest package1.ClassA#methodX,package2.ClassB#methodY \
+ *     com.tests.pkg/androidx.test.runner.AndroidJUnitRunner
+ * </pre>
+ *
+ * Those options can be used simultaneously. For example
+ * <pre>
+ * adb shell am instrument -w \
+ *     -e filter com.android.test.filters.SelectTest                        \
+ *     -e selectTest package1.,package2.classA,package3.ClassB#methodZ \
+ *     com.tests.pkg/androidx.test.runner.AndroidJUnitRunner
+ * </pre>
+ * will select out all tests in package1, all tests in classA, and ClassB#methodZ test.
+ *
+ * <p>Note that when this option is specified with either {@code -e package} or {@code -e class}
+ * option, filtering behaves as logically conjunction. Other options, such as {@code -e notPackage},
+ * {@code -e notClass}, {@code -e annotation}, and {@code -e notAnnotation}, should work as expected
+ * with this SelectTest option.
+ *
+ * <p>When specified with {@code -e selectTest_verbose true} option, {@link SelectTest} verbosely
+ * logs to logcat while parsing {@code -e selectTest} option.
+ */
+public class SelectTest extends Filter {
+
+    private static final String TAG = SelectTest.class.getSimpleName();
+
+    @VisibleForTesting
+    static final String OPTION_SELECT_TEST = "selectTest";
+    @VisibleForTesting
+    static final String OPTION_SELECT_TEST_VERBOSE = OPTION_SELECT_TEST + "_verbose";
+
+    private static final String ARGUMENT_ITEM_SEPARATOR = ",";
+    private static final String PACKAGE_NAME_SEPARATOR = ".";
+    private static final String METHOD_SEPARATOR = "#";
+
+    @Nullable
+    private final PackageSet mPackageSet;
+
+    /**
+     * Construct {@link SelectTest} filter from instrumentation arguments in {@link Bundle}.
+     *
+     * @param testArgs instrumentation test arguments.
+     */
+    public SelectTest(@NonNull Bundle testArgs) {
+        mPackageSet = parseSelectTest(testArgs);
+    }
+
+    @Override
+    public boolean shouldRun(Description description) {
+        if (mPackageSet == null) {
+            // Accept all tests because this filter is disabled.
+            return true;
+        }
+        String testClassName = description.getClassName();
+        String testMethodName = description.getMethodName();
+        return mPackageSet.accept(testClassName, testMethodName);
+    }
+
+    @Override
+    public String describe() {
+        return OPTION_SELECT_TEST + "=" + mPackageSet;
+    }
+
+    /**
+     * Create {@link #OPTION_SELECT_TEST} argument and add it to {@code testArgs}.
+     *
+     * <p>This method is intended to be used at constructor of extended {@link Filter} class.
+     *
+     * @param testArgs instrumentation test arguments.
+     * @param selectTests array of class name to be selected to run.
+     * @return modified instrumentation test arguments.
+     */
+    @NonNull
+    protected static Bundle addSelectTest(
+            @NonNull Bundle testArgs, @NonNull String... selectTests) {
+        if (selectTests.length == 0) {
+            return testArgs;
+        }
+        testArgs.putString(OPTION_SELECT_TEST, join(Arrays.asList(selectTests)));
+        return testArgs;
+    }
+
+    /**
+     * Parse {@code -e selectTest} argument.
+     * @param testArgs instrumentation test arguments.
+     * @return {@link PackageSet} that will filter tests. Returns {@code null} when no
+     *     {@code -e selectTest} option is specified, thus this filter gets disabled.
+     */
+    @Nullable
+    private static PackageSet parseSelectTest(Bundle testArgs) {
+        final String selectTestArgs = testArgs.getString(OPTION_SELECT_TEST);
+        if (selectTestArgs == null) {
+            Log.w(TAG, "Disabled because no " + OPTION_SELECT_TEST + " option specified");
+            return null;
+        }
+
+        final boolean verbose = new Boolean(testArgs.getString(OPTION_SELECT_TEST_VERBOSE));
+        final PackageSet packageSet = new PackageSet(verbose);
+        for (String selectTestArg : selectTestArgs.split(ARGUMENT_ITEM_SEPARATOR)) {
+            packageSet.add(selectTestArg);
+        }
+        return packageSet;
+    }
+
+    private static String getPackageName(String selectTestArg) {
+        int endPackagePos = selectTestArg.lastIndexOf(PACKAGE_NAME_SEPARATOR);
+        return (endPackagePos < 0) ? "" : selectTestArg.substring(0, endPackagePos);
+    }
+
+    @Nullable
+    private static String getClassName(String selectTestArg) {
+        if (selectTestArg.endsWith(PACKAGE_NAME_SEPARATOR)) {
+            return null;
+        }
+        int methodSepPos = selectTestArg.indexOf(METHOD_SEPARATOR);
+        return (methodSepPos < 0) ? selectTestArg : selectTestArg.substring(0, methodSepPos);
+    }
+
+    @Nullable
+    private static String getMethodName(String selectTestArg) {
+        int methodSepPos = selectTestArg.indexOf(METHOD_SEPARATOR);
+        return (methodSepPos < 0) ? null : selectTestArg.substring(methodSepPos + 1);
+    }
+
+    /** Package level filter */
+    private static class PackageSet {
+        private final boolean mVerbose;
+        /**
+         * Java package name to {@link ClassSet} map. To represent package filtering, a map value
+         * can be {@code null}.
+         */
+        private final Map<String, ClassSet> mClassSetMap = new LinkedHashMap<>();
+
+        PackageSet(boolean verbose) {
+            mVerbose = verbose;
+        }
+
+        void add(final String selectTestArg) {
+            final String packageName = getPackageName(selectTestArg);
+            final String className = getClassName(selectTestArg);
+
+            if (className == null) {
+                ClassSet classSet = mClassSetMap.put(packageName, null); // package filtering.
+                if (mVerbose) {
+                    logging("Select package " + selectTestArg, classSet != null,
+                            "; supersede " + classSet);
+                }
+                return;
+            }
+
+            ClassSet classSet = mClassSetMap.get(packageName);
+            if (classSet == null) {
+                if (mClassSetMap.containsKey(packageName)) {
+                    if (mVerbose) {
+                        logging("Select package " + packageName + PACKAGE_NAME_SEPARATOR, true,
+                                " ignore " + selectTestArg);
+                    }
+                    return;
+                }
+                classSet = new ClassSet(mVerbose);
+                mClassSetMap.put(packageName, classSet);
+            }
+            classSet.add(selectTestArg);
+        }
+
+        boolean accept(String className, @Nullable String methodName) {
+            String packageName = getPackageName(className);
+            if (!mClassSetMap.containsKey(packageName)) {
+                return false;
+            }
+            ClassSet classSet = mClassSetMap.get(packageName);
+            return classSet == null || classSet.accept(className, methodName);
+        }
+
+        @Override
+        public String toString() {
+            StringJoiner joiner = new StringJoiner(ARGUMENT_ITEM_SEPARATOR);
+            for (String packageName : mClassSetMap.keySet()) {
+                ClassSet classSet = mClassSetMap.get(packageName);
+                joiner.add(classSet == null
+                        ? packageName + PACKAGE_NAME_SEPARATOR : classSet.toString());
+            }
+            return joiner.toString();
+        }
+    }
+
+    /** Class level filter */
+    private static class ClassSet {
+        private final boolean mVerbose;
+        /**
+         * Java class name to set of method names map. To represent class filtering, a map value
+         * can be {@code null}.
+         */
+        private final Map<String, Set<String>> mMethodSetMap = new LinkedHashMap<>();
+
+        ClassSet(boolean verbose) {
+            mVerbose = verbose;
+        }
+
+        void add(String selectTestArg) {
+            final String className = getClassName(selectTestArg);
+            final String methodName = getMethodName(selectTestArg);
+
+            if (methodName == null) {
+                Set<String> methodSet = mMethodSetMap.put(className, null); // class filtering.
+                if (mVerbose) {
+                    logging("Select class " + selectTestArg, methodSet != null,
+                            "; supersede " + toString(className, methodSet));
+                }
+                return;
+            }
+
+            Set<String> methodSet = mMethodSetMap.get(className);
+            if (methodSet == null) {
+                if (mMethodSetMap.containsKey(className)) {
+                    if (mVerbose) {
+                        logging("Select class " + className, true, "; ignore " + selectTestArg);
+                    }
+                    return;
+                }
+                methodSet = new LinkedHashSet<>();
+                mMethodSetMap.put(className, methodSet);
+            }
+
+            methodSet.add(methodName);
+            if (mVerbose) {
+                logging("Select method " + selectTestArg, false, null);
+            }
+        }
+
+        boolean accept(String className, @Nullable String methodName) {
+            if (!mMethodSetMap.containsKey(className)) {
+                return false;
+            }
+            Set<String> methodSet = mMethodSetMap.get(className);
+            return methodName == null || methodSet == null || methodSet.contains(methodName);
+        }
+
+        @Override
+        public String toString() {
+            StringJoiner joiner = new StringJoiner(ARGUMENT_ITEM_SEPARATOR);
+            for (String className : mMethodSetMap.keySet()) {
+                joiner.add(toString(className, mMethodSetMap.get(className)));
+            }
+            return joiner.toString();
+        }
+
+        private static String toString(String className, @Nullable Set<String> methodSet) {
+            if (methodSet == null) {
+                return className;
+            }
+            StringJoiner joiner = new StringJoiner(ARGUMENT_ITEM_SEPARATOR);
+            for (String methodName : methodSet) {
+                joiner.add(className + METHOD_SEPARATOR + methodName);
+            }
+            return joiner.toString();
+        }
+    }
+
+    private static void logging(String infoLog, boolean isWarning, String warningLog) {
+        if (isWarning) {
+            Log.w(TAG, infoLog + warningLog);
+        } else {
+            Log.i(TAG, infoLog);
+        }
+    }
+
+    private static String join(Collection<String> list) {
+        StringJoiner joiner = new StringJoiner(ARGUMENT_ITEM_SEPARATOR);
+        for (String text : list) {
+            joiner.add(text);
+        }
+        return joiner.toString();
+    }
+}
diff --git a/tests/utils/testutils/java/com/android/test/filters/SelectTestTests.java b/tests/utils/testutils/java/com/android/test/filters/SelectTestTests.java
new file mode 100644
index 0000000..163b00a
--- /dev/null
+++ b/tests/utils/testutils/java/com/android/test/filters/SelectTestTests.java
@@ -0,0 +1,220 @@
+/*
+ * 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.test.filters;
+
+import static com.android.test.filters.SelectTest.OPTION_SELECT_TEST;
+import static com.android.test.filters.SelectTest.OPTION_SELECT_TEST_VERBOSE;
+
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertTrue;
+
+import android.os.Bundle;
+import android.util.ArraySet;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.Description;
+import org.junit.runner.manipulation.Filter;
+
+import java.util.Collections;
+import java.util.LinkedHashSet;
+import java.util.Set;
+import java.util.StringJoiner;
+
+public class SelectTestTests {
+
+    private static final String PACKAGE_A = "packageA.";
+    private static final String PACKAGE_B = "packageB.";
+    private static final String PACKAGE_C = "packageC.";
+    private static final String CLASS_A1 = PACKAGE_A + "Class1";
+    private static final String CLASS_A2 = PACKAGE_A + "Class2";
+    private static final String CLASS_B3 = PACKAGE_B + "Class3";
+    private static final String CLASS_B4 = PACKAGE_B + "Class4";
+    private static final String CLASS_C5 = PACKAGE_C + "Class5";
+    private static final String CLASS_C6 = PACKAGE_C + "Class6";
+    private static final String METHOD_A1K = CLASS_A1 + "#methodK";
+    private static final String METHOD_A1L = CLASS_A1 + "#methodL";
+    private static final String METHOD_A2M = CLASS_A2 + "#methodM";
+    private static final String METHOD_A2N = CLASS_A2 + "#methodN";
+    private static final String METHOD_B3P = CLASS_B3 + "#methodP";
+    private static final String METHOD_B3Q = CLASS_B3 + "#methodQ";
+    private static final String METHOD_B4R = CLASS_B4 + "#methodR";
+    private static final String METHOD_B4S = CLASS_B4 + "#methodS";
+    private static final String METHOD_C5W = CLASS_C5 + "#methodW";
+    private static final String METHOD_C5X = CLASS_C5 + "#methodX";
+    private static final String METHOD_C6Y = CLASS_C6 + "#methodY";
+    private static final String METHOD_C6Z = CLASS_C6 + "#methodZ";
+
+    private static final Set<Description> TEST_METHOD_A1K = methodTest(METHOD_A1K);
+    private static final Set<Description> TEST_METHOD_A1L = methodTest(METHOD_A1L);
+    private static final Set<Description> TEST_METHOD_A2M = methodTest(METHOD_A2M);
+    private static final Set<Description> TEST_METHOD_A2N = methodTest(METHOD_A2N);
+    private static final Set<Description> TEST_METHOD_B3P = methodTest(METHOD_B3P);
+    private static final Set<Description> TEST_METHOD_B3Q = methodTest(METHOD_B3Q);
+    private static final Set<Description> TEST_METHOD_B4R = methodTest(METHOD_B4R);
+    private static final Set<Description> TEST_METHOD_B4S = methodTest(METHOD_B4S);
+    private static final Set<Description> TEST_METHOD_C5W = methodTest(METHOD_C5W);
+    private static final Set<Description> TEST_METHOD_C5X = methodTest(METHOD_C5X);
+    private static final Set<Description> TEST_METHOD_C6Y = methodTest(METHOD_C6Y);
+    private static final Set<Description> TEST_METHOD_C6Z = methodTest(METHOD_C6Z);
+    private static final Set<Description> TEST_CLASS_A1 = merge(TEST_METHOD_A1K, TEST_METHOD_A1L);
+    private static final Set<Description> TEST_CLASS_A2 = merge(TEST_METHOD_A2M, TEST_METHOD_A2N);
+    private static final Set<Description> TEST_CLASS_B3 = merge(TEST_METHOD_B3P, TEST_METHOD_B3Q);
+    private static final Set<Description> TEST_CLASS_B4 = merge(TEST_METHOD_B4R, TEST_METHOD_B4S);
+    private static final Set<Description> TEST_CLASS_C5 = merge(TEST_METHOD_C5W, TEST_METHOD_C5X);
+    private static final Set<Description> TEST_CLASS_C6 = merge(TEST_METHOD_C6Y, TEST_METHOD_C6Z);
+    private static final Set<Description> TEST_PACKAGE_A = merge(TEST_CLASS_A1, TEST_CLASS_A2);
+    private static final Set<Description> TEST_PACKAGE_B = merge(TEST_CLASS_B3, TEST_CLASS_B4);
+    private static final Set<Description> TEST_PACKAGE_C = merge(TEST_CLASS_C5, TEST_CLASS_C6);
+    private static final Set<Description> TEST_ALL =
+            merge(TEST_PACKAGE_A, TEST_PACKAGE_B, TEST_PACKAGE_C);
+
+    private SelectTestBuilder mBuilder;
+
+    @Before
+    public void setUp() {
+        mBuilder = new SelectTestBuilder();
+    }
+
+    private static class SelectTestBuilder {
+        private final Bundle mTestArgs = new Bundle();
+
+        Filter build() {
+            mTestArgs.putString(OPTION_SELECT_TEST_VERBOSE, Boolean.TRUE.toString());
+            return new SelectTest(mTestArgs);
+        }
+
+        SelectTestBuilder withSelectTest(String... selectTestArgs) {
+            putTestOption(OPTION_SELECT_TEST, selectTestArgs);
+            return this;
+        }
+
+        private void putTestOption(String option, String... args) {
+            if (args.length > 0) {
+                StringJoiner joiner = new StringJoiner(",");
+                for (String arg : args) {
+                    joiner.add(arg);
+                }
+                mTestArgs.putString(option, joiner.toString());
+            }
+        }
+    }
+
+    private static Set<Description> methodTest(String testName) {
+        int methodSep = testName.indexOf("#");
+        String className = testName.substring(0, methodSep);
+        String methodName = testName.substring(methodSep + 1);
+        final Set<Description> tests = new ArraySet<>();
+        tests.add(Description.createSuiteDescription(className));
+        tests.add(Description.createTestDescription(className, methodName));
+        return Collections.unmodifiableSet(tests);
+    }
+
+    @SafeVarargs
+    private static Set<Description> merge(Set<Description>... testSpecs) {
+        final Set<Description> merged = new LinkedHashSet<>();
+        for (Set<Description> testSet : testSpecs) {
+            merged.addAll(testSet);
+        }
+        return Collections.unmodifiableSet(merged);
+    }
+
+    @SafeVarargs
+    private static void acceptTests(Filter filter, Set<Description>... testSpecs) {
+        final Set<Description> accepts = merge(testSpecs);
+        for (Description test : TEST_ALL) {
+            if (accepts.contains(test)) {
+                assertTrue("accept " + test, filter.shouldRun(test));
+            } else {
+                assertFalse("reject " + test, filter.shouldRun(test));
+            }
+        }
+    }
+
+    @Test
+    public void testFilterDisabled() {
+        final Filter filter = mBuilder.build();
+        acceptTests(filter, TEST_ALL);
+    }
+
+    @Test
+    public void testSelectPackage() {
+        final Filter filter = mBuilder.withSelectTest(PACKAGE_A, PACKAGE_B).build();
+        acceptTests(filter, TEST_PACKAGE_A, TEST_PACKAGE_B);
+    }
+
+    @Test
+    public void testSelectClass() {
+        final Filter filter = mBuilder.withSelectTest(CLASS_A1, CLASS_A2, CLASS_B3).build();
+        acceptTests(filter, TEST_CLASS_A1, TEST_CLASS_A2, TEST_CLASS_B3);
+    }
+
+    @Test
+    public void testSelectMethod() {
+        final Filter filter = mBuilder
+                .withSelectTest(METHOD_A1K, METHOD_A2M, METHOD_A2N, METHOD_B3P).build();
+        acceptTests(filter, TEST_METHOD_A1K, TEST_METHOD_A2M, TEST_METHOD_A2N, TEST_METHOD_B3P);
+    }
+
+    @Test
+    public void testSelectClassAndPackage() {
+        final Filter filter = mBuilder.withSelectTest(CLASS_A1, PACKAGE_B, CLASS_C5).build();
+        acceptTests(filter, TEST_CLASS_A1, TEST_PACKAGE_B, TEST_CLASS_C5);
+    }
+
+    @Test
+    public void testSelectMethodAndPackage() {
+        final Filter filter = mBuilder.withSelectTest(METHOD_A1K, PACKAGE_B, METHOD_C5W).build();
+        acceptTests(filter, TEST_METHOD_A1K, TEST_PACKAGE_B, TEST_METHOD_C5W);
+    }
+
+    @Test
+    public void testSelectMethodAndClass() {
+        final Filter filter = mBuilder.withSelectTest(METHOD_A1K, CLASS_C5, METHOD_B3P).build();
+        acceptTests(filter, TEST_METHOD_A1K, TEST_CLASS_C5, TEST_METHOD_B3P);
+    }
+
+    @Test
+    public void testSelectClassAndSamePackage() {
+        final Filter filter = mBuilder.withSelectTest(
+                CLASS_A1, PACKAGE_A, CLASS_B3, PACKAGE_C, CLASS_C5).build();
+        acceptTests(filter, TEST_PACKAGE_A, TEST_CLASS_B3, TEST_PACKAGE_C);
+    }
+
+    @Test
+    public void testSelectMethodAndSameClass() {
+        final Filter filter = mBuilder.withSelectTest(
+                METHOD_A1K, METHOD_A2M, CLASS_A1, CLASS_B3, METHOD_B3P, METHOD_B4R).build();
+        acceptTests(filter, TEST_CLASS_A1, TEST_METHOD_A2M, TEST_CLASS_B3, TEST_METHOD_B4R);
+    }
+
+    @Test
+    public void testSelectMethodAndSamePackage() {
+        final Filter filter = mBuilder.withSelectTest(
+                METHOD_A1K, METHOD_A1L, METHOD_A2M, PACKAGE_A,
+                PACKAGE_C, METHOD_C5W, METHOD_C5X, METHOD_C6Y).build();
+        acceptTests(filter, TEST_PACKAGE_A, TEST_PACKAGE_C);
+    }
+
+    @Test
+    public void testSelectMethodAndClassAndPackage() {
+        final Filter filter = mBuilder.withSelectTest(
+                METHOD_A1K, CLASS_A1, METHOD_A1L, METHOD_A2M, PACKAGE_A,
+                PACKAGE_B, METHOD_B3Q, CLASS_B3, METHOD_B4R, METHOD_B3P).build();
+        acceptTests(filter, TEST_PACKAGE_A, TEST_PACKAGE_B);
+    }
+}
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/Compile.cpp b/tools/aapt2/cmd/Compile.cpp
index 92beb4e..0512bdc 100644
--- a/tools/aapt2/cmd/Compile.cpp
+++ b/tools/aapt2/cmd/Compile.cpp
@@ -469,16 +469,12 @@
       return false;
     }
 
-    // Read the file as a string
-    char buffer_2[data->size()];
-    memcpy(&buffer_2, data->data(), data->size());
-    StringPiece content(buffer_2, data->size());
-
     BigBuffer crunched_png_buffer(4096);
     io::BigBufferOutputStream crunched_png_buffer_out(&crunched_png_buffer);
 
     // Ensure that we only keep the chunks we care about if we end up
     // using the original PNG instead of the crunched one.
+    const StringPiece content(reinterpret_cast<const char*>(data->data()), data->size());
     PngChunkFilter png_chunk_filter(content);
     std::unique_ptr<Image> image = ReadPng(context, path_data.source, &png_chunk_filter);
     if (!image) {
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/processors/unsupportedappusage/Android.bp b/tools/processors/unsupportedappusage/Android.bp
index 1aca3ed..0e33fdd 100644
--- a/tools/processors/unsupportedappusage/Android.bp
+++ b/tools/processors/unsupportedappusage/Android.bp
@@ -1,6 +1,8 @@
 
-java_library_host {
+java_plugin {
     name: "unsupportedappusage-annotation-processor",
+    processor_class: "android.processor.unsupportedappusage.UnsupportedAppUsageProcessor",
+
     java_resources: [
         "META-INF/**/*",
     ],
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 8c00ceb..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:
@@ -2084,6 +2078,8 @@
     public static final int WIFI_FEATURE_LOW_LATENCY      = 0x40000000; // Low Latency modes
     /** @hide */
     public static final int WIFI_FEATURE_DPP              = 0x80000000; // DPP (Easy-Connect)
+    /** @hide */
+    public static final long WIFI_FEATURE_P2P_RAND_MAC    = 0x100000000L; // Random P2P MAC
 
     private long getSupportedFeatures() {
         try {
@@ -2288,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.
      */
@@ -2599,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
@@ -2682,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>
@@ -3717,10 +3712,8 @@
      * @param SSID, in the format of WifiConfiguration's SSID.
      * @hide
      */
-    @SystemApi
     @RequiresPermission(anyOf = {
             android.Manifest.permission.NETWORK_SETTINGS,
-            android.Manifest.permission.NETWORK_SETUP_WIZARD,
             android.Manifest.permission.NETWORK_STACK
     })
     public void disableEphemeralNetwork(String SSID) {
@@ -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/wifi/java/android/net/wifi/WifiNetworkAgentSpecifier.java b/wifi/java/android/net/wifi/WifiNetworkAgentSpecifier.java
index aa1669e..52ee742 100644
--- a/wifi/java/android/net/wifi/WifiNetworkAgentSpecifier.java
+++ b/wifi/java/android/net/wifi/WifiNetworkAgentSpecifier.java
@@ -28,6 +28,7 @@
 import android.net.NetworkSpecifier;
 import android.os.Parcel;
 import android.os.Parcelable;
+import android.text.TextUtils;
 
 import java.util.Objects;
 
@@ -50,12 +51,24 @@
      */
     private final int mOriginalRequestorUid;
 
+    /**
+     * The package name of the app that requested a specific wifi network using
+     * {@link WifiNetworkSpecifier}.
+     *
+     * Will only be filled when the device connects to a wifi network as a result of a
+     * {@link NetworkRequest} with {@link WifiNetworkSpecifier}. Will be set to null if the device
+     * auto-connected to a wifi network.
+     */
+    private final String mOriginalRequestorPackageName;
+
     public WifiNetworkAgentSpecifier(@NonNull WifiConfiguration wifiConfiguration,
-                                     int originalRequestorUid) {
+                                     int originalRequestorUid,
+                                     @Nullable String originalRequestorPackageName) {
         checkNotNull(wifiConfiguration);
 
         mWifiConfiguration = wifiConfiguration;
         mOriginalRequestorUid = originalRequestorUid;
+        mOriginalRequestorPackageName = originalRequestorPackageName;
     }
 
     /**
@@ -67,7 +80,9 @@
                 public WifiNetworkAgentSpecifier createFromParcel(@NonNull Parcel in) {
                     WifiConfiguration wifiConfiguration = in.readParcelable(null);
                     int originalRequestorUid = in.readInt();
-                    return new WifiNetworkAgentSpecifier(wifiConfiguration, originalRequestorUid);
+                    String originalRequestorPackageName = in.readString();
+                    return new WifiNetworkAgentSpecifier(
+                            wifiConfiguration, originalRequestorUid, originalRequestorPackageName);
                 }
 
                 @Override
@@ -85,6 +100,7 @@
     public void writeToParcel(@NonNull Parcel dest, int flags) {
         dest.writeParcelable(mWifiConfiguration, flags);
         dest.writeInt(mOriginalRequestorUid);
+        dest.writeString(mOriginalRequestorPackageName);
     }
 
     @Override
@@ -137,6 +153,9 @@
         if (ns.requestorUid != this.mOriginalRequestorUid) {
             return false;
         }
+        if (!TextUtils.equals(ns.requestorPackageName, this.mOriginalRequestorPackageName)) {
+            return false;
+        }
         return true;
     }
 
@@ -146,7 +165,8 @@
                 mWifiConfiguration.SSID,
                 mWifiConfiguration.BSSID,
                 mWifiConfiguration.allowedKeyManagement,
-                mOriginalRequestorUid);
+                mOriginalRequestorUid,
+                mOriginalRequestorPackageName);
     }
 
     @Override
@@ -162,7 +182,9 @@
                 && Objects.equals(this.mWifiConfiguration.BSSID, lhs.mWifiConfiguration.BSSID)
                 && Objects.equals(this.mWifiConfiguration.allowedKeyManagement,
                     lhs.mWifiConfiguration.allowedKeyManagement)
-                && mOriginalRequestorUid == lhs.mOriginalRequestorUid;
+                && mOriginalRequestorUid == lhs.mOriginalRequestorUid
+                && TextUtils.equals(mOriginalRequestorPackageName,
+                lhs.mOriginalRequestorPackageName);
     }
 
     @Override
@@ -172,6 +194,7 @@
                 .append(", SSID=").append(mWifiConfiguration.SSID)
                 .append(", BSSID=").append(mWifiConfiguration.BSSID)
                 .append(", mOriginalRequestorUid=").append(mOriginalRequestorUid)
+                .append(", mOriginalRequestorPackageName=").append(mOriginalRequestorPackageName)
                 .append("]");
         return sb.toString();
     }
diff --git a/wifi/java/android/net/wifi/WifiNetworkConfigBuilder.java b/wifi/java/android/net/wifi/WifiNetworkConfigBuilder.java
index ecee5ff0..42d4393 100644
--- a/wifi/java/android/net/wifi/WifiNetworkConfigBuilder.java
+++ b/wifi/java/android/net/wifi/WifiNetworkConfigBuilder.java
@@ -20,6 +20,7 @@
 
 import android.annotation.NonNull;
 import android.annotation.Nullable;
+import android.app.ActivityThread;
 import android.net.MacAddress;
 import android.net.NetworkRequest;
 import android.net.NetworkSpecifier;
@@ -586,7 +587,8 @@
                 mSsidPatternMatcher,
                 mBssidPatternMatcher,
                 buildWifiConfiguration(),
-                Process.myUid());
+                Process.myUid(),
+                ActivityThread.currentApplication().getApplicationContext().getOpPackageName());
     }
 
     /**
@@ -648,7 +650,8 @@
                 buildWifiConfiguration(),
                 mIsAppInteractionRequired,
                 mIsUserInteractionRequired,
-                Process.myUid());
+                Process.myUid(),
+                ActivityThread.currentApplication().getApplicationContext().getOpPackageName());
 
     }
 }
diff --git a/wifi/java/android/net/wifi/WifiNetworkSpecifier.java b/wifi/java/android/net/wifi/WifiNetworkSpecifier.java
index 6e4eeef..a5f4675 100644
--- a/wifi/java/android/net/wifi/WifiNetworkSpecifier.java
+++ b/wifi/java/android/net/wifi/WifiNetworkSpecifier.java
@@ -25,6 +25,7 @@
 import android.os.Parcel;
 import android.os.Parcelable;
 import android.os.PatternMatcher;
+import android.text.TextUtils;
 import android.util.Pair;
 
 import java.util.Objects;
@@ -63,18 +64,25 @@
      */
     public final int requestorUid;
 
+    /**
+     * The package name of the app initializing this network specifier.
+     */
+    public final String requestorPackageName;
+
     public WifiNetworkSpecifier(@NonNull PatternMatcher ssidPatternMatcher,
                  @NonNull Pair<MacAddress, MacAddress> bssidPatternMatcher,
                  @NonNull WifiConfiguration wifiConfiguration,
-                 int requestorUid) {
+                 int requestorUid, @NonNull String requestorPackageName) {
         checkNotNull(ssidPatternMatcher);
         checkNotNull(bssidPatternMatcher);
         checkNotNull(wifiConfiguration);
+        checkNotNull(requestorPackageName);
 
         this.ssidPatternMatcher = ssidPatternMatcher;
         this.bssidPatternMatcher = bssidPatternMatcher;
         this.wifiConfiguration = wifiConfiguration;
         this.requestorUid = requestorUid;
+        this.requestorPackageName = requestorPackageName;
     }
 
     public static final Creator<WifiNetworkSpecifier> CREATOR =
@@ -88,8 +96,9 @@
                             Pair.create(baseAddress, mask);
                     WifiConfiguration wifiConfiguration = in.readParcelable(null);
                     int requestorUid = in.readInt();
+                    String requestorPackageName = in.readString();
                     return new WifiNetworkSpecifier(ssidPatternMatcher, bssidPatternMatcher,
-                            wifiConfiguration, requestorUid);
+                            wifiConfiguration, requestorUid, requestorPackageName);
                 }
 
                 @Override
@@ -110,6 +119,7 @@
         dest.writeParcelable(bssidPatternMatcher.second, flags);
         dest.writeParcelable(wifiConfiguration, flags);
         dest.writeInt(requestorUid);
+        dest.writeString(requestorPackageName);
     }
 
     @Override
@@ -136,7 +146,7 @@
                 ssidPatternMatcher.getType(),
                 bssidPatternMatcher,
                 wifiConfiguration.allowedKeyManagement,
-                requestorUid);
+                requestorUid, requestorPackageName);
     }
 
     @Override
@@ -156,7 +166,8 @@
                     lhs.bssidPatternMatcher)
                 && Objects.equals(this.wifiConfiguration.allowedKeyManagement,
                     lhs.wifiConfiguration.allowedKeyManagement)
-                && requestorUid == lhs.requestorUid;
+                && requestorUid == lhs.requestorUid
+                && TextUtils.equals(requestorPackageName, lhs.requestorPackageName);
     }
 
     @Override
@@ -168,6 +179,7 @@
                 .append(", SSID=").append(wifiConfiguration.SSID)
                 .append(", BSSID=").append(wifiConfiguration.BSSID)
                 .append(", requestorUid=").append(requestorUid)
+                .append(", requestorPackageName=").append(requestorPackageName)
                 .append("]")
                 .toString();
     }
diff --git a/wifi/java/android/net/wifi/WifiNetworkSuggestion.java b/wifi/java/android/net/wifi/WifiNetworkSuggestion.java
index 3c90eb7..6b05dfc 100644
--- a/wifi/java/android/net/wifi/WifiNetworkSuggestion.java
+++ b/wifi/java/android/net/wifi/WifiNetworkSuggestion.java
@@ -18,8 +18,10 @@
 
 import static com.android.internal.util.Preconditions.checkNotNull;
 
+import android.annotation.NonNull;
 import android.os.Parcel;
 import android.os.Parcelable;
+import android.text.TextUtils;
 
 import java.util.List;
 import java.util.Objects;
@@ -58,17 +60,25 @@
      */
     public final int suggestorUid;
 
+    /**
+     * The package name of the process initializing this network suggestion.
+     * @hide
+     */
+    public final String suggestorPackageName;
+
     /** @hide */
-    public WifiNetworkSuggestion(WifiConfiguration wifiConfiguration,
+    public WifiNetworkSuggestion(@NonNull WifiConfiguration wifiConfiguration,
                                  boolean isAppInteractionRequired,
                                  boolean isUserInteractionRequired,
-                                 int suggestorUid) {
+                                 int suggestorUid, @NonNull String suggestorPackageName) {
         checkNotNull(wifiConfiguration);
+        checkNotNull(suggestorPackageName);
 
         this.wifiConfiguration = wifiConfiguration;
         this.isAppInteractionRequired = isAppInteractionRequired;
         this.isUserInteractionRequired = isUserInteractionRequired;
         this.suggestorUid = suggestorUid;
+        this.suggestorPackageName = suggestorPackageName;
     }
 
     public static final Creator<WifiNetworkSuggestion> CREATOR =
@@ -79,7 +89,8 @@
                             in.readParcelable(null), // wifiConfiguration
                             in.readBoolean(), // isAppInteractionRequired
                             in.readBoolean(), // isUserInteractionRequired
-                            in.readInt() // suggestorUid
+                            in.readInt(), // suggestorUid
+                            in.readString() // suggestorPackageName
                     );
                 }
 
@@ -100,12 +111,13 @@
         dest.writeBoolean(isAppInteractionRequired);
         dest.writeBoolean(isUserInteractionRequired);
         dest.writeInt(suggestorUid);
+        dest.writeString(suggestorPackageName);
     }
 
     @Override
     public int hashCode() {
         return Objects.hash(wifiConfiguration.SSID, wifiConfiguration.BSSID,
-                wifiConfiguration.allowedKeyManagement, suggestorUid);
+                wifiConfiguration.allowedKeyManagement, suggestorUid, suggestorPackageName);
     }
 
     /**
@@ -124,7 +136,8 @@
                 && Objects.equals(this.wifiConfiguration.BSSID, lhs.wifiConfiguration.BSSID)
                 && Objects.equals(this.wifiConfiguration.allowedKeyManagement,
                                   lhs.wifiConfiguration.allowedKeyManagement)
-                && suggestorUid == lhs.suggestorUid;
+                && suggestorUid == lhs.suggestorUid
+                && TextUtils.equals(suggestorPackageName, lhs.suggestorPackageName);
     }
 
     @Override
@@ -135,6 +148,7 @@
                 .append(", isAppInteractionRequired=").append(isAppInteractionRequired)
                 .append(", isUserInteractionRequired=").append(isUserInteractionRequired)
                 .append(", suggestorUid=").append(suggestorUid)
+                .append(", suggestorPackageName=").append(suggestorPackageName)
                 .append("]");
         return sb.toString();
     }
diff --git a/tests/utils/SleepUtils/AlarmService/src/com/android/testing/alarmservice/Alarm.aidl b/wifi/java/android/net/wifi/WifiUsabilityStatsEntry.aidl
similarity index 73%
copy from tests/utils/SleepUtils/AlarmService/src/com/android/testing/alarmservice/Alarm.aidl
copy to wifi/java/android/net/wifi/WifiUsabilityStatsEntry.aidl
index 62a8c48..839af54 100644
--- a/tests/utils/SleepUtils/AlarmService/src/com/android/testing/alarmservice/Alarm.aidl
+++ b/wifi/java/android/net/wifi/WifiUsabilityStatsEntry.aidl
@@ -1,5 +1,5 @@
 /*
- * Copyright (C) 2013 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.
@@ -14,10 +14,6 @@
  * limitations under the License.
  */
 
-package com.android.testing.alarmservice;
+package android.net.wifi;
 
-interface Alarm {
-    int prepare();
-    int setAlarmAndWait(long timeoutMills);
-    int done();
-}
\ No newline at end of file
+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/hotspot2/ProvisioningCallback.java b/wifi/java/android/net/wifi/hotspot2/ProvisioningCallback.java
index 1ee874a..1d499b6 100644
--- a/wifi/java/android/net/wifi/hotspot2/ProvisioningCallback.java
+++ b/wifi/java/android/net/wifi/hotspot2/ProvisioningCallback.java
@@ -65,9 +65,9 @@
     public static final int OSU_FAILURE_PROVISIONING_NOT_AVAILABLE = 7;
 
     /**
-     * The reason code for provisioning failure due to invalid server url.
+     * The reason code for provisioning failure due to invalid web url format for an OSU web page.
      */
-    public static final int OSU_FAILURE_INVALID_SERVER_URL = 8;
+    public static final int OSU_FAILURE_INVALID_URL_FORMAT_FOR_OSU = 8;
 
     /**
      * The reason code for provisioning failure when a command received is not the expected command
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/WifiNetworkAgentSpecifierTest.java b/wifi/tests/src/android/net/wifi/WifiNetworkAgentSpecifierTest.java
index 2258e4d..e6eece8 100644
--- a/wifi/tests/src/android/net/wifi/WifiNetworkAgentSpecifierTest.java
+++ b/wifi/tests/src/android/net/wifi/WifiNetworkAgentSpecifierTest.java
@@ -38,6 +38,8 @@
 public class WifiNetworkAgentSpecifierTest {
     private static final int TEST_UID = 5;
     private static final int TEST_UID_1 = 8;
+    private static final String TEST_PACKAGE = "com.test";
+    private static final String TEST_PACKAGE_1 = "com.test.1";
     private static final String TEST_SSID = "Test123";
     private static final String TEST_SSID_PATTERN = "Test";
     private static final String TEST_SSID_1 = "456test";
@@ -104,14 +106,14 @@
         WifiNetworkAgentSpecifier specifier1 =
                 new WifiNetworkAgentSpecifier(
                         wifiConfiguration1,
-                        TEST_UID);
+                        TEST_UID, TEST_PACKAGE);
 
         WifiConfiguration wifiConfiguration2 = new WifiConfiguration(wifiConfiguration1);
         wifiConfiguration2.allowedKeyManagement.set(WifiConfiguration.KeyMgmt.NONE);
         WifiNetworkAgentSpecifier specifier2 =
                 new WifiNetworkAgentSpecifier(
                         wifiConfiguration2,
-                        TEST_UID);
+                        TEST_UID, TEST_PACKAGE);
 
         assertFalse(specifier2.equals(specifier1));
     }
@@ -128,14 +130,14 @@
         WifiNetworkAgentSpecifier specifier1 =
                 new WifiNetworkAgentSpecifier(
                         wifiConfiguration1,
-                        TEST_UID);
+                        TEST_UID, TEST_PACKAGE);
 
         WifiConfiguration wifiConfiguration2 = new WifiConfiguration(wifiConfiguration1);
         wifiConfiguration2.SSID = TEST_SSID_1;
         WifiNetworkAgentSpecifier specifier2 =
                 new WifiNetworkAgentSpecifier(
                         wifiConfiguration2,
-                        TEST_UID);
+                        TEST_UID, TEST_PACKAGE);
 
         assertFalse(specifier2.equals(specifier1));
     }
@@ -152,14 +154,14 @@
         WifiNetworkAgentSpecifier specifier1 =
                 new WifiNetworkAgentSpecifier(
                         wifiConfiguration1,
-                        TEST_UID);
+                        TEST_UID, TEST_PACKAGE);
 
         WifiConfiguration wifiConfiguration2 = new WifiConfiguration(wifiConfiguration1);
         wifiConfiguration2.BSSID = TEST_BSSID_1;
         WifiNetworkAgentSpecifier specifier2 =
                 new WifiNetworkAgentSpecifier(
                         wifiConfiguration2,
-                        TEST_UID);
+                        TEST_UID, TEST_PACKAGE);
 
         assertFalse(specifier2.equals(specifier1));
     }
@@ -214,7 +216,7 @@
                 ssidPattern,
                 bssidPattern,
                 wificonfigurationNetworkSpecifier,
-                TEST_UID);
+                TEST_UID, TEST_PACKAGE);
 
         assertTrue(wifiNetworkSpecifier.satisfiedBy(wifiNetworkAgentSpecifier));
         assertTrue(wifiNetworkAgentSpecifier.satisfiedBy(wifiNetworkSpecifier));
@@ -243,7 +245,7 @@
                 ssidPattern,
                 bssidPattern,
                 wificonfigurationNetworkSpecifier,
-                TEST_UID);
+                TEST_UID, TEST_PACKAGE);
 
         assertTrue(wifiNetworkSpecifier.satisfiedBy(wifiNetworkAgentSpecifier));
         assertTrue(wifiNetworkAgentSpecifier.satisfiedBy(wifiNetworkSpecifier));
@@ -272,7 +274,7 @@
                 ssidPattern,
                 bssidPattern,
                 wificonfigurationNetworkSpecifier,
-                TEST_UID);
+                TEST_UID, TEST_PACKAGE);
 
         assertTrue(wifiNetworkSpecifier.satisfiedBy(wifiNetworkAgentSpecifier));
         assertTrue(wifiNetworkAgentSpecifier.satisfiedBy(wifiNetworkSpecifier));
@@ -292,7 +294,7 @@
         WifiNetworkAgentSpecifier wifiNetworkAgentSpecifier =
                 new WifiNetworkAgentSpecifier(
                         wifiConfigurationNetworkAgent,
-                        TEST_UID);
+                        TEST_UID, TEST_PACKAGE);
 
         PatternMatcher ssidPattern =
                 new PatternMatcher(TEST_SSID_PATTERN, PatternMatcher.PATTERN_PREFIX);
@@ -305,7 +307,7 @@
                 ssidPattern,
                 bssidPattern,
                 wificonfigurationNetworkSpecifier,
-                TEST_UID);
+                TEST_UID, TEST_PACKAGE);
 
         assertFalse(wifiNetworkSpecifier.satisfiedBy(wifiNetworkAgentSpecifier));
         assertFalse(wifiNetworkAgentSpecifier.satisfiedBy(wifiNetworkSpecifier));
@@ -325,7 +327,7 @@
         WifiNetworkAgentSpecifier wifiNetworkAgentSpecifier =
                 new WifiNetworkAgentSpecifier(
                         wifiConfigurationNetworkAgent,
-                        TEST_UID);
+                        TEST_UID, TEST_PACKAGE);
 
         PatternMatcher ssidPattern =
                 new PatternMatcher(".*", PatternMatcher.PATTERN_SIMPLE_GLOB);
@@ -339,7 +341,7 @@
                 ssidPattern,
                 bssidPattern,
                 wificonfigurationNetworkSpecifier,
-                TEST_UID);
+                TEST_UID, TEST_PACKAGE);
 
         assertFalse(wifiNetworkSpecifier.satisfiedBy(wifiNetworkAgentSpecifier));
         assertFalse(wifiNetworkAgentSpecifier.satisfiedBy(wifiNetworkSpecifier));
@@ -359,7 +361,7 @@
         WifiNetworkAgentSpecifier wifiNetworkAgentSpecifier =
                 new WifiNetworkAgentSpecifier(
                         wifiConfigurationNetworkAgent,
-                        TEST_UID);
+                        TEST_UID, TEST_PACKAGE);
 
         PatternMatcher ssidPattern =
                 new PatternMatcher(TEST_SSID_PATTERN, PatternMatcher.PATTERN_PREFIX);
@@ -373,7 +375,7 @@
                 ssidPattern,
                 bssidPattern,
                 wificonfigurationNetworkSpecifier,
-                TEST_UID);
+                TEST_UID, TEST_PACKAGE);
 
         assertFalse(wifiNetworkSpecifier.satisfiedBy(wifiNetworkAgentSpecifier));
         assertFalse(wifiNetworkAgentSpecifier.satisfiedBy(wifiNetworkSpecifier));
@@ -401,7 +403,7 @@
                 ssidPattern,
                 bssidPattern,
                 wificonfigurationNetworkSpecifier,
-                TEST_UID);
+                TEST_UID, TEST_PACKAGE);
 
         assertFalse(wifiNetworkSpecifier.satisfiedBy(wifiNetworkAgentSpecifier));
         assertFalse(wifiNetworkAgentSpecifier.satisfiedBy(wifiNetworkSpecifier));
@@ -430,7 +432,7 @@
                 ssidPattern,
                 bssidPattern,
                 wificonfigurationNetworkSpecifier,
-                TEST_UID_1);
+                TEST_UID_1, TEST_PACKAGE_1);
 
         assertFalse(wifiNetworkSpecifier.satisfiedBy(wifiNetworkAgentSpecifier));
         assertFalse(wifiNetworkAgentSpecifier.satisfiedBy(wifiNetworkSpecifier));
@@ -446,7 +448,8 @@
     }
 
     private WifiNetworkAgentSpecifier createDefaultNetworkAgentSpecifier() {
-        return new WifiNetworkAgentSpecifier(createDefaultWifiConfiguration(), TEST_UID);
+        return new WifiNetworkAgentSpecifier(createDefaultWifiConfiguration(), TEST_UID,
+                TEST_PACKAGE);
     }
 
 }
diff --git a/wifi/tests/src/android/net/wifi/WifiNetworkSpecifierTest.java b/wifi/tests/src/android/net/wifi/WifiNetworkSpecifierTest.java
index 2a8df8d..fce247f 100644
--- a/wifi/tests/src/android/net/wifi/WifiNetworkSpecifierTest.java
+++ b/wifi/tests/src/android/net/wifi/WifiNetworkSpecifierTest.java
@@ -38,6 +38,7 @@
 @SmallTest
 public class WifiNetworkSpecifierTest {
     private static final int TEST_UID = 5;
+    private static final String TEST_PACKAGE_NAME = "com.test";
     private static final String TEST_SSID = "Test123";
     private static final String TEST_BSSID_OUI_BASE_ADDRESS = "12:12:12:00:00:00";
     private static final String TEST_BSSID_OUI_MASK = "ff:ff:ff:00:00:00";
@@ -56,7 +57,7 @@
                         Pair.create(MacAddress.fromString(TEST_BSSID_OUI_BASE_ADDRESS),
                                 MacAddress.fromString(TEST_BSSID_OUI_MASK)),
                         wifiConfiguration,
-                        TEST_UID);
+                        TEST_UID, TEST_PACKAGE_NAME);
 
         Parcel parcelW = Parcel.obtain();
         specifier.writeToParcel(parcelW, 0);
@@ -88,7 +89,7 @@
                         Pair.create(MacAddress.fromString(TEST_BSSID_OUI_BASE_ADDRESS),
                                 MacAddress.fromString(TEST_BSSID_OUI_MASK)),
                         wifiConfiguration,
-                        TEST_UID);
+                        TEST_UID, TEST_PACKAGE_NAME);
 
         assertTrue(specifier.satisfiedBy(null));
         assertTrue(specifier.satisfiedBy(new MatchAllNetworkSpecifier()));
@@ -111,14 +112,14 @@
                         Pair.create(MacAddress.fromString(TEST_BSSID_OUI_BASE_ADDRESS),
                                 MacAddress.fromString(TEST_BSSID_OUI_MASK)),
                         wifiConfiguration,
-                        TEST_UID);
+                        TEST_UID, TEST_PACKAGE_NAME);
 
         WifiNetworkSpecifier specifier2 =
                 new WifiNetworkSpecifier(new PatternMatcher(TEST_SSID, PATTERN_LITERAL),
                         Pair.create(MacAddress.fromString(TEST_BSSID_OUI_BASE_ADDRESS),
                                 MacAddress.fromString(TEST_BSSID_OUI_MASK)),
                         wifiConfiguration,
-                        TEST_UID);
+                        TEST_UID, TEST_PACKAGE_NAME);
 
         assertTrue(specifier2.satisfiedBy(specifier1));
     }
@@ -140,7 +141,7 @@
                         Pair.create(MacAddress.fromString(TEST_BSSID_OUI_BASE_ADDRESS),
                                 MacAddress.fromString(TEST_BSSID_OUI_MASK)),
                         wifiConfiguration1,
-                        TEST_UID);
+                        TEST_UID, TEST_PACKAGE_NAME);
 
         WifiConfiguration wifiConfiguration2 = new WifiConfiguration();
         wifiConfiguration2.allowedKeyManagement.set(WifiConfiguration.KeyMgmt.NONE);
@@ -149,7 +150,7 @@
                         Pair.create(MacAddress.fromString(TEST_BSSID_OUI_BASE_ADDRESS),
                                 MacAddress.fromString(TEST_BSSID_OUI_MASK)),
                         wifiConfiguration2,
-                        TEST_UID);
+                        TEST_UID, TEST_PACKAGE_NAME);
 
         assertFalse(specifier2.satisfiedBy(specifier1));
     }
@@ -171,14 +172,14 @@
                         Pair.create(MacAddress.fromString(TEST_BSSID_OUI_BASE_ADDRESS),
                                 MacAddress.fromString(TEST_BSSID_OUI_MASK)),
                         wifiConfiguration,
-                        TEST_UID);
+                        TEST_UID, TEST_PACKAGE_NAME);
 
         WifiNetworkSpecifier specifier2 =
                 new WifiNetworkSpecifier(new PatternMatcher(TEST_SSID, PATTERN_LITERAL),
                         Pair.create(MacAddress.fromString(TEST_BSSID_OUI_BASE_ADDRESS),
                                 MacAddress.fromString(TEST_BSSID_OUI_MASK)),
                         wifiConfiguration,
-                        TEST_UID);
+                        TEST_UID, TEST_PACKAGE_NAME);
 
         assertFalse(specifier2.satisfiedBy(specifier1));
     }
@@ -200,13 +201,42 @@
                         Pair.create(MacAddress.fromString(TEST_BSSID_OUI_BASE_ADDRESS),
                                 MacAddress.fromString(TEST_BSSID_OUI_MASK)),
                         wifiConfiguration,
-                        TEST_UID);
+                        TEST_UID, TEST_PACKAGE_NAME);
 
         WifiNetworkSpecifier specifier2 =
                 new WifiNetworkSpecifier(new PatternMatcher(TEST_SSID, PATTERN_LITERAL),
                         Pair.create(MacAddress.ALL_ZEROS_ADDRESS, MacAddress.ALL_ZEROS_ADDRESS),
                         wifiConfiguration,
-                        TEST_UID);
+                        TEST_UID, TEST_PACKAGE_NAME);
+
+        assertFalse(specifier2.satisfiedBy(specifier1));
+    }
+
+    /**
+     * Validate NetworkSpecifier matching.
+     * a) Create network specifier 1 for WPA_PSK network
+     * b) Create network specifier 2 with different package name .
+     * c) Ensure that the specifier 2 is not satisfied by specifier 1.
+     */
+    @Test
+    public void testWifiNetworkSpecifierDoesNotSatisfyWhenPackageNameDifferent() {
+        WifiConfiguration wifiConfiguration = new WifiConfiguration();
+        wifiConfiguration.allowedKeyManagement.set(WifiConfiguration.KeyMgmt.WPA_PSK);
+        wifiConfiguration.preSharedKey = TEST_PRESHARED_KEY;
+
+        WifiNetworkSpecifier specifier1 =
+                new WifiNetworkSpecifier(new PatternMatcher(TEST_SSID, PATTERN_LITERAL),
+                        Pair.create(MacAddress.fromString(TEST_BSSID_OUI_BASE_ADDRESS),
+                                MacAddress.fromString(TEST_BSSID_OUI_MASK)),
+                        wifiConfiguration,
+                        TEST_UID, TEST_PACKAGE_NAME);
+
+        WifiNetworkSpecifier specifier2 =
+                new WifiNetworkSpecifier(new PatternMatcher(TEST_SSID, PATTERN_LITERAL),
+                        Pair.create(MacAddress.fromString(TEST_BSSID_OUI_BASE_ADDRESS),
+                                MacAddress.fromString(TEST_BSSID_OUI_MASK)),
+                        wifiConfiguration,
+                        TEST_UID, TEST_PACKAGE_NAME + "blah");
 
         assertFalse(specifier2.satisfiedBy(specifier1));
     }
diff --git a/wifi/tests/src/android/net/wifi/WifiNetworkSuggestionTest.java b/wifi/tests/src/android/net/wifi/WifiNetworkSuggestionTest.java
index 31f501f..5f76055 100644
--- a/wifi/tests/src/android/net/wifi/WifiNetworkSuggestionTest.java
+++ b/wifi/tests/src/android/net/wifi/WifiNetworkSuggestionTest.java
@@ -29,6 +29,10 @@
  */
 @SmallTest
 public class WifiNetworkSuggestionTest {
+    private static final int TEST_UID = 45677;
+    private static final int TEST_UID_OTHER = 45673;
+    private static final String TEST_PACKAGE_NAME = "com.test.packagename";
+    private static final String TEST_PACKAGE_NAME_OTHER = "com.test.packagenameother";
     private static final String TEST_SSID = "\"Test123\"";
     private static final String TEST_BSSID = "12:12:12:12:12:12";
     private static final String TEST_SSID_1 = "\"Test1234\"";
@@ -43,7 +47,7 @@
         configuration.BSSID = TEST_BSSID;
         configuration.allowedKeyManagement.set(WifiConfiguration.KeyMgmt.NONE);
         WifiNetworkSuggestion suggestion =
-                new WifiNetworkSuggestion(configuration, false, true, 0);
+                new WifiNetworkSuggestion(configuration, false, true, TEST_UID, TEST_PACKAGE_NAME);
 
         Parcel parcelW = Parcel.obtain();
         suggestion.writeToParcel(parcelW, 0);
@@ -77,14 +81,16 @@
         configuration.BSSID = TEST_BSSID;
         configuration.allowedKeyManagement.set(WifiConfiguration.KeyMgmt.WPA_PSK);
         WifiNetworkSuggestion suggestion =
-                new WifiNetworkSuggestion(configuration, true, false, 0);
+                new WifiNetworkSuggestion(configuration, true, false, TEST_UID,
+                        TEST_PACKAGE_NAME);
 
         WifiConfiguration configuration1 = new WifiConfiguration();
         configuration1.SSID = TEST_SSID;
         configuration1.BSSID = TEST_BSSID;
         configuration1.allowedKeyManagement.set(WifiConfiguration.KeyMgmt.WPA_PSK);
         WifiNetworkSuggestion suggestion1 =
-                new WifiNetworkSuggestion(configuration1, false, true, 0);
+                new WifiNetworkSuggestion(configuration1, false, true, TEST_UID,
+                        TEST_PACKAGE_NAME);
 
         assertEquals(suggestion, suggestion1);
     }
@@ -99,13 +105,15 @@
         configuration.SSID = TEST_SSID;
         configuration.allowedKeyManagement.set(WifiConfiguration.KeyMgmt.NONE);
         WifiNetworkSuggestion suggestion =
-                new WifiNetworkSuggestion(configuration, false, false, 0);
+                new WifiNetworkSuggestion(configuration, false, false, TEST_UID,
+                        TEST_PACKAGE_NAME);
 
         WifiConfiguration configuration1 = new WifiConfiguration();
         configuration1.SSID = TEST_SSID_1;
         configuration.allowedKeyManagement.set(WifiConfiguration.KeyMgmt.NONE);
         WifiNetworkSuggestion suggestion1 =
-                new WifiNetworkSuggestion(configuration1, false, false, 0);
+                new WifiNetworkSuggestion(configuration1, false, false, TEST_UID,
+                        TEST_PACKAGE_NAME);
 
         assertNotEquals(suggestion, suggestion1);
     }
@@ -121,13 +129,15 @@
         configuration.BSSID = TEST_BSSID;
         configuration.allowedKeyManagement.set(WifiConfiguration.KeyMgmt.NONE);
         WifiNetworkSuggestion suggestion =
-                new WifiNetworkSuggestion(configuration, false, false, 0);
+                new WifiNetworkSuggestion(configuration, false, false, TEST_UID,
+                        TEST_PACKAGE_NAME);
 
         WifiConfiguration configuration1 = new WifiConfiguration();
         configuration1.SSID = TEST_SSID;
         configuration1.allowedKeyManagement.set(WifiConfiguration.KeyMgmt.NONE);
         WifiNetworkSuggestion suggestion1 =
-                new WifiNetworkSuggestion(configuration1, false, false, 0);
+                new WifiNetworkSuggestion(configuration1, false, false, TEST_UID,
+                        TEST_PACKAGE_NAME);
 
         assertNotEquals(suggestion, suggestion1);
     }
@@ -142,13 +152,15 @@
         configuration.SSID = TEST_SSID;
         configuration.allowedKeyManagement.set(WifiConfiguration.KeyMgmt.NONE);
         WifiNetworkSuggestion suggestion =
-                new WifiNetworkSuggestion(configuration, false, false, 0);
+                new WifiNetworkSuggestion(configuration, false, false, TEST_UID,
+                        TEST_PACKAGE_NAME);
 
         WifiConfiguration configuration1 = new WifiConfiguration();
         configuration1.SSID = TEST_SSID;
         configuration1.allowedKeyManagement.set(WifiConfiguration.KeyMgmt.WPA_PSK);
         WifiNetworkSuggestion suggestion1 =
-                new WifiNetworkSuggestion(configuration1, false, false, 0);
+                new WifiNetworkSuggestion(configuration1, false, false, TEST_UID,
+                        TEST_PACKAGE_NAME);
 
         assertNotEquals(suggestion, suggestion1);
     }
@@ -163,10 +175,31 @@
         configuration.SSID = TEST_SSID;
         configuration.allowedKeyManagement.set(WifiConfiguration.KeyMgmt.NONE);
         WifiNetworkSuggestion suggestion =
-                new WifiNetworkSuggestion(configuration, false, false, 0);
+                new WifiNetworkSuggestion(configuration, false, false, TEST_UID,
+                        TEST_PACKAGE_NAME);
 
         WifiNetworkSuggestion suggestion1 =
-                new WifiNetworkSuggestion(configuration, false, false, 1);
+                new WifiNetworkSuggestion(configuration, false, false, TEST_UID_OTHER,
+                        TEST_PACKAGE_NAME);
+
+        assertNotEquals(suggestion, suggestion1);
+    }
+
+    /**
+     * Check NetworkSuggestion equals returns {@code false} for 2 network suggestions with the same
+     * SSID, BSSID and key mgmt, but different package name.
+     */
+    @Test
+    public void testWifiNetworkSuggestionEqualsFailsWhenPackageNameIsDifferent() {
+        WifiConfiguration configuration = new WifiConfiguration();
+        configuration.SSID = TEST_SSID;
+        configuration.allowedKeyManagement.set(WifiConfiguration.KeyMgmt.NONE);
+        WifiNetworkSuggestion suggestion =
+                new WifiNetworkSuggestion(configuration, false, false, TEST_UID, TEST_PACKAGE_NAME);
+
+        WifiNetworkSuggestion suggestion1 =
+                new WifiNetworkSuggestion(configuration, false, false, TEST_UID,
+                        TEST_PACKAGE_NAME_OTHER);
 
         assertNotEquals(suggestion, suggestion1);
     }
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);
+    }
+}