Merge "Voice state + transcription in VoiceInteractionSvc"
diff --git a/Android.bp b/Android.bp
index 1d93ee7..56f3c23 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",
@@ -471,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",
@@ -504,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",
@@ -523,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",
@@ -637,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",
@@ -691,6 +686,7 @@
             "location/java",
             "lowpan/java",
             "media/java",
+            "media/apex/java",
             "media/mca/effect/java",
             "media/mca/filterfw/java",
             "media/mca/filterpacks/java",
@@ -751,6 +747,7 @@
         "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.vibrator-V1.0-java",
         "android.hardware.vibrator-V1.1-java",
         "android.hardware.vibrator-V1.2-java",
@@ -808,11 +805,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
@@ -1271,7 +1264,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",
     ],
@@ -1333,7 +1326,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,
@@ -1779,6 +1772,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",
@@ -1787,6 +1781,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/api/current.txt b/api/current.txt
index 9e17352..912d52e 100644
--- a/api/current.txt
+++ b/api/current.txt
@@ -8610,6 +8610,13 @@
     method @Deprecated @BinderThread public void onHealthChannelStateChange(android.bluetooth.BluetoothHealthAppConfiguration, android.bluetooth.BluetoothDevice, int, int, android.os.ParcelFileDescriptor, int);
   }
 
+  public final class BluetoothHearingAid implements android.bluetooth.BluetoothProfile {
+    method public java.util.List<android.bluetooth.BluetoothDevice> getConnectedDevices();
+    method public int getConnectionState(android.bluetooth.BluetoothDevice);
+    method public java.util.List<android.bluetooth.BluetoothDevice> getDevicesMatchingConnectionStates(int[]);
+    field public static final String ACTION_CONNECTION_STATE_CHANGED = "android.bluetooth.hearingaid.profile.action.CONNECTION_STATE_CHANGED";
+  }
+
   public final class BluetoothHidDevice implements android.bluetooth.BluetoothProfile {
     method public boolean connect(android.bluetooth.BluetoothDevice);
     method public boolean disconnect(android.bluetooth.BluetoothDevice);
@@ -8705,6 +8712,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
@@ -11675,6 +11683,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";
@@ -17078,6 +17087,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;
@@ -25409,6 +25419,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();
@@ -28470,6 +28481,7 @@
   public class ConnectivityManager {
     method public void addDefaultNetworkActiveListener(android.net.ConnectivityManager.OnNetworkActiveListener);
     method public boolean bindProcessToNetwork(@Nullable android.net.Network);
+    method public android.net.SocketKeepalive createSocketKeepalive(@NonNull android.net.Network, @NonNull android.net.IpSecManager.UdpEncapsulationSocket, @NonNull java.net.InetAddress, @NonNull java.net.InetAddress, @NonNull java.util.concurrent.Executor, @NonNull android.net.SocketKeepalive.Callback);
     method @RequiresPermission(android.Manifest.permission.ACCESS_NETWORK_STATE) @Nullable public android.net.Network getActiveNetwork();
     method @Deprecated @Nullable @RequiresPermission(android.Manifest.permission.ACCESS_NETWORK_STATE) public android.net.NetworkInfo getActiveNetworkInfo();
     method @Deprecated @NonNull @RequiresPermission(android.Manifest.permission.ACCESS_NETWORK_STATE) public android.net.NetworkInfo[] getAllNetworkInfo();
@@ -28974,6 +28986,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();
@@ -38195,6 +38230,8 @@
     field public static final String MEDIA_SCANNER_VOLUME = "volume";
     field public static final String META_DATA_STILL_IMAGE_CAMERA_PREWARM_SERVICE = "android.media.still_image_camera_preview_service";
     field public static final String UNKNOWN_STRING = "<unknown>";
+    field public static final String VOLUME_EXTERNAL = "external";
+    field public static final String VOLUME_INTERNAL = "internal";
   }
 
   public static final class MediaStore.Audio {
@@ -38401,28 +38438,28 @@
     field public static final android.net.Uri INTERNAL_CONTENT_URI;
   }
 
-  public static class MediaStore.Images.Thumbnails implements android.provider.BaseColumns {
-    ctor public MediaStore.Images.Thumbnails();
+  @Deprecated public static class MediaStore.Images.Thumbnails implements android.provider.BaseColumns {
+    ctor @Deprecated public MediaStore.Images.Thumbnails();
     method @Deprecated public static void cancelThumbnailRequest(android.content.ContentResolver, long);
     method @Deprecated public static void cancelThumbnailRequest(android.content.ContentResolver, long, long);
-    method public static android.net.Uri getContentUri(String);
+    method @Deprecated public static android.net.Uri getContentUri(String);
     method @Deprecated public static android.graphics.Bitmap getThumbnail(android.content.ContentResolver, long, int, android.graphics.BitmapFactory.Options);
     method @Deprecated public static android.graphics.Bitmap getThumbnail(android.content.ContentResolver, long, long, int, android.graphics.BitmapFactory.Options);
-    method public static final android.database.Cursor query(android.content.ContentResolver, android.net.Uri, String[]);
-    method public static final android.database.Cursor queryMiniThumbnail(android.content.ContentResolver, long, int, String[]);
-    method public static final android.database.Cursor queryMiniThumbnails(android.content.ContentResolver, android.net.Uri, int, String[]);
+    method @Deprecated public static final android.database.Cursor query(android.content.ContentResolver, android.net.Uri, String[]);
+    method @Deprecated public static final android.database.Cursor queryMiniThumbnail(android.content.ContentResolver, long, int, String[]);
+    method @Deprecated public static final android.database.Cursor queryMiniThumbnails(android.content.ContentResolver, android.net.Uri, int, String[]);
     field @Deprecated public static final String DATA = "_data";
-    field public static final String DEFAULT_SORT_ORDER = "image_id ASC";
-    field public static final android.net.Uri EXTERNAL_CONTENT_URI;
-    field public static final int FULL_SCREEN_KIND = 2; // 0x2
-    field public static final String HEIGHT = "height";
-    field public static final String IMAGE_ID = "image_id";
-    field public static final android.net.Uri INTERNAL_CONTENT_URI;
-    field public static final String KIND = "kind";
-    field public static final int MICRO_KIND = 3; // 0x3
-    field public static final int MINI_KIND = 1; // 0x1
-    field public static final String THUMB_DATA = "thumb_data";
-    field public static final String WIDTH = "width";
+    field @Deprecated public static final String DEFAULT_SORT_ORDER = "image_id ASC";
+    field @Deprecated public static final android.net.Uri EXTERNAL_CONTENT_URI;
+    field @Deprecated public static final int FULL_SCREEN_KIND = 2; // 0x2
+    field @Deprecated public static final String HEIGHT = "height";
+    field @Deprecated public static final String IMAGE_ID = "image_id";
+    field @Deprecated public static final android.net.Uri INTERNAL_CONTENT_URI;
+    field @Deprecated public static final String KIND = "kind";
+    field @Deprecated public static final int MICRO_KIND = 3; // 0x3
+    field @Deprecated public static final int MINI_KIND = 1; // 0x1
+    field @Deprecated public static final String THUMB_DATA = "thumb_data";
+    field @Deprecated public static final String WIDTH = "width";
   }
 
   public static interface MediaStore.MediaColumns extends android.provider.BaseColumns {
@@ -38474,24 +38511,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 {
@@ -39072,6 +39109,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";
@@ -43050,6 +43088,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";
   }
 
@@ -43655,6 +43702,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);
@@ -44666,6 +44714,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);
@@ -44716,6 +44765,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 {
@@ -52628,6 +52679,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);
   }
@@ -52644,12 +52696,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 {
@@ -52988,8 +53040,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);
@@ -53086,6 +53138,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);
   }
@@ -53106,7 +53168,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);
diff --git a/api/system-current.txt b/api/system-current.txt
index 8377023..3466e2e 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
   }
 
@@ -1147,7 +1148,7 @@
     field public static final String ACTION_REQUEST_BLE_SCAN_ALWAYS_AVAILABLE = "android.bluetooth.adapter.action.REQUEST_BLE_SCAN_ALWAYS_AVAILABLE";
   }
 
-  public abstract class BluetoothAdapter.MetadataListener {
+  public abstract static class BluetoothAdapter.MetadataListener {
     ctor public BluetoothAdapter.MetadataListener();
     method public void onMetadataChanged(android.bluetooth.BluetoothDevice, int, String);
   }
@@ -4021,6 +4022,7 @@
   }
 
   public class ConnectivityManager {
+    method @RequiresPermission("android.permission.PACKET_KEEPALIVE_OFFLOAD") public android.net.SocketKeepalive createNattKeepalive(@NonNull android.net.Network, @NonNull java.io.FileDescriptor, @NonNull java.net.InetAddress, @NonNull java.net.InetAddress, @NonNull java.util.concurrent.Executor, @NonNull android.net.SocketKeepalive.Callback);
     method public boolean getAvoidBadWifi();
     method @RequiresPermission(android.Manifest.permission.LOCAL_MAC_ADDRESS) public String getCaptivePortalServerUrl();
     method @RequiresPermission(anyOf={android.Manifest.permission.TETHER_PRIVILEGED, android.Manifest.permission.WRITE_SETTINGS}) public boolean isTetheringSupported();
@@ -4041,6 +4043,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;
@@ -4073,6 +4079,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();
@@ -4083,9 +4090,12 @@
 
   public final class LinkProperties implements android.os.Parcelable {
     ctor public LinkProperties();
+    ctor public LinkProperties(android.net.LinkProperties);
     method public boolean addDnsServer(java.net.InetAddress);
     method public boolean addRoute(android.net.RouteInfo);
     method public void clear();
+    method @Nullable public android.net.IpPrefix getNat64Prefix();
+    method public java.util.List<java.net.InetAddress> getPcscfServers();
     method public String getTcpBufferSizes();
     method public java.util.List<java.net.InetAddress> getValidatedPrivateDnsServers();
     method public boolean hasGlobalIPv6Address();
@@ -4103,6 +4113,8 @@
     method public void setInterfaceName(String);
     method public void setLinkAddresses(java.util.Collection<android.net.LinkAddress>);
     method public void setMtu(int);
+    method public void setNat64Prefix(android.net.IpPrefix);
+    method public void setPcscfServers(java.util.Collection<java.net.InetAddress>);
     method public void setPrivateDnsServerName(@Nullable String);
     method public void setTcpBufferSizes(String);
     method public void setUsePrivateDns(boolean);
@@ -4157,6 +4169,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
@@ -4290,6 +4303,7 @@
   }
 
   public class IpConnectivityLog {
+    ctor public IpConnectivityLog();
     method public boolean log(long, android.net.metrics.IpConnectivityLog.Event);
     method public boolean log(String, android.net.metrics.IpConnectivityLog.Event);
     method public boolean log(android.net.Network, int[], android.net.metrics.IpConnectivityLog.Event);
@@ -4338,6 +4352,20 @@
     field public static final int NETWORK_VALIDATION_FAILED = 3; // 0x3
   }
 
+  public final class RaEvent implements android.net.metrics.IpConnectivityLog.Event {
+  }
+
+  public static class RaEvent.Builder {
+    ctor public RaEvent.Builder();
+    method public android.net.metrics.RaEvent build();
+    method public android.net.metrics.RaEvent.Builder updateDnsslLifetime(long);
+    method public android.net.metrics.RaEvent.Builder updatePrefixPreferredLifetime(long);
+    method public android.net.metrics.RaEvent.Builder updatePrefixValidLifetime(long);
+    method public android.net.metrics.RaEvent.Builder updateRdnssLifetime(long);
+    method public android.net.metrics.RaEvent.Builder updateRouteInfoLifetime(long);
+    method public android.net.metrics.RaEvent.Builder updateRouterLifetime(long);
+  }
+
   public final class ValidationProbeEvent implements android.net.metrics.IpConnectivityLog.Event {
     method public static String getProbeName(int);
     field public static final int DNS_FAILURE = 0; // 0x0
@@ -4609,6 +4637,7 @@
   }
 
   public class WifiManager {
+    method @RequiresPermission("android.permission.WIFI_UPDATE_USABILITY_STATS_SCORE") public void addWifiUsabilityStatsListener(@NonNull java.util.concurrent.Executor, @NonNull android.net.wifi.WifiManager.WifiUsabilityStatsListener);
     method @RequiresPermission(anyOf={"android.permission.NETWORK_SETTINGS", android.Manifest.permission.NETWORK_SETUP_WIZARD, "android.permission.NETWORK_STACK"}) public void connect(android.net.wifi.WifiConfiguration, android.net.wifi.WifiManager.ActionListener);
     method @RequiresPermission(anyOf={"android.permission.NETWORK_SETTINGS", android.Manifest.permission.NETWORK_SETUP_WIZARD, "android.permission.NETWORK_STACK"}) public void connect(int, android.net.wifi.WifiManager.ActionListener);
     method @RequiresPermission(anyOf={"android.permission.NETWORK_SETTINGS", android.Manifest.permission.NETWORK_SETUP_WIZARD, "android.permission.NETWORK_STACK"}) public void disable(int, android.net.wifi.WifiManager.ActionListener);
@@ -4624,6 +4653,7 @@
     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);
@@ -4681,6 +4711,10 @@
     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 {
     ctor public WifiNetworkConnectionStatistics(int, int);
     ctor public WifiNetworkConnectionStatistics();
@@ -4810,6 +4844,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 {
@@ -5565,6 +5624,18 @@
     field public static final String NAMESPACE_NOTIFICATION_ASSISTANT = "notification_assistant";
   }
 
+  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);
   }
@@ -5756,6 +5827,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
@@ -6031,10 +6103,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 {
@@ -6048,7 +6117,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 {
@@ -7585,10 +7653,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
@@ -9093,9 +9164,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 1f894c2..3dfd6e4 100644
--- a/api/test-current.txt
+++ b/api/test-current.txt
@@ -802,11 +802,16 @@
     field public static final String EXTRA_CAPTIVE_PORTAL_USER_AGENT = "android.net.extra.CAPTIVE_PORTAL_USER_AGENT";
   }
 
+  public final class IpPrefix implements android.os.Parcelable {
+    ctor public IpPrefix(java.net.InetAddress, int);
+  }
+
   public final class IpSecManager {
     field public static final int INVALID_SECURITY_PARAMETER_INDEX = 0; // 0x0
   }
 
   public class LinkAddress implements android.os.Parcelable {
+    ctor public LinkAddress(java.net.InetAddress, int, int, int);
     method public boolean isGlobalPreferred();
     method public boolean isIPv4();
     method public boolean isIPv6();
@@ -814,7 +819,10 @@
   }
 
   public final class LinkProperties implements android.os.Parcelable {
+    ctor public LinkProperties(android.net.LinkProperties);
     method public boolean addDnsServer(java.net.InetAddress);
+    method @Nullable public android.net.IpPrefix getNat64Prefix();
+    method public java.util.List<java.net.InetAddress> getPcscfServers();
     method public String getTcpBufferSizes();
     method public java.util.List<java.net.InetAddress> getValidatedPrivateDnsServers();
     method public boolean hasGlobalIPv6Address();
@@ -826,6 +834,8 @@
     method public boolean isReachable(java.net.InetAddress);
     method public boolean removeDnsServer(java.net.InetAddress);
     method public boolean removeRoute(android.net.RouteInfo);
+    method public void setNat64Prefix(android.net.IpPrefix);
+    method public void setPcscfServers(java.util.Collection<java.net.InetAddress>);
     method public void setPrivateDnsServerName(@Nullable String);
     method public void setTcpBufferSizes(String);
     method public void setUsePrivateDns(boolean);
@@ -843,6 +853,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
@@ -929,6 +940,7 @@
   }
 
   public class IpConnectivityLog {
+    ctor public IpConnectivityLog();
     method public boolean log(long, android.net.metrics.IpConnectivityLog.Event);
     method public boolean log(String, android.net.metrics.IpConnectivityLog.Event);
     method public boolean log(android.net.Network, int[], android.net.metrics.IpConnectivityLog.Event);
@@ -977,6 +989,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
@@ -1688,10 +1714,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 {
@@ -1705,7 +1728,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 {
@@ -2341,6 +2363,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/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 60b2e25..008a008 100644
--- a/cmds/statsd/src/atoms.proto
+++ b/cmds/statsd/src/atoms.proto
@@ -206,6 +206,7 @@
         BroadcastDispatchLatencyReported broadcast_dispatch_latency_reported = 142;
         AttentionManagerServiceResultReported attention_manager_service_result_reported = 143;
         AdbConnectionChanged adb_connection_changed = 144;
+        SpeechDspStatReported speech_dsp_stat_reported = 145;
     }
 
     // Pulled events will start at field 10000.
@@ -3018,8 +3019,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;
@@ -4521,3 +4522,18 @@
     // 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;
+}
diff --git a/cmds/statsd/src/external/ResourceThermalManagerPuller.cpp b/cmds/statsd/src/external/ResourceThermalManagerPuller.cpp
deleted file mode 100644
index 53709f1..0000000
--- a/cmds/statsd/src/external/ResourceThermalManagerPuller.cpp
+++ /dev/null
@@ -1,147 +0,0 @@
-/*
- * Copyright (C) 2018 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-#define DEBUG false  // STOPSHIP if true
-#include "Log.h"
-
-#include <android/hardware/thermal/2.0/IThermal.h>
-#include "external/ResourceThermalManagerPuller.h"
-#include "external/StatsPuller.h"
-
-#include "ResourceThermalManagerPuller.h"
-#include "logd/LogEvent.h"
-#include "statslog.h"
-#include "stats_log_util.h"
-
-#include <chrono>
-
-using android::hardware::hidl_death_recipient;
-using android::hardware::hidl_vec;
-using android::hidl::base::V1_0::IBase;
-using ::android::hardware::thermal::V2_0::IThermal;
-using ::android::hardware::thermal::V2_0::Temperature;
-using ::android::hardware::thermal::V2_0::TemperatureType;
-using ::android::hardware::thermal::V1_0::ThermalStatus;
-using ::android::hardware::thermal::V1_0::ThermalStatusCode;
-using android::hardware::Return;
-using android::hardware::Void;
-
-using std::chrono::duration_cast;
-using std::chrono::nanoseconds;
-using std::chrono::system_clock;
-using std::make_shared;
-using std::shared_ptr;
-
-namespace android {
-namespace os {
-namespace statsd {
-
-bool getThermalHalLocked();
-sp<android::hardware::thermal::V2_0::IThermal> gThermalHal = nullptr;
-std::mutex gThermalHalMutex;
-
-struct ThermalHalDeathRecipient : virtual public hidl_death_recipient {
-      virtual void serviceDied(uint64_t cookie, const wp<IBase>& who) override {
-          std::lock_guard<std::mutex> lock(gThermalHalMutex);
-          ALOGE("ThermalHAL just died");
-          gThermalHal = nullptr;
-          getThermalHalLocked();
-      }
-};
-
-sp<ThermalHalDeathRecipient> gThermalHalDeathRecipient = nullptr;
-
-// The caller must be holding gThermalHalMutex.
-bool getThermalHalLocked() {
-    if (gThermalHal == nullptr) {
-            gThermalHal = IThermal::getService();
-            if (gThermalHal == nullptr) {
-                ALOGE("Unable to get Thermal service.");
-            } else {
-                if (gThermalHalDeathRecipient == nullptr) {
-                    gThermalHalDeathRecipient = new ThermalHalDeathRecipient();
-                }
-                hardware::Return<bool> linked = gThermalHal->linkToDeath(
-                    gThermalHalDeathRecipient, 0x451F /* cookie */);
-                if (!linked.isOk()) {
-                    ALOGE("Transaction error in linking to ThermalHAL death: %s",
-                            linked.description().c_str());
-                    gThermalHal = nullptr;
-                } else if (!linked) {
-                    ALOGW("Unable to link to ThermalHal death notifications");
-                    gThermalHal = nullptr;
-                } else {
-                    ALOGD("Link to death notification successful");
-                }
-            }
-    }
-    return gThermalHal != nullptr;
-}
-
-ResourceThermalManagerPuller::ResourceThermalManagerPuller() :
-        StatsPuller(android::util::TEMPERATURE) {
-}
-
-bool ResourceThermalManagerPuller::PullInternal(vector<shared_ptr<LogEvent>>* data) {
-    std::lock_guard<std::mutex> lock(gThermalHalMutex);
-    if (!getThermalHalLocked()) {
-        ALOGE("Thermal Hal not loaded");
-        return false;
-    }
-
-    int64_t wallClockTimestampNs = getWallClockNs();
-    int64_t elapsedTimestampNs = getElapsedRealtimeNs();
-
-    data->clear();
-    bool resultSuccess = true;
-
-    Return<void> ret = gThermalHal->getCurrentTemperatures(false, TemperatureType::SKIN,
-            [&](ThermalStatus status, const hidl_vec<Temperature>& temps) {
-        if (status.code != ThermalStatusCode::SUCCESS) {
-            ALOGE("Failed to get temperatures from ThermalHAL. Status: %d", status.code);
-            resultSuccess = false;
-            return;
-        }
-        if (mTagId == android::util::TEMPERATURE) {
-            for (size_t i = 0; i < temps.size(); ++i) {
-                auto ptr = make_shared<LogEvent>(android::util::TEMPERATURE,
-                        wallClockTimestampNs, elapsedTimestampNs);
-                ptr->write((static_cast<int>(temps[i].type)));
-                ptr->write(temps[i].name);
-                // Convert the temperature to an int.
-                int32_t temp = static_cast<int>(temps[i].value * 10);
-                ptr->write(temp);
-                ptr->init();
-                data->push_back(ptr);
-            }
-        } else {
-            ALOGE("Unsupported tag in ResourceThermalManagerPuller: %d", mTagId);
-        }
-    });
-    if (!ret.isOk()) {
-        ALOGE("getThermalHalLocked() failed: thermal HAL service not available. Description: %s",
-                ret.description().c_str());
-        if (ret.isDeadObject()) {
-            gThermalHal = nullptr;
-        }
-        return false;
-    }
-    return resultSuccess;
-}
-
-}  // namespace statsd
-}  // namespace os
-}  // namespace android
diff --git a/cmds/statsd/src/external/ResourceThermalManagerPuller.h b/cmds/statsd/src/external/ResourceThermalManagerPuller.h
deleted file mode 100644
index 5313792..0000000
--- a/cmds/statsd/src/external/ResourceThermalManagerPuller.h
+++ /dev/null
@@ -1,39 +0,0 @@
-/*
- * Copyright (C) 2018 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-#pragma once
-
-#include <utils/String16.h>
-#include "StatsPuller.h"
-
-namespace android {
-namespace os {
-namespace statsd {
-
-/**
- * Reads IThermal.hal
- */
-class ResourceThermalManagerPuller : public StatsPuller {
-public:
-    ResourceThermalManagerPuller();
-
-private:
-    bool PullInternal(vector<std::shared_ptr<LogEvent>>* data) override;
-};
-
-}  // namespace statsd
-}  // namespace os
-}  // namespace android
\ No newline at end of file
diff --git a/cmds/statsd/src/external/StatsPullerManager.cpp b/cmds/statsd/src/external/StatsPullerManager.cpp
index 19a7389..7e56bee 100644
--- a/cmds/statsd/src/external/StatsPullerManager.cpp
+++ b/cmds/statsd/src/external/StatsPullerManager.cpp
@@ -28,7 +28,6 @@
 #include "../statscompanion_util.h"
 #include "PowerStatsPuller.h"
 #include "ResourceHealthManagerPuller.h"
-#include "ResourceThermalManagerPuller.h"
 #include "StatsCompanionServicePuller.h"
 #include "StatsPullerManager.h"
 #include "SubsystemSleepStatePuller.h"
@@ -150,7 +149,8 @@
           .puller =
                   new StatsCompanionServicePuller(android::util::PROCESS_MEMORY_HIGH_WATER_MARK)}},
         // temperature
-        {android::util::TEMPERATURE, {.puller = new ResourceThermalManagerPuller()}},
+        {android::util::TEMPERATURE,
+          {.puller = new StatsCompanionServicePuller(android::util::TEMPERATURE)}},
         // binder_calls
         {android::util::BINDER_CALLS,
          {.additiveFields = {4, 5, 6, 8, 12},
diff --git a/cmds/statsd/src/logd/LogEvent.cpp b/cmds/statsd/src/logd/LogEvent.cpp
index 78a75c5..eaba9be 100644
--- a/cmds/statsd/src/logd/LogEvent.cpp
+++ b/cmds/statsd/src/logd/LogEvent.cpp
@@ -267,6 +267,22 @@
 }
 
 LogEvent::LogEvent(int64_t wallClockTimestampNs, int64_t elapsedTimestampNs,
+                   const SpeechDspStat& speechDspStat) {
+    mLogdTimestampNs = wallClockTimestampNs;
+    mElapsedTimestampNs = elapsedTimestampNs;
+    mTagId = android::util::SPEECH_DSP_STAT_REPORTED;
+
+    mValues.push_back(FieldValue(Field(mTagId, getSimpleField(1)),
+                                 Value(speechDspStat.totalUptimeMillis)));
+    mValues.push_back(FieldValue(Field(mTagId, getSimpleField(2)),
+                                 Value(speechDspStat.totalDowntimeMillis)));
+    mValues.push_back(FieldValue(Field(mTagId, getSimpleField(3)),
+                                 Value(speechDspStat.totalCrashCount)));
+    mValues.push_back(FieldValue(Field(mTagId, getSimpleField(4)),
+                                 Value(speechDspStat.totalRecoverCount)));
+}
+
+LogEvent::LogEvent(int64_t wallClockTimestampNs, int64_t elapsedTimestampNs,
                    const BatteryCausedShutdown& batteryCausedShutdown) {
     mLogdTimestampNs = wallClockTimestampNs;
     mElapsedTimestampNs = elapsedTimestampNs;
@@ -294,6 +310,36 @@
                                  Value(usbPortOverheatEvent.timeToInactive)));
 }
 
+LogEvent::LogEvent(int64_t wallClockTimestampNs, int64_t elapsedTimestampNs,
+                   const VendorAtom& vendorAtom) {
+    mLogdTimestampNs = wallClockTimestampNs;
+    mElapsedTimestampNs = elapsedTimestampNs;
+    mTagId = vendorAtom.atomId;
+
+    mValues.push_back(
+            FieldValue(Field(mTagId, getSimpleField(1)), Value(vendorAtom.reverseDomainName)));
+    for (int i = 0; i < (int)vendorAtom.values.size(); i++) {
+        switch (vendorAtom.values[i].getDiscriminator()) {
+            case VendorAtom::Value::hidl_discriminator::intValue:
+                mValues.push_back(FieldValue(Field(mTagId, getSimpleField(i + 2)),
+                                             Value(vendorAtom.values[i].intValue())));
+                break;
+            case VendorAtom::Value::hidl_discriminator::longValue:
+                mValues.push_back(FieldValue(Field(mTagId, getSimpleField(i + 2)),
+                                             Value(vendorAtom.values[i].longValue())));
+                break;
+            case VendorAtom::Value::hidl_discriminator::floatValue:
+                mValues.push_back(FieldValue(Field(mTagId, getSimpleField(i + 2)),
+                                             Value(vendorAtom.values[i].floatValue())));
+                break;
+            case VendorAtom::Value::hidl_discriminator::stringValue:
+                mValues.push_back(FieldValue(Field(mTagId, getSimpleField(i + 2)),
+                                             Value(vendorAtom.values[i].stringValue())));
+                break;
+        }
+    }
+}
+
 LogEvent::LogEvent(int32_t tagId, int64_t timestampNs) : LogEvent(tagId, timestampNs, 0) {}
 
 LogEvent::LogEvent(int32_t tagId, int64_t timestampNs, int32_t uid) {
diff --git a/cmds/statsd/src/logd/LogEvent.h b/cmds/statsd/src/logd/LogEvent.h
index 3f47b7e..784376a 100644
--- a/cmds/statsd/src/logd/LogEvent.h
+++ b/cmds/statsd/src/logd/LogEvent.h
@@ -121,6 +121,12 @@
     explicit LogEvent(int64_t wallClockTimestampNs, int64_t elapsedTimestampNs,
                       const UsbPortOverheatEvent& usbPortOverheatEvent);
 
+    explicit LogEvent(int64_t wallClockTimestampNs, int64_t elapsedTimestampNs,
+                      const SpeechDspStat& speechDspStat);
+
+    explicit LogEvent(int64_t wallClockTimestampNs, int64_t elapsedTimestampNs,
+                      const VendorAtom& vendorAtom);
+
     ~LogEvent();
 
     /**
diff --git a/cmds/statsd/src/packages/UidMap.cpp b/cmds/statsd/src/packages/UidMap.cpp
index e9c43cd..5cf012638 100644
--- a/cmds/statsd/src/packages/UidMap.cpp
+++ b/cmds/statsd/src/packages/UidMap.cpp
@@ -548,6 +548,7 @@
                                                              {"AID_LMKD", 1069},
                                                              {"AID_LLKD", 1070},
                                                              {"AID_IORAPD", 1071},
+                                                             {"AID_NETWORK_STACK", 1073},
                                                              {"AID_SHELL", 2000},
                                                              {"AID_CACHE", 2001},
                                                              {"AID_DIAG", 2002}};
diff --git a/cmds/statsd/tools/localtools/src/com/android/statsd/shelltools/testdrive/TestDrive.java b/cmds/statsd/tools/localtools/src/com/android/statsd/shelltools/testdrive/TestDrive.java
index de818a8..d9aba61 100644
--- a/cmds/statsd/tools/localtools/src/com/android/statsd/shelltools/testdrive/TestDrive.java
+++ b/cmds/statsd/tools/localtools/src/com/android/statsd/shelltools/testdrive/TestDrive.java
@@ -55,7 +55,8 @@
         "AID_ROOT",
         "AID_BLUETOOTH",
         "AID_LMKD",
-        "com.android.managedprovisioning"
+        "com.android.managedprovisioning",
+        "AID_NETWORK_STACK"
     };
     private static final Logger LOGGER = Logger.getLogger(TestDrive.class.getName());
 
diff --git a/config/hiddenapi-greylist.txt b/config/hiddenapi-greylist.txt
index fc47f67..ee2fe8f 100644
--- a/config/hiddenapi-greylist.txt
+++ b/config/hiddenapi-greylist.txt
@@ -3518,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/core/java/android/app/Activity.java b/core/java/android/app/Activity.java
index 1063be4..92f47e7 100644
--- a/core/java/android/app/Activity.java
+++ b/core/java/android/app/Activity.java
@@ -8218,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/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/bluetooth/BluetoothAdapter.java b/core/java/android/bluetooth/BluetoothAdapter.java
index 97bc079..ab8c196 100644
--- a/core/java/android/bluetooth/BluetoothAdapter.java
+++ b/core/java/android/bluetooth/BluetoothAdapter.java
@@ -1900,6 +1900,20 @@
     }
 
     /**
+     * Return true if Hearing Aid Profile is supported.
+     *
+     * @return true if phone supports Hearing Aid Profile
+     */
+    private boolean isHearingAidProfileSupported() {
+        try {
+            return mManagerService.isHearingAidProfileSupported();
+        } catch (RemoteException e) {
+            Log.e(TAG, "remote expection when calling isHearingAidProfileSupported", e);
+            return false;
+        }
+    }
+
+    /**
      * Get the maximum number of connected audio devices.
      *
      * @return the maximum number of connected audio devices
@@ -2051,6 +2065,11 @@
                             supportedProfiles.add(i);
                         }
                     }
+                } else {
+                    // Bluetooth is disabled. Just fill in known supported Profiles
+                    if (isHearingAidProfileSupported()) {
+                        supportedProfiles.add(BluetoothProfile.HEARING_AID);
+                    }
                 }
             }
         } catch (RemoteException e) {
@@ -2468,15 +2487,16 @@
      * Get the profile proxy object associated with the profile.
      *
      * <p>Profile can be one of {@link BluetoothProfile#HEADSET}, {@link BluetoothProfile#A2DP},
-     * {@link BluetoothProfile#GATT}, or {@link BluetoothProfile#GATT_SERVER}. Clients must
-     * implement {@link BluetoothProfile.ServiceListener} to get notified of the connection status
-     * and to get the proxy object.
+     * {@link BluetoothProfile#GATT}, {@link BluetoothProfile#HEARING_AID}, or {@link
+     * BluetoothProfile#GATT_SERVER}. Clients must implement {@link
+     * BluetoothProfile.ServiceListener} to get notified of the connection status and to get the
+     * proxy object.
      *
      * @param context Context of the application
      * @param listener The service Listener for connection callbacks.
      * @param profile The Bluetooth profile; either {@link BluetoothProfile#HEADSET},
-     * {@link BluetoothProfile#A2DP}. {@link BluetoothProfile#GATT} or
-     * {@link BluetoothProfile#GATT_SERVER}.
+     * {@link BluetoothProfile#A2DP}, {@link BluetoothProfile#GATT}, {@link
+     * BluetoothProfile#HEARING_AID} or {@link BluetoothProfile#GATT_SERVER}.
      * @return true on success, false on error
      */
     public boolean getProfileProxy(Context context, BluetoothProfile.ServiceListener listener,
@@ -2525,8 +2545,11 @@
             BluetoothHidDevice hidDevice = new BluetoothHidDevice(context, listener);
             return true;
         } else if (profile == BluetoothProfile.HEARING_AID) {
-            BluetoothHearingAid hearingAid = new BluetoothHearingAid(context, listener);
-            return true;
+            if (isHearingAidProfileSupported()) {
+                BluetoothHearingAid hearingAid = new BluetoothHearingAid(context, listener);
+                return true;
+            }
+            return false;
         } else {
             return false;
         }
@@ -3253,7 +3276,7 @@
      * @hide
      */
     @SystemApi
-    public abstract class MetadataListener {
+    public abstract static class MetadataListener {
         /**
          * Callback triggered if the metadata of {@link BluetoothDevice} registered in
          * {@link #registerMetadataListener}.
diff --git a/core/java/android/bluetooth/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..b8670db 100644
--- a/core/java/android/bluetooth/BluetoothProfile.java
+++ b/core/java/android/bluetooth/BluetoothProfile.java
@@ -185,7 +185,6 @@
     /**
      * Hearing Aid Device
      *
-     * @hide
      */
     int HEARING_AID = 21;
 
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/PackageManager.java b/core/java/android/content/pm/PackageManager.java
index 9e2f316..783ee64 100644
--- a/core/java/android/content/pm/PackageManager.java
+++ b/core/java/android/content/pm/PackageManager.java
@@ -2223,6 +2223,13 @@
     public static final String FEATURE_TELEPHONY_MBMS = "android.hardware.telephony.mbms";
 
     /**
+     * Feature for {@link #getSystemAvailableFeatures} and {@link #hasSystemFeature}: The device
+     * supports attaching to IMS implementations using the ImsService API in telephony.
+     */
+    @SdkConstant(SdkConstantType.FEATURE)
+    public static final String FEATURE_TELEPHONY_IMS = "android.hardware.telephony.ims";
+
+    /**
      * Feature for {@link #getSystemAvailableFeatures} and
      * {@link #hasSystemFeature}: The device supports connecting to USB devices
      * as the USB host.
diff --git a/core/java/android/content/pm/PackageParser.java b/core/java/android/content/pm/PackageParser.java
index 88a240f..d5636d5 100644
--- a/core/java/android/content/pm/PackageParser.java
+++ b/core/java/android/content/pm/PackageParser.java
@@ -8513,6 +8513,7 @@
 
         pi.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/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/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/net/ConnectivityManager.java b/core/java/android/net/ConnectivityManager.java
index c809cca..5bb24ba 100644
--- a/core/java/android/net/ConnectivityManager.java
+++ b/core/java/android/net/ConnectivityManager.java
@@ -15,6 +15,9 @@
  */
 package android.net;
 
+import static android.net.IpSecManager.INVALID_RESOURCE_ID;
+
+import android.annotation.CallbackExecutor;
 import android.annotation.IntDef;
 import android.annotation.NonNull;
 import android.annotation.Nullable;
@@ -28,6 +31,8 @@
 import android.app.PendingIntent;
 import android.content.Context;
 import android.content.Intent;
+import android.net.IpSecManager.UdpEncapsulationSocket;
+import android.net.SocketKeepalive.Callback;
 import android.os.Binder;
 import android.os.Build;
 import android.os.Build.VERSION_CODES;
@@ -58,6 +63,7 @@
 
 import libcore.net.event.NetworkEventDispatcher;
 
+import java.io.FileDescriptor;
 import java.lang.annotation.Retention;
 import java.lang.annotation.RetentionPolicy;
 import java.net.InetAddress;
@@ -66,6 +72,7 @@
 import java.util.HashMap;
 import java.util.List;
 import java.util.Map;
+import java.util.concurrent.Executor;
 
 /**
  * Class that answers queries about the state of network connectivity. It also
@@ -1699,6 +1706,8 @@
      * {@link PacketKeepaliveCallback#onStopped} if the operation was successful or
      * {@link PacketKeepaliveCallback#onError} if an error occurred.
      *
+     * @deprecated Use {@link SocketKeepalive} instead.
+     *
      * @hide
      */
     public class PacketKeepalive {
@@ -1802,6 +1811,8 @@
     /**
      * Starts an IPsec NAT-T keepalive packet with the specified parameters.
      *
+     * @deprecated Use {@link #createSocketKeepalive} instead.
+     *
      * @hide
      */
     @UnsupportedAppUsage
@@ -1821,6 +1832,62 @@
     }
 
     /**
+     * Request that keepalives be started on a IPsec NAT-T socket.
+     *
+     * @param network The {@link Network} the socket is on.
+     * @param socket The socket that needs to be kept alive.
+     * @param source The source address of the {@link UdpEncapsulationSocket}.
+     * @param destination The destination address of the {@link UdpEncapsulationSocket}.
+     * @param executor The executor on which callback will be invoked. The provided {@link Executor}
+     *                 must run callback sequentially, otherwise the order of callbacks cannot be
+     *                 guaranteed.
+     * @param callback A {@link SocketKeepalive.Callback}. Used for notifications about keepalive
+     *        changes. Must be extended by applications that use this API.
+     *
+     * @return A {@link SocketKeepalive} object, which can be used to control this keepalive object.
+     **/
+    public SocketKeepalive createSocketKeepalive(@NonNull Network network,
+            @NonNull UdpEncapsulationSocket socket,
+            @NonNull InetAddress source,
+            @NonNull InetAddress destination,
+            @NonNull @CallbackExecutor Executor executor,
+            @NonNull Callback callback) {
+        return new NattSocketKeepalive(mService, network, socket.getFileDescriptor(),
+            socket.getResourceId(), source, destination, executor, callback);
+    }
+
+    /**
+     * Request that keepalives be started on a IPsec NAT-T socket file descriptor. Directly called
+     * by system apps which don't use IpSecService to create {@link UdpEncapsulationSocket}.
+     *
+     * @param network The {@link Network} the socket is on.
+     * @param fd The {@link FileDescriptor} that needs to be kept alive. The provided
+     *        {@link FileDescriptor} must be bound to a port and the keepalives will be sent from
+     *        that port.
+     * @param source The source address of the {@link UdpEncapsulationSocket}.
+     * @param destination The destination address of the {@link UdpEncapsulationSocket}. The
+     *        keepalive packets will always be sent to port 4500 of the given {@code destination}.
+     * @param executor The executor on which callback will be invoked. The provided {@link Executor}
+     *                 must run callback sequentially, otherwise the order of callbacks cannot be
+     *                 guaranteed.
+     * @param callback A {@link SocketKeepalive.Callback}. Used for notifications about keepalive
+     *        changes. Must be extended by applications that use this API.
+     *
+     * @hide
+     */
+    @SystemApi
+    @RequiresPermission(android.Manifest.permission.PACKET_KEEPALIVE_OFFLOAD)
+    public SocketKeepalive createNattKeepalive(@NonNull Network network,
+            @NonNull FileDescriptor fd,
+            @NonNull InetAddress source,
+            @NonNull InetAddress destination,
+            @NonNull @CallbackExecutor Executor executor,
+            @NonNull Callback callback) {
+        return new NattSocketKeepalive(mService, network, fd, INVALID_RESOURCE_ID /* Unused */,
+                source, destination, executor, callback);
+    }
+
+    /**
      * Ensure that a network route exists to deliver traffic to the specified
      * host via the specified network interface. An attempt to add a route that
      * already exists is ignored, but treated as successful.
diff --git a/core/java/android/net/IConnectivityManager.aidl b/core/java/android/net/IConnectivityManager.aidl
index 131925e..e97060a 100644
--- a/core/java/android/net/IConnectivityManager.aidl
+++ b/core/java/android/net/IConnectivityManager.aidl
@@ -181,6 +181,10 @@
     void startNattKeepalive(in Network network, int intervalSeconds, in Messenger messenger,
             in IBinder binder, String srcAddr, int srcPort, String dstAddr);
 
+    void startNattKeepaliveWithFd(in Network network, in FileDescriptor fd, int resourceId,
+            int intervalSeconds, in Messenger messenger, in IBinder binder, String srcAddr,
+            String dstAddr);
+
     void stopKeepalive(in Network network, int slot);
 
     String getCaptivePortalServerUrl();
diff --git a/core/java/android/net/IpPrefix.java b/core/java/android/net/IpPrefix.java
index 4631c56..b996cda 100644
--- a/core/java/android/net/IpPrefix.java
+++ b/core/java/android/net/IpPrefix.java
@@ -16,6 +16,8 @@
 
 package android.net;
 
+import android.annotation.SystemApi;
+import android.annotation.TestApi;
 import android.os.Parcel;
 import android.os.Parcelable;
 import android.util.Pair;
@@ -83,6 +85,8 @@
      * @param prefixLength the prefix length. Must be &gt;= 0 and &lt;= (32 or 128) (IPv4 or IPv6).
      * @hide
      */
+    @SystemApi
+    @TestApi
     public IpPrefix(InetAddress address, int prefixLength) {
         // We don't reuse the (byte[], int) constructor because it calls clone() on the byte array,
         // which is unnecessary because getAddress() already returns a clone.
diff --git a/core/java/android/net/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/RouteInfo.java b/core/java/android/net/RouteInfo.java
index 6bf2c67..5c0f758 100644
--- a/core/java/android/net/RouteInfo.java
+++ b/core/java/android/net/RouteInfo.java
@@ -110,6 +110,8 @@
      *
      * @hide
      */
+    @SystemApi
+    @TestApi
     public RouteInfo(IpPrefix destination, InetAddress gateway, String iface, int type) {
         switch (type) {
             case RTN_UNICAST:
diff --git a/core/java/android/net/SocketKeepalive.java b/core/java/android/net/SocketKeepalive.java
new file mode 100644
index 0000000..97d50f4
--- /dev/null
+++ b/core/java/android/net/SocketKeepalive.java
@@ -0,0 +1,224 @@
+/*
+ * Copyright (C) 2019 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.net;
+
+import android.annotation.IntDef;
+import android.annotation.IntRange;
+import android.annotation.NonNull;
+import android.os.Handler;
+import android.os.HandlerThread;
+import android.os.Looper;
+import android.os.Message;
+import android.os.Messenger;
+import android.os.Process;
+import android.util.Log;
+
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+import java.util.concurrent.Executor;
+
+/**
+ * Allows applications to request that the system periodically send specific packets on their
+ * behalf, using hardware offload to save battery power.
+ *
+ * To request that the system send keepalives, call one of the methods that return a
+ * {@link SocketKeepalive} object, such as {@link ConnectivityManager#createSocketKeepalive},
+ * passing in a non-null callback. If the {@link SocketKeepalive} is successfully
+ * started, the callback's {@code onStarted} method will be called. If an error occurs,
+ * {@code onError} will be called, specifying one of the {@code ERROR_*} constants in this
+ * class.
+ *
+ * To stop an existing keepalive, call {@link SocketKeepalive#stop}. The system will call
+ * {@link SocketKeepalive.Callback#onStopped} if the operation was successful or
+ * {@link SocketKeepalive.Callback#onError} if an error occurred.
+ */
+public abstract class SocketKeepalive implements AutoCloseable {
+    static final String TAG = "SocketKeepalive";
+
+    /** @hide */
+    public static final int SUCCESS = 0;
+
+    /** @hide */
+    public static final int NO_KEEPALIVE = -1;
+
+    /** @hide */
+    public static final int DATA_RECEIVED = -2;
+
+    /** @hide */
+    public static final int BINDER_DIED = -10;
+
+    /** The specified {@code Network} is not connected. */
+    public static final int ERROR_INVALID_NETWORK = -20;
+    /** The specified IP addresses are invalid. For example, the specified source IP address is
+     * not configured on the specified {@code Network}. */
+    public static final int ERROR_INVALID_IP_ADDRESS = -21;
+    /** The requested port is invalid. */
+    public static final int ERROR_INVALID_PORT = -22;
+    /** The packet length is invalid (e.g., too long). */
+    public static final int ERROR_INVALID_LENGTH = -23;
+    /** The packet transmission interval is invalid (e.g., too short). */
+    public static final int ERROR_INVALID_INTERVAL = -24;
+    /** The target socket is invalid. */
+    public static final int ERROR_INVALID_SOCKET = -25;
+    /** The target socket is not idle. */
+    public static final int ERROR_SOCKET_NOT_IDLE = -26;
+
+    /** The hardware does not support this request. */
+    public static final int ERROR_HARDWARE_UNSUPPORTED = -30;
+    /** The hardware returned an error. */
+    public static final int ERROR_HARDWARE_ERROR = -31;
+
+    /** @hide */
+    @Retention(RetentionPolicy.SOURCE)
+    @IntDef(prefix = { "ERROR_" }, value = {
+            ERROR_INVALID_NETWORK,
+            ERROR_INVALID_IP_ADDRESS,
+            ERROR_INVALID_PORT,
+            ERROR_INVALID_LENGTH,
+            ERROR_INVALID_INTERVAL,
+            ERROR_INVALID_SOCKET,
+            ERROR_SOCKET_NOT_IDLE
+    })
+    public @interface ErrorCode {}
+
+    /**
+     * The minimum interval in seconds between keepalive packet transmissions.
+     *
+     * @hide
+     **/
+    public static final int MIN_INTERVAL_SEC = 10;
+
+    /**
+     * The maximum interval in seconds between keepalive packet transmissions.
+     *
+     * @hide
+     **/
+    public static final int MAX_INTERVAL_SEC = 3600;
+
+    @NonNull final IConnectivityManager mService;
+    @NonNull final Network mNetwork;
+    @NonNull private final Executor mExecutor;
+    @NonNull private final SocketKeepalive.Callback mCallback;
+    @NonNull private final Looper mLooper;
+    @NonNull final Messenger mMessenger;
+    @NonNull Integer mSlot;
+
+    SocketKeepalive(@NonNull IConnectivityManager service, @NonNull Network network,
+            @NonNull Executor executor, @NonNull Callback callback) {
+        mService = service;
+        mNetwork = network;
+        mExecutor = executor;
+        mCallback = callback;
+        // TODO: 1. Use other thread modeling instead of create one thread for every instance to
+        //          reduce the memory cost.
+        //       2. support restart.
+        //       3. Fix race condition which caused by rapidly start and stop.
+        HandlerThread thread = new HandlerThread(TAG, Process.THREAD_PRIORITY_BACKGROUND
+                + Process.THREAD_PRIORITY_LESS_FAVORABLE);
+        thread.start();
+        mLooper = thread.getLooper();
+        mMessenger = new Messenger(new Handler(mLooper) {
+            @Override
+            public void handleMessage(Message message) {
+                switch (message.what) {
+                    case NetworkAgent.EVENT_PACKET_KEEPALIVE:
+                        final int status = message.arg2;
+                        try {
+                            if (status == SUCCESS) {
+                                if (mSlot == null) {
+                                    mSlot = message.arg1;
+                                    mExecutor.execute(() -> mCallback.onStarted());
+                                } else {
+                                    mSlot = null;
+                                    stopLooper();
+                                    mExecutor.execute(() -> mCallback.onStopped());
+                                }
+                            } else if (status == DATA_RECEIVED) {
+                                stopLooper();
+                                mExecutor.execute(() -> mCallback.onDataReceived());
+                            } else {
+                                stopLooper();
+                                mExecutor.execute(() -> mCallback.onError(status));
+                            }
+                        } catch (Exception e) {
+                            Log.e(TAG, "Exception in keepalive callback(" + status + ")", e);
+                        }
+                        break;
+                    default:
+                        Log.e(TAG, "Unhandled message " + Integer.toHexString(message.what));
+                        break;
+                }
+            }
+        });
+    }
+
+    /**
+     * Request that keepalive be started with the given {@code intervalSec}. See
+     * {@link SocketKeepalive}.
+     *
+     * @param intervalSec The target interval in seconds between keepalive packet transmissions.
+     *                    The interval should be between 10 seconds and 3600 seconds, otherwise
+     *                    {@link #ERROR_INVALID_INTERVAL} will be returned.
+     */
+    public final void start(@IntRange(from = MIN_INTERVAL_SEC, to = MAX_INTERVAL_SEC)
+            int intervalSec) {
+        startImpl(intervalSec);
+    }
+
+    abstract void startImpl(int intervalSec);
+
+    /** @hide */
+    protected void stopLooper() {
+        // TODO: remove this after changing thread modeling.
+        mLooper.quit();
+    }
+
+    /**
+     * Requests that keepalive be stopped. The application must wait for {@link Callback#onStopped}
+     * before using the object. See {@link SocketKeepalive}.
+     */
+    public final void stop() {
+        stopImpl();
+    }
+
+    abstract void stopImpl();
+
+    /**
+     * Deactivate this {@link SocketKeepalive} and free allocated resources. The instance won't be
+     * usable again if {@code close()} is called.
+     */
+    @Override
+    public final void close() {
+        stop();
+        stopLooper();
+    }
+
+    /**
+     * The callback which app can use to learn the status changes of {@link SocketKeepalive}. See
+     * {@link SocketKeepalive}.
+     */
+    public static class Callback {
+        /** The requested keepalive was successfully started. */
+        public void onStarted() {}
+        /** The keepalive was successfully stopped. */
+        public void onStopped() {}
+        /** An error occurred. */
+        public void onError(@ErrorCode int error) {}
+        /** The keepalive on a TCP socket was stopped because the socket received data. */
+        public void onDataReceived() {}
+    }
+}
diff --git a/core/java/android/net/metrics/IpConnectivityLog.java b/core/java/android/net/metrics/IpConnectivityLog.java
index 16aea31b..5b5a235 100644
--- a/core/java/android/net/metrics/IpConnectivityLog.java
+++ b/core/java/android/net/metrics/IpConnectivityLog.java
@@ -18,7 +18,6 @@
 
 import android.annotation.SystemApi;
 import android.annotation.TestApi;
-import android.annotation.UnsupportedAppUsage;
 import android.net.ConnectivityMetricsEvent;
 import android.net.IIpConnectivityMetrics;
 import android.net.Network;
@@ -51,7 +50,8 @@
     public interface Event extends Parcelable {}
 
     /** @hide */
-    @UnsupportedAppUsage
+    @SystemApi
+    @TestApi
     public IpConnectivityLog() {
     }
 
diff --git a/core/java/android/net/metrics/RaEvent.java b/core/java/android/net/metrics/RaEvent.java
index d308246..04a2e6e 100644
--- a/core/java/android/net/metrics/RaEvent.java
+++ b/core/java/android/net/metrics/RaEvent.java
@@ -16,7 +16,8 @@
 
 package android.net.metrics;
 
-import android.annotation.UnsupportedAppUsage;
+import android.annotation.SystemApi;
+import android.annotation.TestApi;
 import android.os.Parcel;
 import android.os.Parcelable;
 
@@ -24,19 +25,28 @@
  * An event logged when the APF packet socket receives an RA packet.
  * {@hide}
  */
+@SystemApi
+@TestApi
 public final class RaEvent implements IpConnectivityLog.Event {
 
-    public static final long NO_LIFETIME = -1L;
+    private static final long NO_LIFETIME = -1L;
 
     // Lifetime in seconds of options found in a single RA packet.
     // When an option is not set, the value of the associated field is -1;
+    /** @hide */
     public final long routerLifetime;
+    /** @hide */
     public final long prefixValidLifetime;
+    /** @hide */
     public final long prefixPreferredLifetime;
+    /** @hide */
     public final long routeInfoLifetime;
+    /** @hide */
     public final long rdnssLifetime;
+    /** @hide */
     public final long dnsslLifetime;
 
+    /** @hide */
     public RaEvent(long routerLifetime, long prefixValidLifetime, long prefixPreferredLifetime,
             long routeInfoLifetime, long rdnssLifetime, long dnsslLifetime) {
         this.routerLifetime = routerLifetime;
@@ -47,6 +57,7 @@
         this.dnsslLifetime = dnsslLifetime;
     }
 
+    /** @hide */
     private RaEvent(Parcel in) {
         routerLifetime          = in.readLong();
         prefixValidLifetime     = in.readLong();
@@ -56,6 +67,7 @@
         dnsslLifetime           = in.readLong();
     }
 
+    /** @hide */
     @Override
     public void writeToParcel(Parcel out, int flags) {
         out.writeLong(routerLifetime);
@@ -66,6 +78,7 @@
         out.writeLong(dnsslLifetime);
     }
 
+    /** @hide */
     @Override
     public int describeContents() {
         return 0;
@@ -83,6 +96,7 @@
                 .toString();
     }
 
+    /** @hide */
     public static final Parcelable.Creator<RaEvent> CREATOR = new Parcelable.Creator<RaEvent>() {
         public RaEvent createFromParcel(Parcel in) {
             return new RaEvent(in);
@@ -102,47 +116,39 @@
         long rdnssLifetime           = NO_LIFETIME;
         long dnsslLifetime           = NO_LIFETIME;
 
-        @UnsupportedAppUsage
         public Builder() {
         }
 
-        @UnsupportedAppUsage
         public RaEvent build() {
             return new RaEvent(routerLifetime, prefixValidLifetime, prefixPreferredLifetime,
                     routeInfoLifetime, rdnssLifetime, dnsslLifetime);
         }
 
-        @UnsupportedAppUsage
         public Builder updateRouterLifetime(long lifetime) {
             routerLifetime = updateLifetime(routerLifetime, lifetime);
             return this;
         }
 
-        @UnsupportedAppUsage
         public Builder updatePrefixValidLifetime(long lifetime) {
             prefixValidLifetime = updateLifetime(prefixValidLifetime, lifetime);
             return this;
         }
 
-        @UnsupportedAppUsage
         public Builder updatePrefixPreferredLifetime(long lifetime) {
             prefixPreferredLifetime = updateLifetime(prefixPreferredLifetime, lifetime);
             return this;
         }
 
-        @UnsupportedAppUsage
         public Builder updateRouteInfoLifetime(long lifetime) {
             routeInfoLifetime = updateLifetime(routeInfoLifetime, lifetime);
             return this;
         }
 
-        @UnsupportedAppUsage
         public Builder updateRdnssLifetime(long lifetime) {
             rdnssLifetime = updateLifetime(rdnssLifetime, lifetime);
             return this;
         }
 
-        @UnsupportedAppUsage
         public Builder updateDnsslLifetime(long lifetime) {
             dnsslLifetime = updateLifetime(dnsslLifetime, lifetime);
             return this;
diff --git a/core/java/android/os/Process.java b/core/java/android/os/Process.java
index 07c4933..d2ab053 100644
--- a/core/java/android/os/Process.java
+++ b/core/java/android/os/Process.java
@@ -530,7 +530,7 @@
         return ZYGOTE_PROCESS.start(processClass, niceName, uid, gid, gids,
                     runtimeFlags, mountExternal, targetSdkVersion, seInfo,
                     abi, instructionSet, appDataDir, invokeWith, packageName,
-                    packagesForUid, visibleVols, zygoteArgs);
+                    packagesForUid, visibleVols, /*useBlastulaPool=*/ true, zygoteArgs);
     }
 
     /** @hide */
@@ -551,7 +551,7 @@
         return WebViewZygote.getProcess().start(processClass, niceName, uid, gid, gids,
                     runtimeFlags, mountExternal, targetSdkVersion, seInfo,
                     abi, instructionSet, appDataDir, invokeWith, packageName,
-                    packagesForUid, visibleVols, zygoteArgs);
+                    packagesForUid, visibleVols, /*useBlastulaPool=*/ false, zygoteArgs);
     }
 
     /**
diff --git a/core/java/android/os/ZygoteProcess.java b/core/java/android/os/ZygoteProcess.java
index 4cb9c5b..9e47179 100644
--- a/core/java/android/os/ZygoteProcess.java
+++ b/core/java/android/os/ZygoteProcess.java
@@ -76,6 +76,16 @@
     /**
      * @hide for internal use only
      */
+    public static final String BLASTULA_POOL_SOCKET_NAME = "blastula_pool";
+
+    /**
+     * @hide for internal use only
+     */
+    public static final String BLASTULA_POOL_SECONDARY_SOCKET_NAME = "blastula_pool_secondary";
+
+    /**
+     * @hide for internal use only
+     */
     private static final String LOG_TAG = "ZygoteProcess";
 
     /**
@@ -87,6 +97,15 @@
      * The name of the secondary (alternate ABI) zygote socket.
      */
     private final LocalSocketAddress mZygoteSecondarySocketAddress;
+    /**
+     * The name of the socket used to communicate with the primary blastula pool.
+     */
+    private final LocalSocketAddress mBlastulaPoolSocketAddress;
+
+    /**
+     * The name of the socket used to communicate with the secondary (alternate ABI) blastula pool.
+     */
+    private final LocalSocketAddress mBlastulaPoolSecondarySocketAddress;
 
     public ZygoteProcess() {
         mZygoteSocketAddress =
@@ -94,12 +113,22 @@
         mZygoteSecondarySocketAddress =
                 new LocalSocketAddress(ZYGOTE_SECONDARY_SOCKET_NAME,
                                        LocalSocketAddress.Namespace.RESERVED);
+
+        mBlastulaPoolSocketAddress =
+                new LocalSocketAddress(BLASTULA_POOL_SOCKET_NAME,
+                                       LocalSocketAddress.Namespace.RESERVED);
+        mBlastulaPoolSecondarySocketAddress =
+                new LocalSocketAddress(BLASTULA_POOL_SECONDARY_SOCKET_NAME,
+                                       LocalSocketAddress.Namespace.RESERVED);
     }
 
     public ZygoteProcess(LocalSocketAddress primarySocketAddress,
                          LocalSocketAddress secondarySocketAddress) {
         mZygoteSocketAddress = primarySocketAddress;
         mZygoteSecondarySocketAddress = secondarySocketAddress;
+
+        mBlastulaPoolSocketAddress = null;
+        mBlastulaPoolSecondarySocketAddress = null;
     }
 
     public LocalSocketAddress getPrimarySocketAddress() {
@@ -111,6 +140,7 @@
      */
     public static class ZygoteState {
         final LocalSocketAddress mZygoteSocketAddress;
+        final LocalSocketAddress mBlastulaSocketAddress;
 
         private final LocalSocket mZygoteSessionSocket;
 
@@ -122,11 +152,13 @@
         private boolean mClosed;
 
         private ZygoteState(LocalSocketAddress zygoteSocketAddress,
+                            LocalSocketAddress blastulaSocketAddress,
                             LocalSocket zygoteSessionSocket,
                             DataInputStream zygoteInputStream,
                             BufferedWriter zygoteOutputWriter,
                             List<String> abiList) {
             this.mZygoteSocketAddress = zygoteSocketAddress;
+            this.mBlastulaSocketAddress = blastulaSocketAddress;
             this.mZygoteSessionSocket = zygoteSessionSocket;
             this.mZygoteInputStream = zygoteInputStream;
             this.mZygoteOutputWriter = zygoteOutputWriter;
@@ -134,14 +166,17 @@
         }
 
         /**
-         * Create a new ZygoteState object by connecting to the given Zygote socket.
+         * Create a new ZygoteState object by connecting to the given Zygote socket and saving the
+         * given blastula socket address.
          *
          * @param zygoteSocketAddress  Zygote socket to connect to
+         * @param blastulaSocketAddress  Blastula socket address to save for later
          * @return  A new ZygoteState object containing a session socket for the given Zygote socket
          * address
          * @throws IOException
          */
-        public static ZygoteState connect(LocalSocketAddress zygoteSocketAddress)
+        public static ZygoteState connect(LocalSocketAddress zygoteSocketAddress,
+                                          LocalSocketAddress blastulaSocketAddress)
                 throws IOException {
 
             DataInputStream zygoteInputStream = null;
@@ -154,7 +189,7 @@
                 zygoteOutputWriter =
                         new BufferedWriter(
                                 new OutputStreamWriter(zygoteSessionSocket.getOutputStream()),
-                                256);
+                                Zygote.SOCKET_BUFFER_SIZE);
             } catch (IOException ex) {
                 try {
                     zygoteSessionSocket.close();
@@ -163,11 +198,18 @@
                 throw ex;
             }
 
-            return new ZygoteState(zygoteSocketAddress,
+            return new ZygoteState(zygoteSocketAddress, blastulaSocketAddress,
                                    zygoteSessionSocket, zygoteInputStream, zygoteOutputWriter,
                                    getAbiList(zygoteOutputWriter, zygoteInputStream));
         }
 
+        LocalSocket getBlastulaSessionSocket() throws IOException {
+            final LocalSocket blastulaSessionSocket = new LocalSocket();
+            blastulaSessionSocket.connect(this.mBlastulaSocketAddress);
+
+            return blastulaSessionSocket;
+        }
+
         boolean matches(String abi) {
             return mABIList.contains(abi);
         }
@@ -269,12 +311,13 @@
                                                   @Nullable String packageName,
                                                   @Nullable String[] packagesForUid,
                                                   @Nullable String[] visibleVols,
+                                                  boolean useBlastulaPool,
                                                   @Nullable String[] zygoteArgs) {
         try {
             return startViaZygote(processClass, niceName, uid, gid, gids,
                     runtimeFlags, mountExternal, targetSdkVersion, seInfo,
                     abi, instructionSet, appDataDir, invokeWith, /*startChildZygote=*/false,
-                    packageName, packagesForUid, visibleVols, zygoteArgs);
+                    packageName, packagesForUid, visibleVols, useBlastulaPool, zygoteArgs);
         } catch (ZygoteStartFailedEx ex) {
             Log.e(LOG_TAG,
                     "Starting VM process through Zygote failed");
@@ -322,59 +365,128 @@
      */
     @GuardedBy("mLock")
     private static Process.ProcessStartResult zygoteSendArgsAndGetResult(
-            ZygoteState zygoteState, ArrayList<String> args)
+            ZygoteState zygoteState, boolean useBlastulaPool, ArrayList<String> args)
             throws ZygoteStartFailedEx {
-        try {
-            // Throw early if any of the arguments are malformed. This means we can
-            // avoid writing a partial response to the zygote.
-            int sz = args.size();
-            for (int i = 0; i < sz; i++) {
-                if (args.get(i).indexOf('\n') >= 0) {
-                    throw new ZygoteStartFailedEx("embedded newlines not allowed");
+        // Throw early if any of the arguments are malformed. This means we can
+        // avoid writing a partial response to the zygote.
+        for (String arg : args) {
+            if (arg.indexOf('\n') >= 0) {
+                throw new ZygoteStartFailedEx("embedded newlines not allowed");
+            }
+        }
+
+        /**
+         * See com.android.internal.os.SystemZygoteInit.readArgumentList()
+         * Presently the wire format to the zygote process is:
+         * a) a count of arguments (argc, in essence)
+         * b) a number of newline-separated argument strings equal to count
+         *
+         * After the zygote process reads these it will write the pid of
+         * the child or -1 on failure, followed by boolean to
+         * indicate whether a wrapper process was used.
+         */
+        String msgStr = Integer.toString(args.size()) + "\n"
+                        + String.join("\n", args) + "\n";
+
+        // Should there be a timeout on this?
+        Process.ProcessStartResult result = new Process.ProcessStartResult();
+
+        // TODO (chriswailes): Move branch body into separate function.
+        if (useBlastulaPool && Zygote.BLASTULA_POOL_ENABLED && isValidBlastulaCommand(args)) {
+            LocalSocket blastulaSessionSocket = null;
+
+            try {
+                blastulaSessionSocket = zygoteState.getBlastulaSessionSocket();
+
+                final BufferedWriter blastulaWriter =
+                        new BufferedWriter(
+                                new OutputStreamWriter(blastulaSessionSocket.getOutputStream()),
+                                Zygote.SOCKET_BUFFER_SIZE);
+                final DataInputStream blastulaReader =
+                        new DataInputStream(blastulaSessionSocket.getInputStream());
+
+                blastulaWriter.write(msgStr);
+                blastulaWriter.flush();
+
+                result.pid = blastulaReader.readInt();
+                // Blastulas can't be used to spawn processes that need wrappers.
+                result.usingWrapper = false;
+
+                if (result.pid < 0) {
+                    throw new ZygoteStartFailedEx("Blastula specialization failed");
+                }
+
+                return result;
+            } catch (IOException ex) {
+                // If there was an IOException using the blastula pool we will log the error and
+                // attempt to start the process through the Zygote.
+                Log.e(LOG_TAG, "IO Exception while communicating with blastula pool - "
+                               + ex.toString());
+            } finally {
+                try {
+                    blastulaSessionSocket.close();
+                } catch (IOException ex) {
+                    Log.e(LOG_TAG, "Failed to close blastula session socket: " + ex.getMessage());
                 }
             }
+        }
 
-            /**
-             * See com.android.internal.os.SystemZygoteInit.readArgumentList()
-             * Presently the wire format to the zygote process is:
-             * a) a count of arguments (argc, in essence)
-             * b) a number of newline-separated argument strings equal to count
-             *
-             * After the zygote process reads these it will write the pid of
-             * the child or -1 on failure, followed by boolean to
-             * indicate whether a wrapper process was used.
-             */
-            final BufferedWriter writer = zygoteState.mZygoteOutputWriter;
-            final DataInputStream inputStream = zygoteState.mZygoteInputStream;
+        try {
+            final BufferedWriter zygoteWriter = zygoteState.mZygoteOutputWriter;
+            final DataInputStream zygoteInputStream = zygoteState.mZygoteInputStream;
 
-            writer.write(Integer.toString(args.size()));
-            writer.newLine();
-
-            for (int i = 0; i < sz; i++) {
-                String arg = args.get(i);
-                writer.write(arg);
-                writer.newLine();
-            }
-
-            writer.flush();
-
-            // Should there be a timeout on this?
-            Process.ProcessStartResult result = new Process.ProcessStartResult();
+            zygoteWriter.write(msgStr);
+            zygoteWriter.flush();
 
             // Always read the entire result from the input stream to avoid leaving
             // bytes in the stream for future process starts to accidentally stumble
             // upon.
-            result.pid = inputStream.readInt();
-            result.usingWrapper = inputStream.readBoolean();
-
-            if (result.pid < 0) {
-                throw new ZygoteStartFailedEx("fork() failed");
-            }
-            return result;
+            result.pid = zygoteInputStream.readInt();
+            result.usingWrapper = zygoteInputStream.readBoolean();
         } catch (IOException ex) {
             zygoteState.close();
+            Log.e(LOG_TAG, "IO Exception while communicating with Zygote - "
+                    + ex.toString());
             throw new ZygoteStartFailedEx(ex);
         }
+
+        if (result.pid < 0) {
+            throw new ZygoteStartFailedEx("fork() failed");
+        }
+
+        return result;
+    }
+
+    /**
+     * Flags that may not be passed to a blastula.
+     */
+    private static final String[] INVALID_BLASTULA_FLAGS = {
+        "--query-abi-list",
+        "--get-pid",
+        "--preload-default",
+        "--preload-package",
+        "--preload-app",
+        "--start-child-zygote",
+        "--set-api-blacklist-exemptions",
+        "--hidden-api-log-sampling-rate",
+        "--invoke-with"
+    };
+
+    /**
+     * Tests a command list to see if it is valid to send to a blastula.
+     * @param args  Zygote/Blastula command arguments
+     * @return  True if the command can be passed to a blastula; false otherwise
+     */
+    private static boolean isValidBlastulaCommand(ArrayList<String> args) {
+        for (String flag : args) {
+            for (String badFlag : INVALID_BLASTULA_FLAGS) {
+                if (flag.startsWith(badFlag)) {
+                    return false;
+                }
+            }
+        }
+
+        return true;
     }
 
     /**
@@ -416,6 +528,7 @@
                                                       @Nullable String packageName,
                                                       @Nullable String[] packagesForUid,
                                                       @Nullable String[] visibleVols,
+                                                      boolean useBlastulaPool,
                                                       @Nullable String[] extraArgs)
                                                       throws ZygoteStartFailedEx {
         ArrayList<String> argsForZygote = new ArrayList<String>();
@@ -522,7 +635,9 @@
         }
 
         synchronized(mLock) {
-            return zygoteSendArgsAndGetResult(openZygoteSocketIfNeeded(abi), argsForZygote);
+            return zygoteSendArgsAndGetResult(openZygoteSocketIfNeeded(abi),
+                                              useBlastulaPool,
+                                              argsForZygote);
         }
     }
 
@@ -686,7 +801,7 @@
         if (primaryZygoteState == null || primaryZygoteState.isClosed()) {
             try {
                 primaryZygoteState =
-                    ZygoteState.connect(mZygoteSocketAddress);
+                    ZygoteState.connect(mZygoteSocketAddress, mBlastulaPoolSocketAddress);
             } catch (IOException ioe) {
                 throw new ZygoteStartFailedEx("Error connecting to primary zygote", ioe);
             }
@@ -703,7 +818,8 @@
         if (secondaryZygoteState == null || secondaryZygoteState.isClosed()) {
             try {
                 secondaryZygoteState =
-                    ZygoteState.connect(mZygoteSecondarySocketAddress);
+                    ZygoteState.connect(mZygoteSecondarySocketAddress,
+                                        mBlastulaPoolSecondarySocketAddress);
             } catch (IOException ioe) {
                 throw new ZygoteStartFailedEx("Error connecting to secondary zygote", ioe);
             }
@@ -820,7 +936,7 @@
         for (int n = 20; n >= 0; n--) {
             try {
                 final ZygoteState zs =
-                        ZygoteState.connect(zygoteSocketAddress);
+                        ZygoteState.connect(zygoteSocketAddress, null);
                 zs.close();
                 return;
             } catch (IOException ioe) {
@@ -884,7 +1000,8 @@
                     gids, runtimeFlags, 0 /* mountExternal */, 0 /* targetSdkVersion */, seInfo,
                     abi, instructionSet, null /* appDataDir */, null /* invokeWith */,
                     true /* startChildZygote */, null /* packageName */,
-                    null /* packagesForUid */, null /* visibleVolumes */, extraArgs);
+                    null /* packagesForUid */, null /* visibleVolumes */,
+                    false /* useBlastulaPool */, extraArgs);
         } catch (ZygoteStartFailedEx ex) {
             throw new RuntimeException("Starting child-zygote through Zygote failed", ex);
         }
diff --git a/core/java/android/provider/DeviceConfig.java b/core/java/android/provider/DeviceConfig.java
index 87efbf3..6ccd296 100644
--- a/core/java/android/provider/DeviceConfig.java
+++ b/core/java/android/provider/DeviceConfig.java
@@ -54,6 +54,7 @@
 
     /**
      * Namespace for all Game Driver features.
+     *
      * @hide
      */
     @SystemApi
@@ -104,6 +105,24 @@
     public static final String NAMESPACE_NOTIFICATION_ASSISTANT = "notification_assistant";
 
     /**
+     * Namespace for attention-based features provided by on-device machine intelligence.
+     *
+     * @hide
+     */
+    @SystemApi
+    public interface IntelligenceAttention {
+        String NAMESPACE = "intelligence_attention";
+        /**
+         * If {@code true}, enables the attention check.
+         */
+        String PROPERTY_ATTENTION_CHECK_ENABLED = "attention_check_enabled";
+        /**
+         * Settings for performing the attention check.
+         */
+        String PROPERTY_ATTENTION_CHECK_SETTINGS = "attention_check_settings";
+    }
+
+    /**
      * Telephony related properties definitions.
      *
      * @hide
@@ -121,6 +140,19 @@
         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";
+    }
+
     private static final Object sLock = new Object();
     @GuardedBy("sLock")
     private static Map<OnPropertyChangedListener, Pair<String, Executor>> sListeners =
@@ -136,9 +168,8 @@
      * Look up the value of a property for a particular namespace.
      *
      * @param namespace The namespace containing the property to look up.
-     * @param name The name of the property to look up.
+     * @param name      The name of the property to look up.
      * @return the corresponding value, or null if not present.
-     *
      * @hide
      */
     @SystemApi
@@ -160,14 +191,13 @@
      * All properties stored for a particular scope can be reverted to their default values
      * by passing the namespace to {@link #resetToDefaults(int, String)}.
      *
-     * @param namespace The namespace containing the property to create or update.
-     * @param name The name of the property to create or update.
-     * @param value The value to store for the property.
+     * @param namespace   The namespace containing the property to create or update.
+     * @param name        The name of the property to create or update.
+     * @param value       The value to store for the property.
      * @param makeDefault Whether to make the new value the default one.
      * @return True if the value was set, false if the storage implementation throws errors.
-     * @see #resetToDefaults(int, String).
-     *
      * @hide
+     * @see #resetToDefaults(int, String).
      */
     @SystemApi
     @RequiresPermission(WRITE_DEVICE_CONFIG)
@@ -186,9 +216,8 @@
      *
      * @param resetMode The reset mode to use.
      * @param namespace Optionally, the specific namespace which resets will be limited to.
-     * @see #setProperty(String, String, String, boolean)
-     *
      * @hide
+     * @see #setProperty(String, String, String, boolean)
      */
     @SystemApi
     @RequiresPermission(WRITE_DEVICE_CONFIG)
@@ -205,12 +234,11 @@
      * will replace the old namespace and executor. Remove the listener entirely by calling
      * {@link #removeOnPropertyChangedListener(OnPropertyChangedListener)}.
      *
-     * @param namespace The namespace containing properties to monitor.
-     * @param executor The executor which will be used to run callbacks.
+     * @param namespace                 The namespace containing properties to monitor.
+     * @param executor                  The executor which will be used to run callbacks.
      * @param onPropertyChangedListener The listener to add.
-     * @see #removeOnPropertyChangedListener(OnPropertyChangedListener)
-     *
      * @hide
+     * @see #removeOnPropertyChangedListener(OnPropertyChangedListener)
      */
     @SystemApi
     @RequiresPermission(READ_DEVICE_CONFIG)
@@ -242,9 +270,8 @@
      * property changes.
      *
      * @param onPropertyChangedListener The listener to remove.
-     * @see #addOnPropertyChangedListener(String, Executor, OnPropertyChangedListener)
-     *
      * @hide
+     * @see #addOnPropertyChangedListener(String, Executor, OnPropertyChangedListener)
      */
     @SystemApi
     public static void removeOnPropertyChangedListener(
@@ -345,8 +372,8 @@
          * Called when a property has changed.
          *
          * @param namespace The namespace containing the property which has changed.
-         * @param name The name of the property which has changed.
-         * @param value The new value of the property which has changed.
+         * @param name      The name of the property which has changed.
+         * @param value     The new value of the property which has changed.
          */
         void onPropertyChanged(String namespace, String name, String value);
     }
diff --git a/core/java/android/provider/MediaStore.java b/core/java/android/provider/MediaStore.java
index 487198b..f5c442f 100644
--- a/core/java/android/provider/MediaStore.java
+++ b/core/java/android/provider/MediaStore.java
@@ -89,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";
 
     /**
@@ -1566,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);
@@ -2743,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
diff --git a/core/java/android/provider/Settings.java b/core/java/android/provider/Settings.java
index fa3f54a..95cfb6e 100644
--- a/core/java/android/provider/Settings.java
+++ b/core/java/android/provider/Settings.java
@@ -8421,6 +8421,20 @@
         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(",");
+
         /**
          * This are the settings to be backed up.
          *
@@ -8544,6 +8558,7 @@
             LOCK_SCREEN_WHEN_TRUST_LOST,
             SKIP_GESTURE,
             SILENCE_GESTURE,
+            THEME_CUSTOMIZATION_OVERLAY_PACKAGES,
         };
 
         /**
@@ -8714,6 +8729,8 @@
             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);
         }
 
         /**
@@ -9452,23 +9469,6 @@
                 "hdmi_control_auto_device_off_enabled";
 
         /**
-         * If <b>true</b>, enables out-of-the-box execution for priv apps.
-         * Default: false
-         * Values: 0 = false, 1 = true
-         *
-         * @hide
-         */
-        public static final String PRIV_APP_OOB_ENABLED = "priv_app_oob_enabled";
-
-        /**
-         * Comma separated list of privileged package names, which will be running out-of-box APK.
-         * Default: "ALL"
-         *
-         * @hide
-         */
-        public static final String PRIV_APP_OOB_LIST = "priv_app_oob_list";
-
-        /**
          * The interval in milliseconds at which location requests will be throttled when they are
          * coming from the background.
          *
@@ -9493,6 +9493,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
          */
@@ -12150,6 +12158,14 @@
         public static final String GUP_BLACKLIST = "gup_blacklist";
 
         /**
+         * Apps on the whitelist that are allowed to use Game Driver.
+         * The string is a list of application package names, seperated by comma.
+         * i.e. <apk1>,<apk2>,...,<apkN>
+         * @hide
+         */
+        public static final String GAME_DRIVER_WHITELIST = "game_driver_whitelist";
+
+        /**
          * Ordered GPU debug layer list for Vulkan
          * i.e. <layer1>:<layer2>:...:<layerN>
          * @hide
@@ -12194,6 +12210,31 @@
         public static final String LOW_POWER_MODE_STICKY = "low_power_sticky";
 
         /**
+         * When a device is unplugged from a changer (or is rebooted), do not re-activate battery
+         * saver even if {@link #LOW_POWER_MODE_STICKY} is 1, if the battery level is equal to or
+         * above this threshold.
+         *
+         * @hide
+         */
+        public static final String LOW_POWER_MODE_STICKY_AUTO_DISABLE_LEVEL =
+                "low_power_sticky_auto_disable_level";
+
+        private static final Validator LOW_POWER_MODE_STICKY_AUTO_DISABLE_LEVEL_VALIDATOR =
+                new SettingsValidators.InclusiveIntegerRangeValidator(0, 100);
+
+        /**
+         * Whether sticky battery saver should be deactivated once the battery level has reached the
+         * threshold specified by {@link #LOW_POWER_MODE_STICKY_AUTO_DISABLE_LEVEL}.
+         *
+         * @hide
+         */
+        public static final String LOW_POWER_MODE_STICKY_AUTO_DISABLE_ENABLED =
+                "low_power_sticky_auto_disable_enabled";
+
+        private static final Validator LOW_POWER_MODE_STICKY_AUTO_DISABLE_ENABLED_VALIDATOR =
+                new SettingsValidators.DiscreteValueValidator(new String[] {"0", "1"});
+
+        /**
          * Battery level [1-100] at which low power mode automatically turns on.
          * Pre-Q If 0, it will not automatically turn on. Q and newer it will only automatically
          * turn on if the {@link #AUTOMATIC_POWER_SAVER_MODE} setting is also set to
@@ -12205,7 +12246,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);
 
@@ -13196,6 +13236,8 @@
             ENCODED_SURROUND_OUTPUT,
             ENCODED_SURROUND_OUTPUT_ENABLED_FORMATS,
             LOW_POWER_MODE_TRIGGER_LEVEL,
+            LOW_POWER_MODE_STICKY_AUTO_DISABLE_ENABLED,
+            LOW_POWER_MODE_STICKY_AUTO_DISABLE_LEVEL,
             BLUETOOTH_ON,
             PRIVATE_DNS_MODE,
             PRIVATE_DNS_SPECIFIER,
@@ -13234,6 +13276,10 @@
             VALIDATORS.put(ENCODED_SURROUND_OUTPUT, ENCODED_SURROUND_OUTPUT_VALIDATOR);
             VALIDATORS.put(ENCODED_SURROUND_OUTPUT_ENABLED_FORMATS,
                     ENCODED_SURROUND_OUTPUT_ENABLED_FORMATS_VALIDATOR);
+            VALIDATORS.put(LOW_POWER_MODE_STICKY_AUTO_DISABLE_LEVEL,
+                    LOW_POWER_MODE_STICKY_AUTO_DISABLE_LEVEL_VALIDATOR);
+            VALIDATORS.put(LOW_POWER_MODE_STICKY_AUTO_DISABLE_ENABLED,
+                    LOW_POWER_MODE_STICKY_AUTO_DISABLE_ENABLED_VALIDATOR);
             VALIDATORS.put(LOW_POWER_MODE_TRIGGER_LEVEL, LOW_POWER_MODE_TRIGGER_LEVEL_VALIDATOR);
             VALIDATORS.put(LOW_POWER_MODE_TRIGGER_LEVEL_MAX,
                     LOW_POWER_MODE_TRIGGER_LEVEL_VALIDATOR);
@@ -13748,6 +13794,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
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/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/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/View.java b/core/java/android/view/View.java
index 2014ec2..483280e 100644
--- a/core/java/android/view/View.java
+++ b/core/java/android/view/View.java
@@ -8203,10 +8203,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>
@@ -8608,7 +8608,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) {
@@ -13994,7 +13994,7 @@
                     if (clickable) {
                         setPressed(true, x, y);
                     }
-                    checkForLongClick(0, x, y);
+                    checkForLongClick(ViewConfiguration.getLongPressTimeout(), x, y);
                     return true;
                 }
             }
@@ -14735,7 +14735,7 @@
                     mHasPerformedLongPress = false;
 
                     if (!clickable) {
-                        checkForLongClick(0, x, y);
+                        checkForLongClick(ViewConfiguration.getLongPressTimeout(), x, y);
                         break;
                     }
 
@@ -14759,7 +14759,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;
 
@@ -25434,7 +25434,7 @@
         }
     }
 
-    private void checkForLongClick(int delayOffset, float x, float y) {
+    private void checkForLongClick(long delay, float x, float y) {
         if ((mViewFlags & LONG_CLICKABLE) == LONG_CLICKABLE || (mViewFlags & TOOLTIP) == TOOLTIP) {
             mHasPerformedLongPress = false;
 
@@ -25444,8 +25444,7 @@
             mPendingCheckForLongPress.setAnchor(x, y);
             mPendingCheckForLongPress.rememberWindowAttachCount();
             mPendingCheckForLongPress.rememberPressedState();
-            postDelayed(mPendingCheckForLongPress,
-                    ViewConfiguration.getLongPressTimeout() - delayOffset);
+            postDelayed(mPendingCheckForLongPress, delay);
         }
     }
 
@@ -27035,7 +27034,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/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..6ed2d80 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;
@@ -246,7 +246,7 @@
     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;
@@ -352,14 +352,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 +405,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 +423,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/core/java/android/view/contentcapture/UserDataRemovalRequest.aidl b/core/java/android/view/contentcapture/UserDataRemovalRequest.aidl
new file mode 100644
index 0000000..fbe47e0
--- /dev/null
+++ b/core/java/android/view/contentcapture/UserDataRemovalRequest.aidl
@@ -0,0 +1,19 @@
+/*
+ * Copyright (C) 2019 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.view.contentcapture;
+
+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/InputMethodManager.java b/core/java/android/view/inputmethod/InputMethodManager.java
index 15088d2..0cb1800 100644
--- a/core/java/android/view/inputmethod/InputMethodManager.java
+++ b/core/java/android/view/inputmethod/InputMethodManager.java
@@ -2490,7 +2490,14 @@
      * @param subtype A new input method subtype to switch.
      * @return true if the current subtype was successfully switched. When the specified subtype is
      * null, this method returns false.
+     * @deprecated If the calling process is an IME, use
+     *             {@link InputMethodService#switchInputMethod(String, InputMethodSubtype)}, which
+     *             does not require any permission as long as the caller is the current IME.
+     *             If the calling process is some privileged app that already has
+     *             {@link android.Manifest.permission#WRITE_SECURE_SETTINGS} permission, just
+     *             directly update {@link Settings.Secure#SELECTED_INPUT_METHOD_SUBTYPE}.
      */
+    @Deprecated
     @RequiresPermission(WRITE_SECURE_SETTINGS)
     public boolean setCurrentInputMethodSubtype(InputMethodSubtype subtype) {
         if (Process.myUid() == Process.SYSTEM_UID) {
@@ -2675,7 +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/com/android/internal/app/ChooserActivity.java b/core/java/com/android/internal/app/ChooserActivity.java
index b4d8322..30137e38 100644
--- a/core/java/com/android/internal/app/ChooserActivity.java
+++ b/core/java/com/android/internal/app/ChooserActivity.java
@@ -103,7 +103,9 @@
      * 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 +138,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 +185,9 @@
                         mChooserListAdapter.addServiceResults(resultInfo.originalTarget,
                                 resultInfo.resultTargets);
                     }
+                    break;
+
+                case SHORTCUT_MANAGER_SHARE_TARGET_RESULT_COMPLETED:
                     sendVoiceChoicesIfNeeded();
                     mChooserListAdapter.setShowServiceTargets(true);
                     break;
@@ -630,6 +636,7 @@
             // 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++) {
@@ -646,6 +653,13 @@
                 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);
             }
         });
     }
@@ -1178,13 +1192,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/os/Zygote.java b/core/java/com/android/internal/os/Zygote.java
index f609f2f..069413f 100644
--- a/core/java/com/android/internal/os/Zygote.java
+++ b/core/java/com/android/internal/os/Zygote.java
@@ -117,7 +117,13 @@
     /** Number of bytes sent to the Zygote over blastula pipes or the pool event FD */
     public static final int BLASTULA_MANAGEMENT_MESSAGE_BYTES = 8;
 
-    /** If the blastula pool should be created and used to start applications */
+    /**
+     * If the blastula pool should be created and used to start applications.
+     *
+     * Setting this value to false will disable the creation, maintenance, and use of the blastula
+     * pool.  When the blastula pool is disabled the application lifecycle will be identical to
+     * previous versions of Android.
+     */
     public static final boolean BLASTULA_POOL_ENABLED = false;
 
     /**
@@ -187,6 +193,11 @@
     // TODO (chriswailes): This must be updated at the same time as sBlastulaPoolMax.
     static int sBlastulaPoolRefillThreshold = (sBlastulaPoolMax / 2);
 
+    /**
+     * @hide for internal use only
+     */
+    public static final int SOCKET_BUFFER_SIZE = 256;
+
     private static LocalServerSocket sBlastulaPoolSocket = null;
 
     /** a prototype instance for a future List.toArray() */
@@ -234,14 +245,14 @@
     public static int forkAndSpecialize(int uid, int gid, int[] gids, int runtimeFlags,
             int[][] rlimits, int mountExternal, String seInfo, String niceName, int[] fdsToClose,
             int[] fdsToIgnore, boolean startChildZygote, String instructionSet, String appDataDir,
-            String packageName, String[] packagesForUid, String[] visibleVolIds) {
+            String packageName, String[] packagesForUID, String[] visibleVolIDs) {
         VM_HOOKS.preFork();
         // Resets nice priority for zygote process.
         resetNicePriority();
         int pid = nativeForkAndSpecialize(
                 uid, gid, gids, runtimeFlags, rlimits, mountExternal, seInfo, niceName, fdsToClose,
                 fdsToIgnore, startChildZygote, instructionSet, appDataDir, packageName,
-                packagesForUid, visibleVolIds);
+                packagesForUID, visibleVolIDs);
         // Enable tracing as soon as possible for the child process.
         if (pid == 0) {
             Trace.setTracingEnabled(true, runtimeFlags);
@@ -256,7 +267,7 @@
     private static native int nativeForkAndSpecialize(int uid, int gid, int[] gids,
             int runtimeFlags, int[][] rlimits, int mountExternal, String seInfo, String niceName,
             int[] fdsToClose, int[] fdsToIgnore, boolean startChildZygote, String instructionSet,
-            String appDataDir, String packageName, String[] packagesForUid, String[] visibleVolIds);
+            String appDataDir, String packageName, String[] packagesForUID, String[] visibleVolIDs);
 
     /**
      * Specialize a Blastula instance.  The current VM must have been started
@@ -281,12 +292,12 @@
      */
     public static void specializeBlastula(int uid, int gid, int[] gids, int runtimeFlags,
             int[][] rlimits, int mountExternal, String seInfo, String niceName,
-            boolean startChildZygote, String instructionSet, String appDataDir,
-            String packageName, String[] packagesForUid, String[] visibleVolIds) {
+            boolean startChildZygote, String instructionSet, String appDataDir, String packageName,
+            String[] packagesForUID, String[] visibleVolIDs) {
 
         nativeSpecializeBlastula(uid, gid, gids, runtimeFlags, rlimits, mountExternal, seInfo,
                                  niceName, startChildZygote, instructionSet, appDataDir,
-                                 packageName, packagesForUid, visibleVolIds);
+                                 packageName, packagesForUID, visibleVolIDs);
 
         // Enable tracing as soon as possible for the child process.
         Trace.setTracingEnabled(true, runtimeFlags);
@@ -306,7 +317,7 @@
     private static native void nativeSpecializeBlastula(int uid, int gid, int[] gids,
             int runtimeFlags, int[][] rlimits, int mountExternal, String seInfo, String niceName,
             boolean startChildZygote, String instructionSet, String appDataDir, String packageName,
-            String[] packagesForUid, String[] visibleVolIds);
+            String[] packagesForUID, String[] visibleVolIDs);
 
     /**
      * Called to do any initialization before starting an application.
diff --git a/core/java/com/android/internal/os/ZygoteConnection.java b/core/java/com/android/internal/os/ZygoteConnection.java
index 78ecee1..ffbe8eb 100644
--- a/core/java/com/android/internal/os/ZygoteConnection.java
+++ b/core/java/com/android/internal/os/ZygoteConnection.java
@@ -242,7 +242,7 @@
             fdsToClose[0] = fd.getInt$();
         }
 
-        fd = zygoteServer.getServerSocketFileDescriptor();
+        fd = zygoteServer.getZygoteSocketFileDescriptor();
 
         if (fd != null) {
             fdsToClose[1] = fd.getInt$();
diff --git a/core/java/com/android/internal/os/ZygoteInit.java b/core/java/com/android/internal/os/ZygoteInit.java
index 2f00c07..e3e55ed 100644
--- a/core/java/com/android/internal/os/ZygoteInit.java
+++ b/core/java/com/android/internal/os/ZygoteInit.java
@@ -269,7 +269,7 @@
 
         try {
             BufferedReader br =
-                    new BufferedReader(new InputStreamReader(is), 256);
+                    new BufferedReader(new InputStreamReader(is), Zygote.SOCKET_BUFFER_SIZE);
 
             int count = 0;
             String line;
@@ -750,7 +750,7 @@
             throw new RuntimeException("Failed to setpgid(0,0)", ex);
         }
 
-        final Runnable caller;
+        Runnable caller;
         try {
             // Report Zygote start time to tron unless it is a runtime restart
             if (!"1".equals(SystemProperties.get("sys.boot_completed"))) {
@@ -786,7 +786,17 @@
                 throw new RuntimeException("No ABI list supplied.");
             }
 
-            zygoteServer.registerServerSocketFromEnv(socketName);
+            // TODO (chriswailes): Wrap these three calls in a helper function?
+            final String blastulaSocketName =
+                    socketName.equals(ZygoteProcess.ZYGOTE_SOCKET_NAME)
+                            ? ZygoteProcess.BLASTULA_POOL_SOCKET_NAME
+                            : ZygoteProcess.BLASTULA_POOL_SECONDARY_SOCKET_NAME;
+
+            zygoteServer.createZygoteSocket(socketName);
+            Zygote.createBlastulaSocket(blastulaSocketName);
+
+            Zygote.getSocketFDs(socketName.equals(ZygoteProcess.ZYGOTE_SOCKET_NAME));
+
             // In some configurations, we avoid preloading resources and classes eagerly.
             // In such cases, we will preload things prior to our first fork.
             if (!enableLazyPreload) {
@@ -829,11 +839,18 @@
                 }
             }
 
-            Log.i(TAG, "Accepting command socket connections");
+            // If the return value is null then this is the zygote process
+            // returning to the normal control flow.  If it returns a Runnable
+            // object then this is a blastula that has finished specializing.
+            caller = Zygote.initBlastulaPool();
 
-            // The select loop returns early in the child process after a fork and
-            // loops forever in the zygote.
-            caller = zygoteServer.runSelectLoop(abiList);
+            if (caller == null) {
+                Log.i(TAG, "Accepting command socket connections");
+
+                // The select loop returns early in the child process after a fork and
+                // loops forever in the zygote.
+                caller = zygoteServer.runSelectLoop(abiList);
+            }
         } catch (Throwable ex) {
             Log.e(TAG, "System zygote died with exception", ex);
             throw ex;
diff --git a/core/java/com/android/internal/os/ZygoteServer.java b/core/java/com/android/internal/os/ZygoteServer.java
index c1bfde1..a78c095 100644
--- a/core/java/com/android/internal/os/ZygoteServer.java
+++ b/core/java/com/android/internal/os/ZygoteServer.java
@@ -26,6 +26,8 @@
 import android.util.Log;
 import android.util.Slog;
 
+import java.io.ByteArrayInputStream;
+import java.io.DataInputStream;
 import java.io.FileDescriptor;
 import java.io.IOException;
 import java.util.ArrayList;
@@ -40,18 +42,17 @@
  * client protocol.
  */
 class ZygoteServer {
+    // TODO (chriswailes): Change this so it is set with Zygote or ZygoteSecondary as appropriate
     public static final String TAG = "ZygoteServer";
 
-    private static final String ANDROID_SOCKET_PREFIX = "ANDROID_SOCKET_";
-
     /**
      * Listening socket that accepts new server connections.
      */
-    private LocalServerSocket mServerSocket;
+    private LocalServerSocket mZygoteSocket;
 
     /**
-     * Whether or not mServerSocket's underlying FD should be closed directly.
-     * If mServerSocket is created with an existing FD, closing the socket does
+     * Whether or not mZygoteSocket's underlying FD should be closed directly.
+     * If mZygoteSocket is created with an existing FD, closing the socket does
      * not close the FD and it must be closed explicitly. If the socket is created
      * with a name instead, then closing the socket will close the underlying FD
      * and it should not be double-closed.
@@ -70,31 +71,17 @@
     }
 
     /**
-     * Registers a server socket for zygote command connections. This locates the server socket
-     * file descriptor through an ANDROID_SOCKET_ environment variable.
+     * Creates a managed object representing the Zygote socket that has already
+     * been initialized and bound by init.
+     *
+     * TODO (chriswailes): Move the name selection logic into this function.
      *
      * @throws RuntimeException when open fails
      */
-    void registerServerSocketFromEnv(String socketName) {
-        if (mServerSocket == null) {
-            int fileDesc;
-            final String fullSocketName = ANDROID_SOCKET_PREFIX + socketName;
-            try {
-                String env = System.getenv(fullSocketName);
-                fileDesc = Integer.parseInt(env);
-            } catch (RuntimeException ex) {
-                throw new RuntimeException(fullSocketName + " unset or invalid", ex);
-            }
-
-            try {
-                FileDescriptor fd = new FileDescriptor();
-                fd.setInt$(fileDesc);
-                mServerSocket = new LocalServerSocket(fd);
-                mCloseSocketFd = true;
-            } catch (IOException ex) {
-                throw new RuntimeException(
-                        "Error binding to local socket '" + fileDesc + "'", ex);
-            }
+    void createZygoteSocket(String socketName) {
+        if (mZygoteSocket == null) {
+            mZygoteSocket = Zygote.createManagedSocketFromInitSocket(socketName);
+            mCloseSocketFd = true;
         }
     }
 
@@ -103,9 +90,9 @@
      * at the specified name in the abstract socket namespace.
      */
     void registerServerSocketAtAbstractName(String socketName) {
-        if (mServerSocket == null) {
+        if (mZygoteSocket == null) {
             try {
-                mServerSocket = new LocalServerSocket(socketName);
+                mZygoteSocket = new LocalServerSocket(socketName);
                 mCloseSocketFd = false;
             } catch (IOException ex) {
                 throw new RuntimeException(
@@ -120,7 +107,7 @@
      */
     private ZygoteConnection acceptCommandPeer(String abiList) {
         try {
-            return createNewConnection(mServerSocket.accept(), abiList);
+            return createNewConnection(mZygoteSocket.accept(), abiList);
         } catch (IOException ex) {
             throw new RuntimeException(
                     "IOException during accept()", ex);
@@ -138,9 +125,9 @@
      */
     void closeServerSocket() {
         try {
-            if (mServerSocket != null) {
-                FileDescriptor fd = mServerSocket.getFileDescriptor();
-                mServerSocket.close();
+            if (mZygoteSocket != null) {
+                FileDescriptor fd = mZygoteSocket.getFileDescriptor();
+                mZygoteSocket.close();
                 if (fd != null && mCloseSocketFd) {
                     Os.close(fd);
                 }
@@ -151,7 +138,7 @@
             Log.e(TAG, "Zygote:  error closing descriptor", ex);
         }
 
-        mServerSocket = null;
+        mZygoteSocket = null;
     }
 
     /**
@@ -160,8 +147,8 @@
      * closure after a child process is forked off.
      */
 
-    FileDescriptor getServerSocketFileDescriptor() {
-        return mServerSocket.getFileDescriptor();
+    FileDescriptor getZygoteSocketFileDescriptor() {
+        return mZygoteSocket.getFileDescriptor();
     }
 
     /**
@@ -170,36 +157,67 @@
      * worth at a time.
      */
     Runnable runSelectLoop(String abiList) {
-        ArrayList<FileDescriptor> fds = new ArrayList<FileDescriptor>();
+        ArrayList<FileDescriptor> socketFDs = new ArrayList<FileDescriptor>();
         ArrayList<ZygoteConnection> peers = new ArrayList<ZygoteConnection>();
 
-        fds.add(mServerSocket.getFileDescriptor());
+        socketFDs.add(mZygoteSocket.getFileDescriptor());
         peers.add(null);
 
         while (true) {
-            StructPollfd[] pollFds = new StructPollfd[fds.size()];
-            for (int i = 0; i < pollFds.length; ++i) {
-                pollFds[i] = new StructPollfd();
-                pollFds[i].fd = fds.get(i);
-                pollFds[i].events = (short) POLLIN;
+            int[] blastulaPipeFDs = Zygote.getBlastulaPipeFDs();
+
+            // Space for all of the socket FDs, the Blastula Pool Event FD, and
+            // all of the open blastula read pipe FDs.
+            StructPollfd[] pollFDs =
+                new StructPollfd[socketFDs.size() + 1 + blastulaPipeFDs.length];
+
+            int pollIndex = 0;
+            for (FileDescriptor socketFD : socketFDs) {
+                pollFDs[pollIndex] = new StructPollfd();
+                pollFDs[pollIndex].fd = socketFD;
+                pollFDs[pollIndex].events = (short) POLLIN;
+                ++pollIndex;
             }
+
+            final int blastulaPoolEventFDIndex = pollIndex;
+            pollFDs[pollIndex] = new StructPollfd();
+            pollFDs[pollIndex].fd = Zygote.sBlastulaPoolEventFD;
+            pollFDs[pollIndex].events = (short) POLLIN;
+            ++pollIndex;
+
+            for (int blastulaPipeFD : blastulaPipeFDs) {
+                FileDescriptor managedFd = new FileDescriptor();
+                managedFd.setInt$(blastulaPipeFD);
+
+                pollFDs[pollIndex] = new StructPollfd();
+                pollFDs[pollIndex].fd = managedFd;
+                pollFDs[pollIndex].events = (short) POLLIN;
+                ++pollIndex;
+            }
+
             try {
-                Os.poll(pollFds, -1);
+                Os.poll(pollFDs, -1);
             } catch (ErrnoException ex) {
                 throw new RuntimeException("poll failed", ex);
             }
-            for (int i = pollFds.length - 1; i >= 0; --i) {
-                if ((pollFds[i].revents & POLLIN) == 0) {
+
+            while (--pollIndex >= 0) {
+                if ((pollFDs[pollIndex].revents & POLLIN) == 0) {
                     continue;
                 }
 
-                if (i == 0) {
+                if (pollIndex == 0) {
+                    // Zygote server socket
+
                     ZygoteConnection newPeer = acceptCommandPeer(abiList);
                     peers.add(newPeer);
-                    fds.add(newPeer.getFileDescriptor());
-                } else {
+                    socketFDs.add(newPeer.getFileDescriptor());
+
+                } else if (pollIndex < blastulaPoolEventFDIndex) {
+                    // Session socket accepted from the Zygote server socket
+
                     try {
-                        ZygoteConnection connection = peers.get(i);
+                        ZygoteConnection connection = peers.get(pollIndex);
                         final Runnable command = connection.processOneCommand(this);
 
                         if (mIsForkChild) {
@@ -217,12 +235,12 @@
                             }
 
                             // We don't know whether the remote side of the socket was closed or
-                            // not until we attempt to read from it from processOneCommand. This shows up as
-                            // a regular POLLIN event in our regular processing loop.
+                            // not until we attempt to read from it from processOneCommand. This
+                            // shows up as a regular POLLIN event in our regular processing loop.
                             if (connection.isClosedByPeer()) {
                                 connection.closeSocket();
-                                peers.remove(i);
-                                fds.remove(i);
+                                peers.remove(pollIndex);
+                                socketFDs.remove(pollIndex);
                             }
                         }
                     } catch (Exception e) {
@@ -234,13 +252,13 @@
 
                             Slog.e(TAG, "Exception executing zygote command: ", e);
 
-                            // Make sure the socket is closed so that the other end knows immediately
-                            // that something has gone wrong and doesn't time out waiting for a
-                            // response.
-                            ZygoteConnection conn = peers.remove(i);
+                            // Make sure the socket is closed so that the other end knows
+                            // immediately that something has gone wrong and doesn't time out
+                            // waiting for a response.
+                            ZygoteConnection conn = peers.remove(pollIndex);
                             conn.closeSocket();
 
-                            fds.remove(i);
+                            socketFDs.remove(pollIndex);
                         } else {
                             // We're in the child so any exception caught here has happened post
                             // fork and before we execute ActivityThread.main (or any other main()
@@ -254,6 +272,55 @@
                         // is returned.
                         mIsForkChild = false;
                     }
+                } else {
+                    // Either the blastula pool event FD or a blastula reporting pipe.
+
+                    // If this is the event FD the payload will be the number of blastulas removed.
+                    // If this is a reporting pipe FD the payload will be the PID of the blastula
+                    // that was just specialized.
+                    long messagePayload = -1;
+
+                    try {
+                        byte[] buffer = new byte[Zygote.BLASTULA_MANAGEMENT_MESSAGE_BYTES];
+                        int readBytes = Os.read(pollFDs[pollIndex].fd, buffer, 0, buffer.length);
+
+                        if (readBytes == Zygote.BLASTULA_MANAGEMENT_MESSAGE_BYTES) {
+                            DataInputStream inputStream =
+                                    new DataInputStream(new ByteArrayInputStream(buffer));
+
+                            messagePayload = inputStream.readLong();
+                        } else {
+                            Log.e(TAG, "Incomplete read from blastula management FD of size "
+                                    + readBytes);
+                            continue;
+                        }
+                    } catch (Exception ex) {
+                        if (pollIndex == blastulaPoolEventFDIndex) {
+                            Log.e(TAG, "Failed to read from blastula pool event FD: "
+                                    + ex.getMessage());
+                        } else {
+                            Log.e(TAG, "Failed to read from blastula reporting pipe: "
+                                    + ex.getMessage());
+                        }
+
+                        continue;
+                    }
+
+                    if (pollIndex > blastulaPoolEventFDIndex) {
+                        Zygote.removeBlastulaTableEntry((int) messagePayload);
+                    }
+
+                    int[] sessionSocketRawFDs =
+                            socketFDs.subList(1, socketFDs.size())
+                                .stream()
+                                .mapToInt(fd -> fd.getInt$())
+                                .toArray();
+
+                    final Runnable command = Zygote.fillBlastulaPool(sessionSocketRawFDs);
+
+                    if (command != null) {
+                        return command;
+                    }
                 }
             }
         }
diff --git a/core/java/com/android/internal/statusbar/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/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/AndroidRuntime.cpp b/core/jni/AndroidRuntime.cpp
index 18d9b5a..f458299 100644
--- a/core/jni/AndroidRuntime.cpp
+++ b/core/jni/AndroidRuntime.cpp
@@ -152,6 +152,8 @@
 extern int register_android_graphics_text_LineBreaker(JNIEnv *env);
 extern int register_android_view_DisplayEventReceiver(JNIEnv* env);
 extern int register_android_view_DisplayListCanvas(JNIEnv* env);
+extern int register_android_view_InputApplicationHandle(JNIEnv* env);
+extern int register_android_view_InputWindowHandle(JNIEnv* env);
 extern int register_android_view_TextureLayer(JNIEnv* env);
 extern int register_android_view_RenderNode(JNIEnv* env);
 extern int register_android_view_RenderNodeAnimator(JNIEnv* env);
@@ -1370,6 +1372,8 @@
     REG_JNI(register_android_view_RenderNode),
     REG_JNI(register_android_view_RenderNodeAnimator),
     REG_JNI(register_android_view_DisplayListCanvas),
+    REG_JNI(register_android_view_InputApplicationHandle),
+    REG_JNI(register_android_view_InputWindowHandle),
     REG_JNI(register_android_view_TextureLayer),
     REG_JNI(register_android_view_ThreadedRenderer),
     REG_JNI(register_android_view_Surface),
diff --git a/core/jni/android_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_view_SurfaceControl.cpp b/core/jni/android_view_SurfaceControl.cpp
index 0453195..69877c7 100644
--- a/core/jni/android_view_SurfaceControl.cpp
+++ b/core/jni/android_view_SurfaceControl.cpp
@@ -360,7 +360,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();
 
diff --git a/core/jni/com_android_internal_os_Zygote.cpp b/core/jni/com_android_internal_os_Zygote.cpp
index 2e7184b..8681d4b 100644
--- a/core/jni/com_android_internal_os_Zygote.cpp
+++ b/core/jni/com_android_internal_os_Zygote.cpp
@@ -1472,7 +1472,7 @@
     fds_to_close.insert(fds_to_close.end(), blastula_pipes.begin(), blastula_pipes.end());
     fds_to_ignore.insert(fds_to_ignore.end(), blastula_pipes.begin(), blastula_pipes.end());
 
-//    fds_to_close.push_back(gBlastulaPoolSocketFD);
+    fds_to_close.push_back(gBlastulaPoolSocketFD);
 
     if (gBlastulaPoolEventFD != -1) {
       fds_to_close.push_back(gBlastulaPoolEventFD);
@@ -1498,7 +1498,7 @@
   std::vector<int> fds_to_close(MakeBlastulaPipeReadFDVector()),
                    fds_to_ignore(fds_to_close);
 
-//  fds_to_close.push_back(gBlastulaPoolSocketFD);
+  fds_to_close.push_back(gBlastulaPoolSocketFD);
 
   if (gBlastulaPoolEventFD != -1) {
     fds_to_close.push_back(gBlastulaPoolEventFD);
diff --git a/core/proto/android/providers/settings/global.proto b/core/proto/android/providers/settings/global.proto
index 4158577..d79eb94 100644
--- a/core/proto/android/providers/settings/global.proto
+++ b/core/proto/android/providers/settings/global.proto
@@ -449,6 +449,8 @@
         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;
     }
     optional Gpu gpu = 59;
 
@@ -535,6 +537,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 +743,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..c0d6139 100644
--- a/core/proto/android/providers/settings/secure.proto
+++ b/core/proto/android/providers/settings/secure.proto
@@ -528,8 +528,9 @@
 
     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 ];
 
     // Please insert fields in alphabetical order and group them into messages
     // if possible (to avoid reaching the method limit).
-    // Next tag = 76;
+    // Next tag = 77;
 }
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/powermanagerservice.proto b/core/proto/android/server/powermanagerservice.proto
index cee556a..af0a942 100644
--- a/core/proto/android/server/powermanagerservice.proto
+++ b/core/proto/android/server/powermanagerservice.proto
@@ -350,4 +350,12 @@
     // The value of Global.LOW_POWER_MODE_TRIGGER_LEVEL. This is a cached value, so it could
     // be slightly different from what's in GlobalSettingsProto.LowPowerMode.
     optional int32 setting_battery_saver_trigger_threshold = 11;
+
+    // The value of Global.LOW_POWER_MODE_STICKY_AUTO_DISABLE_ENABLED. This is a cached value, so
+    // it could be slightly different from what's in GlobalSettingsProto.LowPowerMode.
+    optional bool setting_battery_saver_sticky_auto_disable_enabled = 12;
+
+    // The value of Global.LOW_POWER_MODE_STICKY_AUTO_DISABLE_LEVEL. This is a cached value, so it
+    // could be slightly different from what's in GlobalSettingsProto.LowPowerMode.
+    optional int32 setting_battery_saver_sticky_auto_disable_threshold = 13;
 }
diff --git a/core/proto/android/service/usb.proto b/core/proto/android/service/usb.proto
index f7dcee2..00fae3d 100644
--- a/core/proto/android/service/usb.proto
+++ b/core/proto/android/service/usb.proto
@@ -245,11 +245,20 @@
         DATA_ROLE_DEVICE = 2;
     }
 
+    /* Same as android.hardware.usb.V1_2.Constants.ContaminantPresenceStatus */
+    enum ContaminantPresenceStatus {
+        CONTAMINANT_STATUS_NOT_SUPPORTED = 0;
+        CONTAMINANT_STATUS_DISABLED = 1;
+        CONTAMINANT_STATUS_NOT_DETECTED = 2;
+        CONTAMINANT_STATUS_DETECTED = 3;
+    }
+
     optional bool connected = 1;
     optional UsbPortProto.Mode current_mode = 2;
     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/view/remote_animation_target.proto b/core/proto/android/view/remote_animation_target.proto
index fb4d5bd..808c514 100644
--- a/core/proto/android/view/remote_animation_target.proto
+++ b/core/proto/android/view/remote_animation_target.proto
@@ -42,4 +42,6 @@
     optional .android.graphics.PointProto position = 8;
     optional .android.graphics.RectProto source_container_bounds = 9;
     optional .android.app.WindowConfigurationProto window_configuration = 10;
+    optional .android.view.SurfaceControlProto start_leash = 11;
+    optional .android.graphics.RectProto start_bounds = 12;
 }
diff --git a/core/res/AndroidManifest.xml b/core/res/AndroidManifest.xml
index 96b8dc2..f92df6a 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 -->
     <!-- ======================================= -->
diff --git a/core/res/res/values/attrs_manifest.xml b/core/res/res/values/attrs_manifest.xml
index 8ef264a..de6468d 100644
--- a/core/res/res/values/attrs_manifest.xml
+++ b/core/res/res/values/attrs_manifest.xml
@@ -950,6 +950,17 @@
          <p>The default value of this attribute is <code>false</code>. -->
     <attr name="allowEmbedded" format="boolean" />
 
+    <!-- @hide @SystemApi Specifies whether this {@link android.app.Activity} should be shown on
+         top of the lock screen whenever the lockscreen is up and this activity has another
+         activity behind it with the {@link android.R.attr#showWhenLocked} attribute set. That
+         is, this activity is only visible on the lock screen if there is another activity with
+         the {@link android.R.attr#showWhenLocked} attribute visible at the same time on the
+         lock screen. A use case for this is permission dialogs, that should only be visible on
+         the lock screen if their requesting activity is also visible.
+
+         <p>The default value of this attribute is <code>false</code>. -->
+    <attr name="inheritShowWhenLocked" format="boolean" />
+
     <!-- Descriptive text for the associated data. -->
     <attr name="description" format="reference" />
 
@@ -2415,18 +2426,6 @@
         <attr name="showForAllUsers" />
 
         <attr name="showWhenLocked" />
-        <!-- @hide @SystemApi Specifies whether this {@link android.app.Activity} should be shown on
-             top of the lock screen whenever the lockscreen is up and this activity has another
-             activity behind it with the {@link android.R.attr#showWhenLocked} attribute set. That
-             is, this activity is only visible on the lock screen if there is another activity with
-             the {@link android.R.attr#showWhenLocked} attribute visible at the same time on the
-             lock screen. A use case for this is permission dialogs, that should only be visible on
-             the lock screen if their requesting activity is also visible.
-
-         The default value of this attribute is <code>false</code>. -->
-    <attr name="inheritShowWhenLocked" format="boolean" />
-
-
         <attr name="inheritShowWhenLocked" />
         <attr name="turnScreenOn" />
 
diff --git a/core/res/res/values/config.xml b/core/res/res/values/config.xml
index 1a21306..dd8ecdb 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>
 
diff --git a/core/res/res/values/public.xml b/core/res/res/values/public.xml
index 777886a..4235341 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">
diff --git a/core/res/res/values/strings.xml b/core/res/res/values/strings.xml
index f2b4b9c..65a8959 100644
--- a/core/res/res/values/strings.xml
+++ b/core/res/res/values/strings.xml
@@ -3573,6 +3573,15 @@
     <string name="adb_active_notification_message">Tap to turn off USB debugging</string>
     <string name="adb_active_notification_message" product="tv">Select to disable USB debugging.</string>
 
+    <!-- Title of notification shown when contaminant is detected on the USB port. [CHAR LIMIT=NONE] -->
+    <string name="usb_contaminant_detected_title">Liquid or debris in USB port</string>
+    <!-- Message of notification shown when contaminant is detected on the USB port. [CHAR LIMIT=NONE] -->
+    <string name="usb_contaminant_detected_message">USB port is automatically disabled. Tap to learn more.</string>
+    <!-- Title of notification shown when contaminant is no longer detected on the USB port. [CHAR LIMIT=NONE] -->
+    <string name="usb_contaminant_not_detected_title">Safe to use USB port</string>
+    <!-- Message of notification shown when contaminant is no longer detected on the USB port. [CHAR LIMIT=NONE] -->
+    <string name="usb_contaminant_not_detected_message">Phone no longer detects liquid or debris.</string>
+
     <!-- Title of notification shown to indicate that bug report is being collected. -->
     <string name="taking_remote_bugreport_notification_title">Taking bug report\u2026</string>
     <!-- Title of notification shown to ask for user consent for sharing a bugreport that was requested remotely by the IT administrator. -->
diff --git a/core/res/res/values/symbols.xml b/core/res/res/values/symbols.xml
index 83982c7..5991d88 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" />
@@ -2104,6 +2105,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" />
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/src/android/content/pm/PackageParserTest.java b/core/tests/coretests/src/android/content/pm/PackageParserTest.java
index c5454a6..7b92cf5 100644
--- a/core/tests/coretests/src/android/content/pm/PackageParserTest.java
+++ b/core/tests/coretests/src/android/content/pm/PackageParserTest.java
@@ -528,11 +528,13 @@
         PackageInfo pi = PackageParser.generatePackageInfoFromApex(apexFile, false);
         assertEquals("com.google.android.tzdata", pi.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(1, pi.getLongVersionCode());
+        assertEquals(1, pi.applicationInfo.longVersionCode);
         assertNotNull(pi.signingInfo);
         assertTrue(pi.signingInfo.getApkContentsSigners().length > 0);
     }
diff --git a/core/tests/coretests/src/android/provider/SettingsBackupTest.java b/core/tests/coretests/src/android/provider/SettingsBackupTest.java
index 1431741..a010cb6 100644
--- a/core/tests/coretests/src/android/provider/SettingsBackupTest.java
+++ b/core/tests/coretests/src/android/provider/SettingsBackupTest.java
@@ -298,6 +298,7 @@
                     Settings.Global.LOCATION_BACKGROUND_THROTTLE_INTERVAL_MS,
                     Settings.Global.LOCATION_BACKGROUND_THROTTLE_PROXIMITY_ALERT_INTERVAL_MS,
                     Settings.Global.LOCATION_BACKGROUND_THROTTLE_PACKAGE_WHITELIST,
+                    Settings.Global.LOCATION_IGNORE_SETTINGS_PACKAGE_WHITELIST,
                     Settings.Global.LOCATION_DISABLE_STATUS_CALLBACKS,
                     Settings.Global.LOCATION_LAST_LOCATION_MAX_AGE_MILLIS,
                     Settings.Global.LOCATION_GLOBAL_KILL_SWITCH,
@@ -388,8 +389,6 @@
                     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,
@@ -487,6 +486,7 @@
                     Settings.Global.GUP_DEV_OPT_IN_APPS,
                     Settings.Global.GUP_DEV_OPT_OUT_APPS,
                     Settings.Global.GUP_BLACKLIST,
+                    Settings.Global.GAME_DRIVER_WHITELIST,
                     Settings.Global.GPU_DEBUG_LAYER_APP,
                     Settings.Global.ENABLE_GNSS_RAW_MEAS_FULL_TRACKING,
                     Settings.Global.INSTALL_CARRIER_APP_NOTIFICATION_PERSISTENT,
@@ -564,7 +564,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/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/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/libs/hwui/renderthread/RenderProxy.cpp b/libs/hwui/renderthread/RenderProxy.cpp
index ab59af7..720c603 100644
--- a/libs/hwui/renderthread/RenderProxy.cpp
+++ b/libs/hwui/renderthread/RenderProxy.cpp
@@ -241,8 +241,10 @@
 }
 
 void RenderProxy::dumpGraphicsMemory(int fd) {
-    auto& thread = RenderThread::getInstance();
-    thread.queue().runSync([&]() { thread.dumpGraphicsMemory(fd); });
+    if (RenderThread::hasInstance()) {
+        auto& thread = RenderThread::getInstance();
+        thread.queue().runSync([&]() { thread.dumpGraphicsMemory(fd); });
+    }
 }
 
 void RenderProxy::setProcessStatsBuffer(int fd) {
diff --git a/media/Android.bp b/media/Android.bp
index 0eb86ac..88ed9c6a 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,88 @@
 }
 
 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",
+        "apex/java/android/media/Session2Token.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 100%
rename from media/java/android/media/MediaConstants.java
rename to media/apex/java/android/media/MediaConstants.java
diff --git a/media/java/android/media/MediaController2.java b/media/apex/java/android/media/MediaController2.java
similarity index 100%
rename from media/java/android/media/MediaController2.java
rename to media/apex/java/android/media/MediaController2.java
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 100%
rename from media/java/android/media/MediaSession2.java
rename to media/apex/java/android/media/MediaSession2.java
diff --git a/media/java/android/media/MediaSession2Service.java b/media/apex/java/android/media/MediaSession2Service.java
similarity index 100%
rename from media/java/android/media/MediaSession2Service.java
rename to media/apex/java/android/media/MediaSession2Service.java
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/Session2Token.java b/media/apex/java/android/media/Session2Token.java
similarity index 100%
rename from media/java/android/media/Session2Token.java
rename to media/apex/java/android/media/Session2Token.java
diff --git a/media/java/android/media/VolumeProvider.java b/media/apex/java/android/media/VolumeProvider.java
similarity index 97%
rename from media/java/android/media/VolumeProvider.java
rename to media/apex/java/android/media/VolumeProvider.java
index 0297406..49202ee 100644
--- a/media/java/android/media/VolumeProvider.java
+++ b/media/apex/java/android/media/VolumeProvider.java
@@ -158,7 +158,10 @@
      * @hide
      */
     @SystemApi
-    public static abstract class Callback {
+    public abstract static class Callback {
+        /**
+         * Called when volume changed.
+         */
         public abstract void onVolumeChanged(VolumeProvider volumeProvider);
     }
 }
diff --git a/media/java/android/media/browse/MediaBrowser.aidl b/media/apex/java/android/media/browse/MediaBrowser.aidl
similarity index 100%
rename from media/java/android/media/browse/MediaBrowser.aidl
rename to media/apex/java/android/media/browse/MediaBrowser.aidl
diff --git a/media/java/android/media/browse/MediaBrowser.java b/media/apex/java/android/media/browse/MediaBrowser.java
similarity index 98%
rename from media/java/android/media/browse/MediaBrowser.java
rename to media/apex/java/android/media/browse/MediaBrowser.java
index b1b14c6..2dffef9 100644
--- a/media/java/android/media/browse/MediaBrowser.java
+++ b/media/apex/java/android/media/browse/MediaBrowser.java
@@ -284,8 +284,8 @@
      */
     public @NonNull ComponentName getServiceComponent() {
         if (!isConnected()) {
-            throw new IllegalStateException("getServiceComponent() called while not connected" +
-                    " (state=" + mState + ")");
+            throw new IllegalStateException("getServiceComponent() called while not connected"
+                    + " (state=" + mState + ")");
         }
         return mServiceComponent;
     }
@@ -331,7 +331,7 @@
      *
      * @throws IllegalStateException if not connected.
      */
-     public @NonNull MediaSession.Token getSessionToken() {
+    public @NonNull MediaSession.Token getSessionToken() {
         if (!isConnected()) {
             throw new IllegalStateException("getSessionToken() called while not connected (state="
                     + mState + ")");
@@ -464,7 +464,7 @@
                     cb.onError(mediaId);
                     return;
                 }
-                cb.onItemLoaded((MediaItem)item);
+                cb.onItemLoaded((MediaItem) item);
             }
         };
         try {
@@ -575,7 +575,7 @@
         }
     }
 
-    private final void onServiceConnected(final IMediaBrowserServiceCallbacks callback,
+    private void onServiceConnected(final IMediaBrowserServiceCallbacks callback,
             final String root, final MediaSession.Token session, final Bundle extra) {
         mHandler.post(new Runnable() {
             @Override
@@ -625,7 +625,7 @@
         });
     }
 
-    private final void onConnectionFailed(final IMediaBrowserServiceCallbacks callback) {
+    private void onConnectionFailed(final IMediaBrowserServiceCallbacks callback) {
         mHandler.post(new Runnable() {
             @Override
             public void run() {
@@ -652,7 +652,7 @@
         });
     }
 
-    private final void onLoadChildren(final IMediaBrowserServiceCallbacks callback,
+    private void onLoadChildren(final IMediaBrowserServiceCallbacks callback,
             final String parentId, final MediaParceledListSlice list, final Bundle options) {
         mHandler.post(new Runnable() {
             @Override
@@ -745,7 +745,7 @@
 
         /** @hide */
         @Retention(RetentionPolicy.SOURCE)
-        @IntDef(flag=true, value = { FLAG_BROWSABLE, FLAG_PLAYABLE })
+        @IntDef(flag = true, value = { FLAG_BROWSABLE, FLAG_PLAYABLE })
         public @interface Flags { }
 
         /**
@@ -886,7 +886,7 @@
     /**
      * Callbacks for subscription related events.
      */
-    public static abstract class SubscriptionCallback {
+    public abstract static class SubscriptionCallback {
         Binder mToken;
 
         public SubscriptionCallback() {
@@ -947,7 +947,7 @@
     /**
      * Callback for receiving the result of {@link #getItem}.
      */
-    public static abstract class ItemCallback {
+    public abstract static class ItemCallback {
         /**
          * Called when the item has been returned by the connected service.
          *
@@ -1078,7 +1078,7 @@
     private static class ServiceCallbacks extends IMediaBrowserServiceCallbacks.Stub {
         private WeakReference<MediaBrowser> mMediaBrowser;
 
-        public ServiceCallbacks(MediaBrowser mediaBrowser) {
+        ServiceCallbacks(MediaBrowser mediaBrowser) {
             mMediaBrowser = new WeakReference<MediaBrowser>(mediaBrowser);
         }
 
@@ -1125,7 +1125,7 @@
         private final List<SubscriptionCallback> mCallbacks;
         private final List<Bundle> mOptionsList;
 
-        public Subscription() {
+        Subscription() {
             mCallbacks = new ArrayList<>();
             mOptionsList = new ArrayList<>();
         }
diff --git a/media/java/android/media/browse/MediaBrowserUtils.java b/media/apex/java/android/media/browse/MediaBrowserUtils.java
similarity index 94%
rename from media/java/android/media/browse/MediaBrowserUtils.java
rename to media/apex/java/android/media/browse/MediaBrowserUtils.java
index 2943e60..19d9f00 100644
--- a/media/java/android/media/browse/MediaBrowserUtils.java
+++ b/media/apex/java/android/media/browse/MediaBrowserUtils.java
@@ -22,6 +22,9 @@
  * @hide
  */
 public class MediaBrowserUtils {
+    /**
+     * Compares whether two bundles are the same.
+     */
     public static boolean areSameOptions(Bundle options1, Bundle options2) {
         if (options1 == options2) {
             return true;
@@ -39,6 +42,9 @@
         }
     }
 
+    /**
+     * Returnes true if the page options has duplicated items.
+     */
     public static boolean hasDuplicatedItems(Bundle options1, Bundle options2) {
         int page1 = options1 == null ? -1 : options1.getInt(MediaBrowser.EXTRA_PAGE, -1);
         int page2 = options2 == null ? -1 : options2.getInt(MediaBrowser.EXTRA_PAGE, -1);
diff --git a/media/java/android/media/session/ControllerCallbackLink.aidl b/media/apex/java/android/media/session/ControllerCallbackLink.aidl
similarity index 100%
rename from media/java/android/media/session/ControllerCallbackLink.aidl
rename to media/apex/java/android/media/session/ControllerCallbackLink.aidl
diff --git a/media/java/android/media/session/ControllerCallbackLink.java b/media/apex/java/android/media/session/ControllerCallbackLink.java
similarity index 100%
rename from media/java/android/media/session/ControllerCallbackLink.java
rename to media/apex/java/android/media/session/ControllerCallbackLink.java
diff --git a/media/java/android/media/session/ControllerLink.aidl b/media/apex/java/android/media/session/ControllerLink.aidl
similarity index 100%
rename from media/java/android/media/session/ControllerLink.aidl
rename to media/apex/java/android/media/session/ControllerLink.aidl
diff --git a/media/java/android/media/session/ControllerLink.java b/media/apex/java/android/media/session/ControllerLink.java
similarity index 100%
rename from media/java/android/media/session/ControllerLink.java
rename to media/apex/java/android/media/session/ControllerLink.java
diff --git a/media/java/android/media/session/ISession.aidl b/media/apex/java/android/media/session/ISession.aidl
similarity index 100%
rename from media/java/android/media/session/ISession.aidl
rename to media/apex/java/android/media/session/ISession.aidl
diff --git a/media/java/android/media/session/ISessionCallback.aidl b/media/apex/java/android/media/session/ISessionCallback.aidl
similarity index 100%
rename from media/java/android/media/session/ISessionCallback.aidl
rename to media/apex/java/android/media/session/ISessionCallback.aidl
diff --git a/media/java/android/media/session/ISessionController.aidl b/media/apex/java/android/media/session/ISessionController.aidl
similarity index 100%
rename from media/java/android/media/session/ISessionController.aidl
rename to media/apex/java/android/media/session/ISessionController.aidl
diff --git a/media/java/android/media/session/ISessionControllerCallback.aidl b/media/apex/java/android/media/session/ISessionControllerCallback.aidl
similarity index 100%
rename from media/java/android/media/session/ISessionControllerCallback.aidl
rename to media/apex/java/android/media/session/ISessionControllerCallback.aidl
diff --git a/media/java/android/media/session/MediaController.aidl b/media/apex/java/android/media/session/MediaController.aidl
similarity index 100%
rename from media/java/android/media/session/MediaController.aidl
rename to media/apex/java/android/media/session/MediaController.aidl
diff --git a/media/java/android/media/session/MediaController.java b/media/apex/java/android/media/session/MediaController.java
similarity index 99%
rename from media/java/android/media/session/MediaController.java
rename to media/apex/java/android/media/session/MediaController.java
index 057c9cb..d43acf4 100644
--- a/media/java/android/media/session/MediaController.java
+++ b/media/apex/java/android/media/session/MediaController.java
@@ -206,6 +206,7 @@
                 } catch (RuntimeException e) {
                     Log.wtf(TAG, "Error calling adjustVolumeBy", e);
                 }
+                break;
             }
 
             case KeyEvent.ACTION_UP: {
@@ -319,7 +320,7 @@
      *
      * @return The current set of flags for the session.
      */
-    public @MediaSession.SessionFlags long getFlags() {
+    public long getFlags() {
         try {
             return mSessionBinder.getFlags();
         } catch (RuntimeException e) {
@@ -582,7 +583,7 @@
         return null;
     }
 
-    private final void postMessage(int what, Object obj, Bundle data) {
+    private void postMessage(int what, Object obj, Bundle data) {
         synchronized (mLock) {
             for (int i = mCallbacks.size() - 1; i >= 0; i--) {
                 mCallbacks.get(i).post(what, obj, data);
@@ -594,7 +595,7 @@
      * Callback for receiving updates from the session. A Callback can be
      * registered using {@link #registerCallback}.
      */
-    public static abstract class Callback {
+    public abstract static class Callback {
         /**
          * Override to handle the session being destroyed. The session is no
          * longer valid after this call and calls to it will be ignored.
@@ -1191,11 +1192,11 @@
         }
     }
 
-    private final static class MessageHandler extends Handler {
+    private static final class MessageHandler extends Handler {
         private final MediaController.Callback mCallback;
         private boolean mRegistered = false;
 
-        public MessageHandler(Looper looper, MediaController.Callback cb) {
+        MessageHandler(Looper looper, MediaController.Callback cb) {
             super(looper);
             mCallback = cb;
         }
diff --git a/media/java/android/media/session/MediaSessionEngine.java b/media/apex/java/android/media/session/MediaSessionEngine.java
similarity index 99%
rename from media/java/android/media/session/MediaSessionEngine.java
rename to media/apex/java/android/media/session/MediaSessionEngine.java
index f159a95..1f5fa5f 100644
--- a/media/java/android/media/session/MediaSessionEngine.java
+++ b/media/apex/java/android/media/session/MediaSessionEngine.java
@@ -53,7 +53,7 @@
  */
 @SystemApi
 public final class MediaSessionEngine implements AutoCloseable {
-    private static final String TAG = MediaSession.TAG;
+    private static final String TAG = "MediaSession";
 
     private final Object mLock = new Object();
     private final int mMaxBitmapSize;
@@ -172,7 +172,7 @@
      *
      * @param flags The flags to set for this session.
      */
-    public void setFlags(@MediaSession.SessionFlags int flags) {
+    public void setFlags(int flags) {
         try {
             mSessionLink.setFlags(flags);
         } catch (RuntimeException e) {
@@ -409,7 +409,7 @@
      * <li>{@link Rating#RATING_THUMB_UP_DOWN}</li>
      * </ul>
      */
-    public void setRatingType(@Rating.Style int type) {
+    public void setRatingType(int type) {
         try {
             mSessionLink.setRatingType(type);
         } catch (RuntimeException e) {
diff --git a/media/java/android/media/session/MediaSessionProviderService.java b/media/apex/java/android/media/session/MediaSessionProviderService.java
similarity index 100%
rename from media/java/android/media/session/MediaSessionProviderService.java
rename to media/apex/java/android/media/session/MediaSessionProviderService.java
diff --git a/media/java/android/media/session/PlaybackState.aidl b/media/apex/java/android/media/session/PlaybackState.aidl
similarity index 100%
rename from media/java/android/media/session/PlaybackState.aidl
rename to media/apex/java/android/media/session/PlaybackState.aidl
diff --git a/media/java/android/media/session/PlaybackState.java b/media/apex/java/android/media/session/PlaybackState.java
similarity index 95%
rename from media/java/android/media/session/PlaybackState.java
rename to media/apex/java/android/media/session/PlaybackState.java
index 0d0ec4c..6b28c97 100644
--- a/media/java/android/media/session/PlaybackState.java
+++ b/media/apex/java/android/media/session/PlaybackState.java
@@ -41,7 +41,7 @@
     /**
      * @hide
      */
-    @LongDef(flag=true, value={ACTION_STOP, ACTION_PAUSE, ACTION_PLAY, ACTION_REWIND,
+    @LongDef(flag = true, value = {ACTION_STOP, ACTION_PAUSE, ACTION_PLAY, ACTION_REWIND,
             ACTION_SKIP_TO_PREVIOUS, ACTION_SKIP_TO_NEXT, ACTION_FAST_FORWARD, ACTION_SET_RATING,
             ACTION_SEEK_TO, ACTION_PLAY_PAUSE, ACTION_PLAY_FROM_MEDIA_ID, ACTION_PLAY_FROM_SEARCH,
             ACTION_SKIP_TO_QUEUE_ITEM, ACTION_PLAY_FROM_URI, ACTION_PREPARE,
@@ -191,42 +191,42 @@
      * @see Builder#setState(int, long, float)
      * @see Builder#setState(int, long, float, long)
      */
-    public final static int STATE_NONE = 0;
+    public static final int STATE_NONE = 0;
 
     /**
      * State indicating this item is currently stopped.
      *
      * @see Builder#setState
      */
-    public final static int STATE_STOPPED = 1;
+    public static final int STATE_STOPPED = 1;
 
     /**
      * State indicating this item is currently paused.
      *
      * @see Builder#setState
      */
-    public final static int STATE_PAUSED = 2;
+    public static final int STATE_PAUSED = 2;
 
     /**
      * State indicating this item is currently playing.
      *
      * @see Builder#setState
      */
-    public final static int STATE_PLAYING = 3;
+    public static final int STATE_PLAYING = 3;
 
     /**
      * State indicating this item is currently fast forwarding.
      *
      * @see Builder#setState
      */
-    public final static int STATE_FAST_FORWARDING = 4;
+    public static final int STATE_FAST_FORWARDING = 4;
 
     /**
      * State indicating this item is currently rewinding.
      *
      * @see Builder#setState
      */
-    public final static int STATE_REWINDING = 5;
+    public static final int STATE_REWINDING = 5;
 
     /**
      * State indicating this item is currently buffering and will begin playing
@@ -234,7 +234,7 @@
      *
      * @see Builder#setState
      */
-    public final static int STATE_BUFFERING = 6;
+    public static final int STATE_BUFFERING = 6;
 
     /**
      * State indicating this item is currently in an error state. The error
@@ -242,7 +242,7 @@
      *
      * @see Builder#setState
      */
-    public final static int STATE_ERROR = 7;
+    public static final int STATE_ERROR = 7;
 
     /**
      * State indicating the class doing playback is currently connecting to a
@@ -252,21 +252,21 @@
      *
      * @see Builder#setState
      */
-    public final static int STATE_CONNECTING = 8;
+    public static final int STATE_CONNECTING = 8;
 
     /**
      * State indicating the player is currently skipping to the previous item.
      *
      * @see Builder#setState
      */
-    public final static int STATE_SKIPPING_TO_PREVIOUS = 9;
+    public static final int STATE_SKIPPING_TO_PREVIOUS = 9;
 
     /**
      * State indicating the player is currently skipping to the next item.
      *
      * @see Builder#setState
      */
-    public final static int STATE_SKIPPING_TO_NEXT = 10;
+    public static final int STATE_SKIPPING_TO_NEXT = 10;
 
     /**
      * State indicating the player is currently skipping to a specific item in
@@ -274,12 +274,12 @@
      *
      * @see Builder#setState
      */
-    public final static int STATE_SKIPPING_TO_QUEUE_ITEM = 11;
+    public static final int STATE_SKIPPING_TO_QUEUE_ITEM = 11;
 
     /**
      * Use this value for the position to indicate the position is not known.
      */
-    public final static long PLAYBACK_POSITION_UNKNOWN = -1;
+    public static final long PLAYBACK_POSITION_UNKNOWN = -1;
 
     private final int mState;
     private final long mPosition;
@@ -534,19 +534,19 @@
             return 0;
         }
 
-        public static final Parcelable.Creator<PlaybackState.CustomAction> CREATOR
-                = new Parcelable.Creator<PlaybackState.CustomAction>() {
+        public static final Parcelable.Creator<PlaybackState.CustomAction> CREATOR =
+                new Parcelable.Creator<PlaybackState.CustomAction>() {
 
-            @Override
-            public PlaybackState.CustomAction createFromParcel(Parcel p) {
-                return new PlaybackState.CustomAction(p);
-            }
+                    @Override
+                    public PlaybackState.CustomAction createFromParcel(Parcel p) {
+                        return new PlaybackState.CustomAction(p);
+                    }
 
-            @Override
-            public PlaybackState.CustomAction[] newArray(int size) {
-                return new PlaybackState.CustomAction[size];
-            }
-        };
+                    @Override
+                    public PlaybackState.CustomAction[] newArray(int size) {
+                        return new PlaybackState.CustomAction[size];
+                    }
+                };
 
         /**
          * Returns the action of the {@link CustomAction}.
@@ -588,10 +588,7 @@
 
         @Override
         public String toString() {
-            return "Action:" +
-                    "mName='" + mName +
-                    ", mIcon=" + mIcon +
-                    ", mExtras=" + mExtras;
+            return "Action:" + "mName='" + mName + ", mIcon=" + mIcon + ", mExtras=" + mExtras;
         }
 
         /**
diff --git a/media/java/android/media/session/SessionCallbackLink.aidl b/media/apex/java/android/media/session/SessionCallbackLink.aidl
similarity index 100%
rename from media/java/android/media/session/SessionCallbackLink.aidl
rename to media/apex/java/android/media/session/SessionCallbackLink.aidl
diff --git a/media/java/android/media/session/SessionCallbackLink.java b/media/apex/java/android/media/session/SessionCallbackLink.java
similarity index 100%
rename from media/java/android/media/session/SessionCallbackLink.java
rename to media/apex/java/android/media/session/SessionCallbackLink.java
diff --git a/media/java/android/media/session/SessionLink.aidl b/media/apex/java/android/media/session/SessionLink.aidl
similarity index 100%
rename from media/java/android/media/session/SessionLink.aidl
rename to media/apex/java/android/media/session/SessionLink.aidl
diff --git a/media/java/android/media/session/SessionLink.java b/media/apex/java/android/media/session/SessionLink.java
similarity index 98%
rename from media/java/android/media/session/SessionLink.java
rename to media/apex/java/android/media/session/SessionLink.java
index 0da0a5a..4ea7623 100644
--- a/media/java/android/media/session/SessionLink.java
+++ b/media/apex/java/android/media/session/SessionLink.java
@@ -23,8 +23,6 @@
 import android.media.AudioAttributes;
 import android.media.MediaMetadata;
 import android.media.MediaParceledListSlice;
-import android.media.Rating;
-import android.media.VolumeProvider;
 import android.media.session.MediaSession.QueueItem;
 import android.os.Bundle;
 import android.os.IBinder;
@@ -234,7 +232,7 @@
      *
      * @param type the rating type.
      */
-    void setRatingType(@Rating.Style int type) {
+    void setRatingType(int type) {
         try {
             mISession.setRatingType(type);
         } catch (RemoteException e) {
@@ -261,7 +259,7 @@
      * @param control the volume control type
      * @param max the max volume
      */
-    void setPlaybackToRemote(@VolumeProvider.ControlType int control, int max) {
+    void setPlaybackToRemote(int control, int max) {
         try {
             mISession.setPlaybackToRemote(control, max);
         } catch (RemoteException e) {
diff --git a/media/java/android/service/media/IMediaBrowserService.aidl b/media/apex/java/android/service/media/IMediaBrowserService.aidl
similarity index 94%
rename from media/java/android/service/media/IMediaBrowserService.aidl
rename to media/apex/java/android/service/media/IMediaBrowserService.aidl
index 84f41f6..1c50ec7 100644
--- a/media/java/android/service/media/IMediaBrowserService.aidl
+++ b/media/apex/java/android/service/media/IMediaBrowserService.aidl
@@ -2,9 +2,7 @@
 
 package android.service.media;
 
-import android.content.res.Configuration;
 import android.service.media.IMediaBrowserServiceCallbacks;
-import android.net.Uri;
 import android.os.Bundle;
 import android.os.ResultReceiver;
 
diff --git a/media/java/android/service/media/IMediaBrowserServiceCallbacks.aidl b/media/apex/java/android/service/media/IMediaBrowserServiceCallbacks.aidl
similarity index 96%
rename from media/java/android/service/media/IMediaBrowserServiceCallbacks.aidl
rename to media/apex/java/android/service/media/IMediaBrowserServiceCallbacks.aidl
index 8dc480d..507a8f7 100644
--- a/media/java/android/service/media/IMediaBrowserServiceCallbacks.aidl
+++ b/media/apex/java/android/service/media/IMediaBrowserServiceCallbacks.aidl
@@ -2,7 +2,6 @@
 
 package android.service.media;
 
-import android.graphics.Bitmap;
 import android.media.MediaParceledListSlice;
 import android.media.session.MediaSession;
 import android.os.Bundle;
diff --git a/media/java/android/service/media/MediaBrowserService.java b/media/apex/java/android/service/media/MediaBrowserService.java
similarity index 98%
rename from media/java/android/service/media/MediaBrowserService.java
rename to media/apex/java/android/service/media/MediaBrowserService.java
index 2fbc699..d9ef6ae 100644
--- a/media/java/android/service/media/MediaBrowserService.java
+++ b/media/apex/java/android/service/media/MediaBrowserService.java
@@ -98,7 +98,7 @@
 
     /** @hide */
     @Retention(RetentionPolicy.SOURCE)
-    @IntDef(flag=true, value = { RESULT_FLAG_OPTION_NOT_HANDLED,
+    @IntDef(flag = true, value = { RESULT_FLAG_OPTION_NOT_HANDLED,
             RESULT_FLAG_ON_LOAD_ITEM_NOT_IMPLEMENTED })
     private @interface ResultFlags { }
 
@@ -291,7 +291,7 @@
                         final ConnectionRecord connection = mConnections.get(b);
                         if (connection == null) {
                             Log.w(TAG, "addSubscription for callback that isn't registered id="
-                                + id);
+                                    + id);
                             return;
                         }
 
@@ -301,7 +301,8 @@
         }
 
         @Override
-        public void removeSubscriptionDeprecated(String id, IMediaBrowserServiceCallbacks callbacks) {
+        public void removeSubscriptionDeprecated(
+                String id, IMediaBrowserServiceCallbacks callbacks) {
             // do-nothing
         }
 
@@ -487,7 +488,7 @@
             @Override
             public void run() {
                 Iterator<ConnectionRecord> iter = mConnections.values().iterator();
-                while (iter.hasNext()){
+                while (iter.hasNext()) {
                     ConnectionRecord connection = iter.next();
                     try {
                         connection.callbacks.onConnect(connection.root.getRootId(), token,
@@ -607,7 +608,7 @@
         final PackageManager pm = getPackageManager();
         final String[] packages = pm.getPackagesForUid(uid);
         final int N = packages.length;
-        for (int i=0; i<N; i++) {
+        for (int i = 0; i < N; i++) {
             if (packages[i].equals(pkg)) {
                 return true;
             }
@@ -648,7 +649,7 @@
         List<Pair<IBinder, Bundle>> callbackList = connection.subscriptions.get(id);
         if (callbackList != null) {
             Iterator<Pair<IBinder, Bundle>> iter = callbackList.iterator();
-            while (iter.hasNext()){
+            while (iter.hasNext()) {
                 if (token == iter.next().first) {
                     removed = true;
                     iter.remove();
@@ -819,8 +820,8 @@
          */
         public static final String EXTRA_SUGGESTED = "android.service.media.extra.SUGGESTED";
 
-        final private String mRootId;
-        final private Bundle mExtras;
+        private final String mRootId;
+        private final Bundle mExtras;
 
         /**
          * Constructs a browser root.
@@ -829,8 +830,8 @@
          */
         public BrowserRoot(@NonNull String rootId, @Nullable Bundle extras) {
             if (rootId == null) {
-                throw new IllegalArgumentException("The root id in BrowserRoot cannot be null. " +
-                        "Use null for BrowserRoot instead.");
+                throw new IllegalArgumentException("The root id in BrowserRoot cannot be null. "
+                        + "Use null for BrowserRoot instead.");
             }
             mRootId = rootId;
             mExtras = extras;
diff --git a/media/packages/MediaCore/Android.bp b/media/packages/MediaCore/Android.bp.bak
similarity index 100%
rename from media/packages/MediaCore/Android.bp
rename to media/packages/MediaCore/Android.bp.bak
diff --git a/native/android/libandroid.map.txt b/native/android/libandroid.map.txt
index 8b45af0..51afbc7 100644
--- a/native/android/libandroid.map.txt
+++ b/native/android/libandroid.map.txt
@@ -231,6 +231,7 @@
     ASurfaceTransaction_setBuffer; # introduced=29
     ASurfaceTransaction_setBufferAlpha; # introduced=29
     ASurfaceTransaction_setBufferTransparency; # introduced=29
+    ASurfaceTransaction_setColor; # introduced=29
     ASurfaceTransaction_setDamageRegion; # introduced=29
     ASurfaceTransaction_setDesiredPresentTime; # introduced=29
     ASurfaceTransaction_setGeometry; # introduced=29
diff --git a/native/android/surface_control.cpp b/native/android/surface_control.cpp
index f0100a9..5fae9d5 100644
--- a/native/android/surface_control.cpp
+++ b/native/android/surface_control.cpp
@@ -44,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);
 }
@@ -431,3 +501,24 @@
 
     transaction->setHdrMetadata(surfaceControl, hdrMetadata);
 }
+
+void ASurfaceTransaction_setColor(ASurfaceTransaction* aSurfaceTransaction,
+                                  ASurfaceControl* aSurfaceControl,
+                                  float r, float g, float b, float alpha,
+                                  ADataSpace dataspace) {
+    CHECK_NOT_NULL(aSurfaceTransaction);
+    CHECK_NOT_NULL(aSurfaceControl);
+
+    sp<SurfaceControl> surfaceControl = ASurfaceControl_to_SurfaceControl(aSurfaceControl);
+    LOG_ALWAYS_FATAL_IF(!isDataSpaceValid(surfaceControl, dataspace), "invalid dataspace");
+    Transaction* transaction = ASurfaceTransaction_to_Transaction(aSurfaceTransaction);
+
+    half3 color;
+    color.r = r;
+    color.g = g;
+    color.b = b;
+
+    transaction->setColor(surfaceControl, color)
+                .setColorAlpha(surfaceControl, alpha)
+                .setColorDataspace(surfaceControl, static_cast<ui::Dataspace>(dataspace));
+}
diff --git a/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/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/SettingsProvider/src/com/android/providers/settings/SettingsProtoDumpUtil.java b/packages/SettingsProvider/src/com/android/providers/settings/SettingsProtoDumpUtil.java
index e843eb4..64d68bf 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,
@@ -716,6 +715,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 +739,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);
@@ -832,6 +834,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 +893,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 +1120,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 +1270,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 +1569,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 +2385,10 @@
                 Settings.Secure.SILENCE_GESTURE,
                 SecureSettingsProto.SILENCE_GESTURE_ENABLED);
 
+        dumpSetting(s, p,
+                Settings.Secure.THEME_CUSTOMIZATION_OVERLAY_PACKAGES,
+                SecureSettingsProto.THEME_CUSTOMIZATION_OVERLAY_PACKAGES);
+
         // Please insert new settings using the same order as in SecureSettingsProto.
         p.end(token);
 
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/res/values/strings.xml b/packages/SystemUI/res/values/strings.xml
index 13d27bb..190bd7a 100644
--- a/packages/SystemUI/res/values/strings.xml
+++ b/packages/SystemUI/res/values/strings.xml
@@ -159,6 +159,12 @@
     <!-- Message of notification shown when trying to enable USB debugging but a secondary user is the current foreground user. -->
     <string name="usb_debugging_secondary_user_message">The user currently signed in to this device can\'t turn on USB debugging. To use this feature, switch to the primary user.</string>
 
+    <!-- Title of USB contaminant presence dialog [CHAR LIMIT=NONE] -->
+    <string name="usb_contaminant_title">USB port disabled</string>
+
+    <!-- Message of USB contaminant presence dialog [CHAR LIMIT=NONE] -->
+    <string name="usb_contaminant_message">To protect your device from liquid or debris, the USB port is disabled and won\u2019t detect any accessories.\n\nYou\u2019ll be notified when it\u2019s safe to use the USB port again.</string>
+
     <!-- Checkbox label for application compatibility mode ON (zooming app to look like it's running
          on a phone).  [CHAR LIMIT=25] -->
     <string name="compat_mode_on">Zoom to fill screen</string>
diff --git a/packages/SystemUI/src/com/android/systemui/bubbles/BubbleView.java b/packages/SystemUI/src/com/android/systemui/bubbles/BubbleView.java
index 7b6e79b..e8432b9 100644
--- a/packages/SystemUI/src/com/android/systemui/bubbles/BubbleView.java
+++ b/packages/SystemUI/src/com/android/systemui/bubbles/BubbleView.java
@@ -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;
@@ -251,7 +252,7 @@
             // 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));
         }
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/NotificationLockscreenUserManagerImpl.java b/packages/SystemUI/src/com/android/systemui/statusbar/NotificationLockscreenUserManagerImpl.java
index d5f4d04..d2ce31d 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/NotificationLockscreenUserManagerImpl.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/NotificationLockscreenUserManagerImpl.java
@@ -49,6 +49,7 @@
 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;
 
@@ -130,8 +131,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) {
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/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/logging/NotificationLogger.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/logging/NotificationLogger.java
index 76d394d..7b94c74 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
@@ -32,7 +32,6 @@
 import com.android.internal.annotations.VisibleForTesting;
 import com.android.internal.statusbar.IStatusBarService;
 import com.android.internal.statusbar.NotificationVisibility;
-import com.android.systemui.statusbar.notification.stack.ExpandableViewState;
 import com.android.systemui.UiOffloadThread;
 import com.android.systemui.statusbar.NotificationListener;
 import com.android.systemui.statusbar.StatusBarStateController;
@@ -40,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;
 
@@ -128,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.
@@ -160,6 +162,40 @@
         }
     };
 
+    /**
+     * Returns the location of the notification referenced by the given {@link NotificationEntry}.
+     */
+    public static NotificationVisibility.NotificationLocation getNotificationLocation(
+            NotificationEntry entry) {
+        ExpandableNotificationRow row = entry.getRow();
+        ExpandableViewState childViewState = row.getViewState();
+
+        if (childViewState == null) {
+            return NotificationVisibility.NotificationLocation.LOCATION_UNKNOWN;
+        }
+        return convertNotificationLocation(childViewState.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,
@@ -363,7 +399,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);
     }
 
     /**
@@ -397,10 +435,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);
         }
 
@@ -416,6 +456,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) {
@@ -460,10 +501,8 @@
             final State stateToBeLogged = new State(state);
             mUiOffloadThread.submit(() -> {
                 try {
-                    mBarService.onNotificationExpansionChanged(
-                            key, stateToBeLogged.mIsUserAction, stateToBeLogged.mIsExpanded,
-                            // TODO (b/120767764): fill in location
-                            ExpandableViewState.LOCATION_UNKNOWN /* notificationLocation */);
+                    mBarService.onNotificationExpansionChanged(key, stateToBeLogged.mIsUserAction,
+                            stateToBeLogged.mIsExpanded, stateToBeLogged.mLocation.ordinal());
                 } catch (RemoteException e) {
                     Log.e(TAG, "Failed to call onNotificationExpansionChanged: ", e);
                 }
@@ -477,6 +516,8 @@
             Boolean mIsExpanded;
             @Nullable
             Boolean mIsVisible;
+            @Nullable
+            NotificationVisibility.NotificationLocation mLocation;
 
             private State() {}
 
@@ -484,10 +525,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/NotificationGutsManager.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationGutsManager.java
index bd1dfb1..5e9f207 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();
 
@@ -389,7 +391,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/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/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/usb/UsbContaminantActivity.java b/packages/SystemUI/src/com/android/systemui/usb/UsbContaminantActivity.java
new file mode 100644
index 0000000..fa4b3fe
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/usb/UsbContaminantActivity.java
@@ -0,0 +1,73 @@
+/*
+ * Copyright (C) 2019 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.usb;
+
+import android.content.DialogInterface;
+import android.content.Intent;
+import android.hardware.usb.ParcelableUsbPort;
+import android.hardware.usb.UsbManager;
+import android.hardware.usb.UsbPort;
+import android.os.Bundle;
+import android.view.Window;
+import android.view.WindowManager;
+
+import com.android.internal.app.AlertActivity;
+import com.android.internal.app.AlertController;
+import com.android.systemui.R;
+
+/**
+ * Activity that alerts the user when contaminant is detected on USB port.
+ */
+public class UsbContaminantActivity extends AlertActivity
+                                  implements DialogInterface.OnClickListener {
+    private static final String TAG = "UsbContaminantActivity";
+
+    private UsbDisconnectedReceiver mDisconnectedReceiver;
+    private UsbPort mUsbPort;
+
+    @Override
+    public void onCreate(Bundle icicle) {
+        Window window = getWindow();
+        window.addSystemFlags(WindowManager.LayoutParams
+                .SYSTEM_FLAG_HIDE_NON_SYSTEM_OVERLAY_WINDOWS);
+        window.setType(WindowManager.LayoutParams.TYPE_SYSTEM_DIALOG);
+
+        super.onCreate(icicle);
+
+        Intent intent = getIntent();
+        ParcelableUsbPort port = intent.getParcelableExtra(UsbManager.EXTRA_PORT);
+        mUsbPort = port.getUsbPort(getSystemService(UsbManager.class));
+
+        final AlertController.AlertParams ap = mAlertParams;
+        ap.mTitle = getString(R.string.usb_contaminant_title);
+        ap.mMessage = getString(R.string.usb_contaminant_message);
+        ap.mPositiveButtonText = getString(android.R.string.ok);
+        ap.mPositiveButtonListener = this;
+
+        setupAlert();
+    }
+
+    @Override
+    public void onWindowAttributesChanged(WindowManager.LayoutParams params) {
+        super.onWindowAttributesChanged(params);
+    }
+
+    @Override
+    public void onClick(DialogInterface dialog, int which) {
+        finish();
+    }
+}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/logging/ExpansionStateLoggerTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/logging/ExpansionStateLoggerTest.java
index 2f6b221..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
@@ -28,7 +28,6 @@
 
 import com.android.internal.statusbar.IStatusBarService;
 import com.android.internal.statusbar.NotificationVisibility;
-import com.android.systemui.statusbar.notification.stack.ExpandableViewState;
 import com.android.systemui.Dependency;
 import com.android.systemui.SysuiTestCase;
 import com.android.systemui.UiOffloadThread;
@@ -73,7 +72,8 @@
 
     @Test
     public void testExpanded() throws RemoteException {
-        mLogger.onExpansionChanged(NOTIFICATION_KEY, false, true);
+        mLogger.onExpansionChanged(NOTIFICATION_KEY, false, true,
+                NotificationVisibility.NotificationLocation.LOCATION_UNKNOWN);
         waitForUiOffloadThread();
 
         verify(mBarService, Mockito.never()).onNotificationExpansionChanged(
@@ -82,7 +82,8 @@
 
     @Test
     public void testVisibleAndNotExpanded() throws RemoteException {
-        mLogger.onExpansionChanged(NOTIFICATION_KEY, true, false);
+        mLogger.onExpansionChanged(NOTIFICATION_KEY, true, false,
+                NotificationVisibility.NotificationLocation.LOCATION_UNKNOWN);
         mLogger.onVisibilityChanged(
                 Collections.singletonList(createNotificationVisibility(NOTIFICATION_KEY, true)),
                 Collections.emptyList());
@@ -94,26 +95,33 @@
 
     @Test
     public void testVisibleAndExpanded() throws RemoteException {
-        mLogger.onExpansionChanged(NOTIFICATION_KEY, true, true);
+        mLogger.onExpansionChanged(NOTIFICATION_KEY, true, true,
+                NotificationVisibility.NotificationLocation.LOCATION_UNKNOWN);
         mLogger.onVisibilityChanged(
                 Collections.singletonList(createNotificationVisibility(NOTIFICATION_KEY, true)),
                 Collections.emptyList());
         waitForUiOffloadThread();
 
         verify(mBarService).onNotificationExpansionChanged(
-                NOTIFICATION_KEY, true, true, ExpandableViewState.LOCATION_UNKNOWN);
+                NOTIFICATION_KEY, true, true,
+                NotificationVisibility.NotificationLocation.LOCATION_UNKNOWN.toMetricsEventEnum());
     }
 
     @Test
     public void testExpandedAndVisible_expandedBeforeVisible() throws RemoteException {
-        mLogger.onExpansionChanged(NOTIFICATION_KEY, false, true);
+        mLogger.onExpansionChanged(NOTIFICATION_KEY, false, true,
+                NotificationVisibility.NotificationLocation.LOCATION_UNKNOWN);
         mLogger.onVisibilityChanged(
-                Collections.singletonList(createNotificationVisibility(NOTIFICATION_KEY, true)),
+                Collections.singletonList(createNotificationVisibility(NOTIFICATION_KEY, true,
+                    NotificationVisibility.NotificationLocation.LOCATION_MAIN_AREA)),
                 Collections.emptyList());
         waitForUiOffloadThread();
 
         verify(mBarService).onNotificationExpansionChanged(
-                NOTIFICATION_KEY, false, true, ExpandableViewState.LOCATION_UNKNOWN);
+                NOTIFICATION_KEY, false, true,
+                // The last location seen should be logged (the one passed to onVisibilityChanged).
+                NotificationVisibility.NotificationLocation.LOCATION_MAIN_AREA.toMetricsEventEnum()
+        );
     }
 
     @Test
@@ -121,11 +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, ExpandableViewState.LOCATION_UNKNOWN);
+                NOTIFICATION_KEY, false, true,
+                // The last location seen should be logged (the one passed to onExpansionChanged).
+                NotificationVisibility.NotificationLocation.LOCATION_FIRST_HEADS_UP.toMetricsEventEnum());
     }
 
     @Test
@@ -133,15 +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, ExpandableViewState.LOCATION_UNKNOWN);
+                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..40d2da9 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);
diff --git a/proto/src/system_messages.proto b/proto/src/system_messages.proto
index 6ff2b35..f128d86 100644
--- a/proto/src/system_messages.proto
+++ b/proto/src/system_messages.proto
@@ -220,6 +220,12 @@
     // Package: android
     NOTE_NETWORK_SUGGESTION_AVAILABLE = 51;
 
+    // Inform the user that the contaminant is detected on the USB port
+    NOTE_USB_CONTAMINANT_DETECTED = 52;
+
+    // Inform that user that the USB port is free of contaminants.
+    NOTE_USB_CONTAMINANT_NOT_DETECTED = 53;
+
     // ADD_NEW_IDS_ABOVE_THIS_LINE
     // Legacy IDs with arbitrary values appear below
     // Legacy IDs existed as stable non-conflicting constants prior to the O release
diff --git a/proto/src/wifi.proto b/proto/src/wifi.proto
index 79b63bc..6656919 100644
--- a/proto/src/wifi.proto
+++ b/proto/src/wifi.proto
@@ -1800,6 +1800,17 @@
 
   // 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;
 }
 
 message WifiUsabilityStats {
diff --git a/services/accessibility/java/com/android/server/accessibility/AccessibilityInputFilter.java b/services/accessibility/java/com/android/server/accessibility/AccessibilityInputFilter.java
index cf08681..bcff4e0 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);
+                addFirstEventHandler(displayId, magnificationGestureHandler);
+                mMagnificationGestureHandler.put(displayId, magnificationGestureHandler);
+            }
+
+            if ((mEnabledFeatures & FLAG_FEATURE_INJECT_MOTION_EVENTS) != 0) {
+                MotionEventInjector injector = new MotionEventInjector(
+                        mContext.getMainLooper());
+                addFirstEventHandler(displayId, injector);
+                // TODO: Need to set MotionEventInjector per display.
+                mAms.setMotionEventInjector(injector);
+                mMotionEventInjector.put(displayId, injector);
+            }
         }
 
         if ((mEnabledFeatures & FLAG_FEATURE_FILTER_KEY_EVENTS) != 0) {
             mKeyboardInterceptor = new KeyboardInterceptor(mAms,
                     LocalServices.getService(WindowManagerPolicy.class));
-            addFirstEventHandler(mKeyboardInterceptor);
+            // Since the display id of KeyEvent always would be -1 and it would be dispatched to
+            // the display with input focus directly, we only need one KeyboardInterceptor for
+            // default display.
+            addFirstEventHandler(Display.DEFAULT_DISPLAY, mKeyboardInterceptor);
         }
     }
 
     /**
-     * Adds an event handler to the event handler chain. The handler is added at the beginning of
-     * the chain.
+     * Adds an event handler to the event handler chain for giving display. The handler is added at
+     * the beginning of the chain.
      *
+     * @param displayId The logical display id.
      * @param handler The handler to be added to the event handlers list.
      */
-    private void addFirstEventHandler(EventStreamTransformation handler) {
-        if (mEventHandler != null) {
-            handler.setNext(mEventHandler);
+    private void addFirstEventHandler(int displayId, EventStreamTransformation handler) {
+        EventStreamTransformation eventHandler = mEventHandler.get(displayId);
+        if (eventHandler != null) {
+            handler.setNext(eventHandler);
         } else {
             handler.setNext(this);
         }
-        mEventHandler = handler;
+        eventHandler = handler;
+        mEventHandler.put(displayId, eventHandler);
+    }
+
+    /**
+     * Adds an event handler to the event handler chain for all displays. The handler is added at
+     * the beginning of the chain.
+     *
+     * @param displayList The list of displays
+     * @param handler The handler to be added to the event handlers list.
+     */
+    private void addFirstEventHandlerForAllDisplays(ArrayList<Display> displayList,
+            EventStreamTransformation handler) {
+        for (int i = 0; i < displayList.size(); i++) {
+            final int displayId = displayList.get(i).getDisplayId();
+            addFirstEventHandler(displayId, handler);
+        }
     }
 
     private void disableFeatures() {
-        if (mMotionEventInjector != null) {
+        for (int i = mMotionEventInjector.size() - 1; i >= 0; i--) {
+            final MotionEventInjector injector = mMotionEventInjector.valueAt(i);
+            // TODO: Need to set MotionEventInjector per display.
             mAms.setMotionEventInjector(null);
-            mMotionEventInjector.onDestroy();
-            mMotionEventInjector = null;
+            if (injector != null) {
+                injector.onDestroy();
+            }
         }
+        mMotionEventInjector.clear();
         if (mAutoclickController != null) {
             mAutoclickController.onDestroy();
             mAutoclickController = null;
         }
-        if (mTouchExplorer != null) {
-            mTouchExplorer.onDestroy();
-            mTouchExplorer = null;
+        for (int i = mTouchExplorer.size() - 1; i >= 0; i--) {
+            final TouchExplorer explorer = mTouchExplorer.valueAt(i);
+            if (explorer != null) {
+                explorer.onDestroy();
+            }
         }
-        if (mMagnificationGestureHandler != null) {
-            mMagnificationGestureHandler.onDestroy();
-            mMagnificationGestureHandler = null;
+        mTouchExplorer.clear();
+        for (int i = mMagnificationGestureHandler.size() - 1; i >= 0; i--) {
+            final MagnificationGestureHandler handler = mMagnificationGestureHandler.valueAt(i);
+            if (handler != null) {
+                handler.onDestroy();
+            }
         }
+        mMagnificationGestureHandler.clear();
         if (mKeyboardInterceptor != null) {
             mKeyboardInterceptor.onDestroy();
             mKeyboardInterceptor = null;
         }
 
-        mEventHandler = null;
+        mEventHandler.clear();
         resetStreamState();
     }
 
@@ -441,41 +521,41 @@
      * whose events should not be handled by a11y event stream transformations.
      */
     private static class EventStreamState {
-        private int mDeviceId;
+        private int mSource;
 
         EventStreamState() {
-            mDeviceId = -1;
+            mSource = -1;
         }
 
         /**
-         * Updates the ID of the device associated with the state. If the ID changes, resets
-         * internal state.
+         * Updates the input source of the device associated with the state. If the source changes,
+         * resets internal state.
          *
-         * @param deviceId Updated input device ID.
-         * @return Whether the device ID has changed.
+         * @param source Updated input source.
+         * @return Whether the input source has changed.
          */
-        public boolean updateDeviceId(int deviceId) {
-            if (mDeviceId == deviceId) {
+        public boolean updateInputSource(int source) {
+            if (mSource == source) {
                 return false;
             }
-            // Reset clears internal state, so make sure it's called before |mDeviceId| is updated.
+            // Reset clears internal state, so make sure it's called before |mSource| is updated.
             reset();
-            mDeviceId = deviceId;
+            mSource = source;
             return true;
         }
 
         /**
-         * @return Whether device ID is valid.
+         * @return Whether input source is valid.
          */
-        public boolean deviceIdValid() {
-            return mDeviceId >= 0;
+        public boolean inputSourceValid() {
+            return mSource >= 0;
         }
 
         /**
          * Resets the event stream state.
          */
         public void reset() {
-            mDeviceId = -1;
+            mSource = -1;
         }
 
         /**
@@ -592,20 +672,19 @@
 
         /*
          * Key events from different devices may be interleaved. For example, the volume up and
-         * down keys can come from different device IDs.
+         * down keys can come from different input sources.
          */
         @Override
-        public boolean updateDeviceId(int deviceId) {
+        public boolean updateInputSource(int deviceId) {
             return false;
         }
 
-        // We manage all device ids simultaneously; there is no concept of validity.
+        // We manage all input source simultaneously; there is no concept of validity.
         @Override
-        public boolean deviceIdValid() {
+        public boolean inputSourceValid() {
             return true;
         }
 
-
         @Override
         final public boolean shouldProcessKeyEvent(KeyEvent event) {
             // For each keyboard device, wait for a down event from a device to start processing
diff --git a/services/accessibility/java/com/android/server/accessibility/AccessibilityManagerService.java b/services/accessibility/java/com/android/server/accessibility/AccessibilityManagerService.java
index dbe86c1..305c53e 100644
--- a/services/accessibility/java/com/android/server/accessibility/AccessibilityManagerService.java
+++ b/services/accessibility/java/com/android/server/accessibility/AccessibilityManagerService.java
@@ -38,6 +38,7 @@
 import android.accessibilityservice.IAccessibilityServiceClient;
 import android.annotation.NonNull;
 import android.annotation.Nullable;
+import android.app.ActivityOptions;
 import android.app.AlertDialog;
 import android.app.AppOpsManager;
 import android.app.PendingIntent;
@@ -208,12 +209,12 @@
 
     private final WindowManagerInternal mWindowManagerService;
 
-    private final DisplayManager mDisplayManager;
-
     private AppWidgetManagerInternal mAppWidgetService;
 
     private final SecurityPolicy mSecurityPolicy;
 
+    private final AccessibilityDisplayListener mA11yDisplayListener;
+
     private final AppOpsManager mAppOpsManager;
 
     private final MainHandler mMainHandler;
@@ -306,12 +307,11 @@
         mAppOpsManager = (AppOpsManager) context.getSystemService(Context.APP_OPS_SERVICE);
         mMainHandler = new MainHandler(mContext.getMainLooper());
         mGlobalActionPerformer = new GlobalActionPerformer(mContext, mWindowManagerService);
-        mDisplayManager = mContext.getSystemService(DisplayManager.class);
+        mA11yDisplayListener = new AccessibilityDisplayListener(mContext, mMainHandler);
 
         registerBroadcastReceivers();
         new AccessibilityContentObserver(mMainHandler).register(
                 context.getContentResolver());
-        registerDisplayListener(mMainHandler);
     }
 
     @Override
@@ -527,30 +527,6 @@
         }, UserHandle.ALL, intentFilter, null, null);
     }
 
-    private void registerDisplayListener(Handler handler) {
-        mDisplayManager.registerDisplayListener(new DisplayManager.DisplayListener() {
-            @Override
-            public void onDisplayAdded(int displayId) {
-                synchronized (mLock) {
-                    UserState userState = getCurrentUserStateLocked();
-                    updateMagnificationLocked(userState);
-                }
-            }
-
-            @Override
-            public void onDisplayRemoved(int displayId) {
-                if (mMagnificationController != null) {
-                    mMagnificationController.onDisplayRemoved(displayId);
-                }
-            }
-
-            @Override
-            public void onDisplayChanged(int displayId) {
-                // do nothing
-            }
-        }, handler);
-    }
-
     @Override
     public long addClient(IAccessibilityManagerClient callback, int userId) {
         synchronized (mLock) {
@@ -937,16 +913,18 @@
     /**
      * Invoked remotely over AIDL by SysUi when the accessibility button within the system's
      * navigation area has been clicked.
+     *
+     * @param displayId The logical display id.
      */
     @Override
-    public void notifyAccessibilityButtonClicked() {
+    public void notifyAccessibilityButtonClicked(int displayId) {
         if (mContext.checkCallingOrSelfPermission(android.Manifest.permission.STATUS_BAR_SERVICE)
                 != PackageManager.PERMISSION_GRANTED) {
             throw new SecurityException("Caller does not hold permission "
                     + android.Manifest.permission.STATUS_BAR_SERVICE);
         }
         synchronized (mLock) {
-            notifyAccessibilityButtonClickedLocked();
+            notifyAccessibilityButtonClickedLocked(displayId);
         }
     }
 
@@ -1249,7 +1227,7 @@
         }
     }
 
-    private void notifyAccessibilityButtonClickedLocked() {
+    private void notifyAccessibilityButtonClickedLocked(int displayId) {
         final UserState state = getCurrentUserStateLocked();
 
         int potentialTargets = state.mIsNavBarMagnificationEnabled ? 1 : 0;
@@ -1266,12 +1244,15 @@
         if (potentialTargets == 1) {
             if (state.mIsNavBarMagnificationEnabled) {
                 mMainHandler.sendMessage(obtainMessage(
-                        AccessibilityManagerService::sendAccessibilityButtonToInputFilter, this));
+                        AccessibilityManagerService::sendAccessibilityButtonToInputFilter, this,
+                        displayId));
                 return;
             } else {
                 for (int i = state.mBoundServices.size() - 1; i >= 0; i--) {
                     final AccessibilityServiceConnection service = state.mBoundServices.get(i);
                     if (service.mRequestAccessibilityButton) {
+                        // TODO(b/120762691): Need to notify each accessibility service if
+                        // accessibility button is clicked per display.
                         service.notifyAccessibilityButtonClickedLocked();
                         return;
                     }
@@ -1281,17 +1262,21 @@
             if (state.mServiceAssignedToAccessibilityButton == null
                     && !state.mIsNavBarMagnificationAssignedToAccessibilityButton) {
                 mMainHandler.sendMessage(obtainMessage(
-                        AccessibilityManagerService::showAccessibilityButtonTargetSelection, this));
+                        AccessibilityManagerService::showAccessibilityButtonTargetSelection, this,
+                        displayId));
             } else if (state.mIsNavBarMagnificationEnabled
                     && state.mIsNavBarMagnificationAssignedToAccessibilityButton) {
                 mMainHandler.sendMessage(obtainMessage(
-                        AccessibilityManagerService::sendAccessibilityButtonToInputFilter, this));
+                        AccessibilityManagerService::sendAccessibilityButtonToInputFilter, this,
+                        displayId));
                 return;
             } else {
                 for (int i = state.mBoundServices.size() - 1; i >= 0; i--) {
                     final AccessibilityServiceConnection service = state.mBoundServices.get(i);
                     if (service.mRequestAccessibilityButton && (service.mComponentName.equals(
                             state.mServiceAssignedToAccessibilityButton))) {
+                        // TODO(b/120762691): Need to notify each accessibility service if
+                        // accessibility button is clicked per display.
                         service.notifyAccessibilityButtonClickedLocked();
                         return;
                     }
@@ -1299,22 +1284,24 @@
             }
             // The user may have turned off the assigned service or feature
             mMainHandler.sendMessage(obtainMessage(
-                    AccessibilityManagerService::showAccessibilityButtonTargetSelection, this));
+                    AccessibilityManagerService::showAccessibilityButtonTargetSelection, this,
+                    displayId));
         }
     }
 
-    private void sendAccessibilityButtonToInputFilter() {
+    private void sendAccessibilityButtonToInputFilter(int displayId) {
         synchronized (mLock) {
             if (mHasInputFilter && mInputFilter != null) {
-                mInputFilter.notifyAccessibilityButtonClicked();
+                mInputFilter.notifyAccessibilityButtonClicked(displayId);
             }
         }
     }
 
-    private void showAccessibilityButtonTargetSelection() {
+    private void showAccessibilityButtonTargetSelection(int displayId) {
         Intent intent = new Intent(AccessibilityManager.ACTION_CHOOSE_ACCESSIBILITY_BUTTON);
         intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK | Intent.FLAG_ACTIVITY_CLEAR_TASK);
-        mContext.startActivityAsUser(intent, UserHandle.of(mCurrentUserId));
+        final Bundle bundle = ActivityOptions.makeBasic().setLaunchDisplayId(displayId).toBundle();
+        mContext.startActivityAsUser(intent, bundle, UserHandle.of(mCurrentUserId));
     }
 
     private void notifyAccessibilityButtonVisibilityChangedLocked(boolean available) {
@@ -2226,32 +2213,22 @@
             return;
         }
 
-        // register all display if global magnification is enabled.
-        final Display[] displays = mDisplayManager.getDisplays();
+        // Get all valid displays and register them if global magnification is enabled.
+        // We would skip overlay display because it uses overlay window to simulate secondary
+        // displays in one display. It's not a real display and there's no input events for it.
+        final ArrayList<Display> displays = getValidDisplayList();
         if (userState.mIsDisplayMagnificationEnabled
                 || userState.mIsNavBarMagnificationEnabled) {
-            for (int i = 0; i < displays.length; i++) {
-                final Display display = displays[i];
-                // Overlay display uses overlay window to simulate secondary displays in
-                // one display. It's not a real display and there's no input events for it.
-                // We should ignore it.
-                if (display.getType() == Display.TYPE_OVERLAY) {
-                    continue;
-                }
+            for (int i = 0; i < displays.size(); i++) {
+                final Display display = displays.get(i);
                 getMagnificationController().register(display.getDisplayId());
             }
             return;
         }
 
-        // register if display has listening magnification services.
-        for (int i = 0; i < displays.length; i++) {
-            final Display display = displays[i];
-            // Overlay display uses overlay window to simulate secondary displays in
-            // one display. It's not a real display and there's no input events for it.
-            // We should ignore it.
-            if (display.getType() == Display.TYPE_OVERLAY) {
-                continue;
-            }
+        // Register if display has listening magnification services.
+        for (int i = 0; i < displays.size(); i++) {
+            final Display display = displays.get(i);
             final int displayId = display.getDisplayId();
             if (userHasListeningMagnificationServicesLocked(userState, displayId)) {
                 getMagnificationController().register(displayId);
@@ -3812,6 +3789,92 @@
         }
     }
 
+    /**
+     * Gets all currently valid logical displays.
+     *
+     * @return An array list containing all valid logical displays.
+     */
+    public ArrayList<Display> getValidDisplayList() {
+        return mA11yDisplayListener.getValidDisplayList();
+    }
+
+    /**
+     * A Utility class to handle display state.
+     */
+    public class AccessibilityDisplayListener implements DisplayManager.DisplayListener {
+        private final DisplayManager mDisplayManager;
+        private final ArrayList<Display> mDisplaysList = new ArrayList<>();
+
+        AccessibilityDisplayListener(Context context, MainHandler handler) {
+            mDisplayManager = (DisplayManager) context.getSystemService(Context.DISPLAY_SERVICE);
+            mDisplayManager.registerDisplayListener(this, handler);
+            initializeDisplayList();
+        }
+
+        ArrayList<Display> getValidDisplayList() {
+            synchronized (mLock) {
+                return mDisplaysList;
+            }
+        }
+
+        private void initializeDisplayList() {
+            final Display[] displays = mDisplayManager.getDisplays();
+            synchronized (mLock) {
+                mDisplaysList.clear();
+                for (int i = 0; i < displays.length; i++) {
+                    // Exclude overlay virtual displays. The display list is for A11yInputFilter
+                    // to create event handler per display. The events should be handled by the
+                    // display which is overlaid by it.
+                    final Display display = displays[i];
+                    if (display.getType() == Display.TYPE_OVERLAY) {
+                        continue;
+                    }
+                    mDisplaysList.add(display);
+                }
+            }
+        }
+
+        @Override
+        public void onDisplayAdded(int displayId) {
+            final Display display = mDisplayManager.getDisplay(displayId);
+            if (display == null || display.getType() == Display.TYPE_OVERLAY) {
+                return;
+            }
+
+            synchronized (mLock) {
+                mDisplaysList.add(display);
+                if (mInputFilter != null) {
+                    mInputFilter.onDisplayChanged();
+                }
+                UserState userState = getCurrentUserStateLocked();
+                updateMagnificationLocked(userState);
+            }
+        }
+
+        @Override
+        public void onDisplayRemoved(int displayId) {
+            synchronized (mLock) {
+                for (int i = 0; i < mDisplaysList.size(); i++) {
+                    if (mDisplaysList.get(i).getDisplayId() == displayId) {
+                        mDisplaysList.remove(i);
+                        break;
+                    }
+                }
+                if (mInputFilter != null) {
+                    mInputFilter.onDisplayChanged();
+                }
+            }
+            if (mMagnificationController != null) {
+                mMagnificationController.onDisplayRemoved(displayId);
+            }
+        }
+
+        @Override
+        public void onDisplayChanged(int displayId) {
+            /* do nothing */
+        }
+    }
+
     /** Represents an {@link AccessibilityManager} */
     class Client {
         final IAccessibilityManagerClient mCallback;
diff --git a/services/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 9f7d33f..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,
@@ -2559,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) {
@@ -2574,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() {
@@ -2594,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;
         }
 
@@ -2614,7 +2611,7 @@
             Slog.v(TAG, "calling Augmented Autofill Service ("
                     + remoteService.getComponentName().toShortString() + ") on view "
                     + mCurrentViewId + " using suggestion mode "
-                    + smartSuggestionFlagsToString(mode)
+                    + getSmartSuggestionModeToString(mode)
                     + " when server returned null for session " + this.id);
         }
 
diff --git a/services/contentcapture/java/com/android/server/contentcapture/ContentCaptureManagerService.java b/services/contentcapture/java/com/android/server/contentcapture/ContentCaptureManagerService.java
index e4bbcd6..844096d 100644
--- a/services/contentcapture/java/com/android/server/contentcapture/ContentCaptureManagerService.java
+++ b/services/contentcapture/java/com/android/server/contentcapture/ContentCaptureManagerService.java
@@ -35,6 +35,7 @@
 import android.util.LocalLog;
 import android.util.Slog;
 import android.view.contentcapture.IContentCaptureManager;
+import android.view.contentcapture.UserDataRemovalRequest;
 
 import com.android.internal.annotations.GuardedBy;
 import com.android.internal.os.IResultReceiver;
@@ -224,6 +225,15 @@
         }
 
         @Override
+        public void removeUserData(@UserIdInt int userId, @NonNull UserDataRemovalRequest request) {
+            Preconditions.checkNotNull(request);
+            synchronized (mLock) {
+                final ContentCapturePerUserService service = getServiceForUserLocked(userId);
+                service.removeUserDataLocked(request);
+            }
+        }
+
+        @Override
         public void dump(FileDescriptor fd, PrintWriter pw, String[] args) {
             if (!DumpUtils.checkDumpPermission(getContext(), TAG, pw)) return;
 
diff --git a/services/contentcapture/java/com/android/server/contentcapture/ContentCapturePerUserService.java b/services/contentcapture/java/com/android/server/contentcapture/ContentCapturePerUserService.java
index 8d2c79b..bc0e19a 100644
--- a/services/contentcapture/java/com/android/server/contentcapture/ContentCapturePerUserService.java
+++ b/services/contentcapture/java/com/android/server/contentcapture/ContentCapturePerUserService.java
@@ -28,6 +28,7 @@
 import android.Manifest;
 import android.annotation.NonNull;
 import android.annotation.UserIdInt;
+import android.app.ActivityManagerInternal;
 import android.app.AppGlobals;
 import android.app.assist.AssistContent;
 import android.app.assist.AssistStructure;
@@ -35,18 +36,22 @@
 import android.content.pm.PackageManager;
 import android.content.pm.PackageManager.NameNotFoundException;
 import android.content.pm.ServiceInfo;
+import android.os.Binder;
 import android.os.Bundle;
 import android.os.IBinder;
 import android.os.RemoteException;
+import android.os.UserHandle;
 import android.service.contentcapture.ContentCaptureService;
 import android.service.contentcapture.IContentCaptureServiceCallback;
 import android.service.contentcapture.SnapshotData;
 import android.util.ArrayMap;
 import android.util.Log;
 import android.util.Slog;
+import android.view.contentcapture.UserDataRemovalRequest;
 
 import com.android.internal.annotations.GuardedBy;
 import com.android.internal.os.IResultReceiver;
+import com.android.server.LocalServices;
 import com.android.server.contentcapture.RemoteContentCaptureService.ContentCaptureServiceCallbacks;
 import com.android.server.infra.AbstractPerUserSystemService;
 
@@ -249,6 +254,39 @@
     }
 
     @GuardedBy("mLock")
+    public void removeUserDataLocked(@NonNull UserDataRemovalRequest request) {
+        if (!isEnabledLocked()) {
+            return;
+        }
+        assertCallerLocked(request.getPackageName());
+        mRemoteService.onUserDataRemovalRequest(request);
+    }
+
+    /**
+     * Asserts the component is owned by the caller.
+     */
+    @GuardedBy("mLock")
+    private void assertCallerLocked(@NonNull String packageName) {
+        final PackageManager pm = getContext().getPackageManager();
+        final int callingUid = Binder.getCallingUid();
+        final int packageUid;
+        try {
+            packageUid = pm.getPackageUidAsUser(packageName, UserHandle.getCallingUserId());
+        } catch (NameNotFoundException e) {
+            throw new SecurityException("Could not verify UID for " + packageName);
+        }
+        if (callingUid != packageUid && !LocalServices.getService(ActivityManagerInternal.class)
+                .hasRunningActivity(callingUid, packageName)) {
+            final String[] packages = pm.getPackagesForUid(callingUid);
+            final String callingPackage = packages != null ? packages[0] : "uid-" + callingUid;
+            Slog.w(TAG, "App (package=" + callingPackage + ", UID=" + callingUid
+                    + ") passed package (" + packageName + ") owned by UID " + packageUid);
+
+            throw new SecurityException("Invalid package: " + packageName);
+        }
+    }
+
+    @GuardedBy("mLock")
     public boolean sendActivityAssistDataLocked(@NonNull IBinder activityToken,
             @NonNull Bundle data) {
         final String id = getSessionId(activityToken);
diff --git a/services/contentcapture/java/com/android/server/contentcapture/RemoteContentCaptureService.java b/services/contentcapture/java/com/android/server/contentcapture/RemoteContentCaptureService.java
index 12742ca..54eea5d 100644
--- a/services/contentcapture/java/com/android/server/contentcapture/RemoteContentCaptureService.java
+++ b/services/contentcapture/java/com/android/server/contentcapture/RemoteContentCaptureService.java
@@ -26,6 +26,7 @@
 import android.text.format.DateUtils;
 import android.util.Slog;
 import android.view.contentcapture.ContentCaptureContext;
+import android.view.contentcapture.UserDataRemovalRequest;
 
 import com.android.internal.infra.AbstractMultiplePendingRequestsRemoteService;
 import com.android.internal.os.IResultReceiver;
@@ -108,6 +109,13 @@
         scheduleAsyncRequest((s) -> s.onActivitySnapshot(sessionId, snapshotData));
     }
 
+    /**
+     * Called by {@link ContentCaptureServerSession} to request removal of user data.
+     */
+    public void onUserDataRemovalRequest(@NonNull UserDataRemovalRequest request) {
+        scheduleAsyncRequest((s) -> s.onUserDataRemovalRequest(request));
+    }
+
     public interface ContentCaptureServiceCallbacks
             extends VultureCallback<RemoteContentCaptureService> {
         // NOTE: so far we don't need to notify the callback implementation
diff --git a/services/core/java/com/android/server/AlarmManagerService.java b/services/core/java/com/android/server/AlarmManagerService.java
index fcd136c..e3dcb7d 100644
--- a/services/core/java/com/android/server/AlarmManagerService.java
+++ b/services/core/java/com/android/server/AlarmManagerService.java
@@ -77,6 +77,7 @@
 import android.util.ArraySet;
 import android.util.KeyValueListParser;
 import android.util.Log;
+import android.util.LongArrayQueue;
 import android.util.NtpTrustedTime;
 import android.util.Pair;
 import android.util.Slog;
@@ -91,6 +92,7 @@
 import com.android.internal.annotations.VisibleForTesting;
 import com.android.internal.util.ArrayUtils;
 import com.android.internal.util.DumpUtils;
+import com.android.internal.util.IndentingPrintWriter;
 import com.android.internal.util.LocalLog;
 import com.android.internal.util.StatLogger;
 import com.android.server.AppStateTracker.Listener;
@@ -145,6 +147,7 @@
     static final String TIMEZONE_PROPERTY = "persist.sys.timezone";
 
     static final int TICK_HISTORY_DEPTH = 10;
+    static final long MILLIS_IN_DAY = 24 * 60 * 60 * 1000;
 
     // Indices into the APP_STANDBY_MIN_DELAYS and KEYS_APP_STANDBY_DELAY arrays
     static final int ACTIVE_INDEX = 0;
@@ -195,6 +198,7 @@
     ArrayList<Alarm> mPendingNonWakeupAlarms = new ArrayList<>();
     ArrayList<InFlight> mInFlight = new ArrayList<>();
     AlarmHandler mHandler;
+    AppWakeupHistory mAppWakeupHistory;
     ClockReceiver mClockReceiver;
     final DeliveryTracker mDeliveryTracker = new DeliveryTracker();
     Intent mTimeTickIntent;
@@ -277,7 +281,91 @@
 
     private AppStateTracker mAppStateTracker;
     private boolean mAppStandbyParole;
-    private ArrayMap<Pair<String, Integer>, Long> mLastAlarmDeliveredForPackage = new ArrayMap<>();
+
+    /**
+     * A rolling window history of previous times when an alarm was sent to a package.
+     */
+    private static class AppWakeupHistory {
+        private ArrayMap<Pair<String, Integer>, LongArrayQueue> mPackageHistory =
+                new ArrayMap<>();
+        private long mWindowSize;
+
+        AppWakeupHistory(long windowSize) {
+            mWindowSize = windowSize;
+        }
+
+        void recordAlarmForPackage(String packageName, int userId, long nowElapsed) {
+            final Pair<String, Integer> packageUser = Pair.create(packageName, userId);
+            LongArrayQueue history = mPackageHistory.get(packageUser);
+            if (history == null) {
+                history = new LongArrayQueue();
+                mPackageHistory.put(packageUser, history);
+            }
+            if (history.size() == 0 || history.peekLast() < nowElapsed) {
+                history.addLast(nowElapsed);
+            }
+            snapToWindow(history);
+        }
+
+        void removeForUser(int userId) {
+            for (int i = mPackageHistory.size() - 1; i >= 0; i--) {
+                final Pair<String, Integer> packageUserKey = mPackageHistory.keyAt(i);
+                if (packageUserKey.second == userId) {
+                    mPackageHistory.removeAt(i);
+                }
+            }
+        }
+
+        void removeForPackage(String packageName, int userId) {
+            final Pair<String, Integer> packageUser = Pair.create(packageName, userId);
+            mPackageHistory.remove(packageUser);
+        }
+
+        private void snapToWindow(LongArrayQueue history) {
+            while (history.peekFirst() + mWindowSize < history.peekLast()) {
+                history.removeFirst();
+            }
+        }
+
+        int getTotalWakeupsInWindow(String packageName, int userId) {
+            final LongArrayQueue history = mPackageHistory.get(Pair.create(packageName, userId));
+            return (history == null) ? 0 : history.size();
+        }
+
+        long getLastWakeupForPackage(String packageName, int userId, int positionFromEnd) {
+            final LongArrayQueue history = mPackageHistory.get(Pair.create(packageName, userId));
+            if (history == null) {
+                return 0;
+            }
+            final int i = history.size() - positionFromEnd;
+            return (i < 0) ? 0 : history.get(i);
+        }
+
+        void dump(PrintWriter pw, String prefix, long nowElapsed) {
+            dump(new IndentingPrintWriter(pw, "  ").setIndent(prefix), nowElapsed);
+        }
+
+        void dump(IndentingPrintWriter pw, long nowElapsed) {
+            pw.println("App Alarm history:");
+            pw.increaseIndent();
+            for (int i = 0; i < mPackageHistory.size(); i++) {
+                final Pair<String, Integer> packageUser = mPackageHistory.keyAt(i);
+                final LongArrayQueue timestamps = mPackageHistory.valueAt(i);
+                pw.print(packageUser.first);
+                pw.print(", u");
+                pw.print(packageUser.second);
+                pw.print(": ");
+                // limit dumping to a max of 100 values
+                final int lastIdx = Math.max(0, timestamps.size() - 100);
+                for (int j = timestamps.size() - 1; j >= lastIdx; j--) {
+                    TimeUtils.formatDuration(timestamps.get(j), nowElapsed, pw);
+                    pw.print(", ");
+                }
+                pw.println();
+            }
+            pw.decreaseIndent();
+        }
+    }
 
     /**
      * All times are in milliseconds. These constants are kept synchronized with the system
@@ -302,6 +390,17 @@
                 = "allow_while_idle_whitelist_duration";
         @VisibleForTesting
         static final String KEY_LISTENER_TIMEOUT = "listener_timeout";
+        @VisibleForTesting
+        static final String KEY_APP_STANDBY_QUOTAS_ENABLED = "app_standby_quotas_enabled";
+        private static final String KEY_APP_STANDBY_WINDOW = "app_standby_window";
+        @VisibleForTesting
+        final String[] KEYS_APP_STANDBY_QUOTAS = {
+                "standby_active_quota",
+                "standby_working_quota",
+                "standby_frequent_quota",
+                "standby_rare_quota",
+                "standby_never_quota",
+        };
 
         // Keys for specifying throttling delay based on app standby bucketing
         private final String[] KEYS_APP_STANDBY_DELAY = {
@@ -319,6 +418,18 @@
         private static final long DEFAULT_ALLOW_WHILE_IDLE_LONG_TIME = 9*60*1000;
         private static final long DEFAULT_ALLOW_WHILE_IDLE_WHITELIST_DURATION = 10*1000;
         private static final long DEFAULT_LISTENER_TIMEOUT = 5 * 1000;
+        private static final boolean DEFAULT_APP_STANDBY_QUOTAS_ENABLED = true;
+        private static final long DEFAULT_APP_STANDBY_WINDOW = 60 * 60 * 1000;  // 1 hr
+        /**
+         * Max number of times an app can receive alarms in {@link #APP_STANDBY_WINDOW}
+         */
+        private final int[] DEFAULT_APP_STANDBY_QUOTAS = {
+                720,    // Active
+                10,     // Working
+                2,      // Frequent
+                1,      // Rare
+                0       // Never
+        };
         private final long[] DEFAULT_APP_STANDBY_DELAYS = {
                 0,                       // Active
                 6 * 60_000,              // Working
@@ -348,8 +459,11 @@
 
         // Direct alarm listener callback timeout
         public long LISTENER_TIMEOUT = DEFAULT_LISTENER_TIMEOUT;
+        public boolean APP_STANDBY_QUOTAS_ENABLED = DEFAULT_APP_STANDBY_QUOTAS_ENABLED;
 
+        public long APP_STANDBY_WINDOW = DEFAULT_APP_STANDBY_WINDOW;
         public long[] APP_STANDBY_MIN_DELAYS = new long[DEFAULT_APP_STANDBY_DELAYS.length];
+        public int[] APP_STANDBY_QUOTAS = new int[DEFAULT_APP_STANDBY_QUOTAS.length];
 
         private ContentResolver mResolver;
         private final KeyValueListParser mParser = new KeyValueListParser(',');
@@ -409,48 +523,90 @@
                         DEFAULT_APP_STANDBY_DELAYS[ACTIVE_INDEX]);
                 for (int i = WORKING_INDEX; i < KEYS_APP_STANDBY_DELAY.length; i++) {
                     APP_STANDBY_MIN_DELAYS[i] = mParser.getDurationMillis(KEYS_APP_STANDBY_DELAY[i],
-                            Math.max(APP_STANDBY_MIN_DELAYS[i-1], DEFAULT_APP_STANDBY_DELAYS[i]));
+                            Math.max(APP_STANDBY_MIN_DELAYS[i - 1], DEFAULT_APP_STANDBY_DELAYS[i]));
+                }
+
+                APP_STANDBY_QUOTAS_ENABLED = mParser.getBoolean(KEY_APP_STANDBY_QUOTAS_ENABLED,
+                        DEFAULT_APP_STANDBY_QUOTAS_ENABLED);
+
+                APP_STANDBY_WINDOW = mParser.getLong(KEY_APP_STANDBY_WINDOW,
+                        DEFAULT_APP_STANDBY_WINDOW);
+                if (APP_STANDBY_WINDOW > DEFAULT_APP_STANDBY_WINDOW) {
+                    Slog.w(TAG, "Cannot exceed the app_standby_window size of "
+                            + DEFAULT_APP_STANDBY_WINDOW);
+                    APP_STANDBY_WINDOW = DEFAULT_APP_STANDBY_WINDOW;
+                } else if (APP_STANDBY_WINDOW < DEFAULT_APP_STANDBY_WINDOW) {
+                    // Not recommended outside of testing.
+                    Slog.w(TAG, "Using a non-default app_standby_window of " + APP_STANDBY_WINDOW);
+                }
+
+                APP_STANDBY_QUOTAS[ACTIVE_INDEX] = mParser.getInt(
+                        KEYS_APP_STANDBY_QUOTAS[ACTIVE_INDEX],
+                        DEFAULT_APP_STANDBY_QUOTAS[ACTIVE_INDEX]);
+                for (int i = WORKING_INDEX; i < KEYS_APP_STANDBY_QUOTAS.length; i++) {
+                    APP_STANDBY_QUOTAS[i] = mParser.getInt(KEYS_APP_STANDBY_QUOTAS[i],
+                            Math.min(APP_STANDBY_QUOTAS[i - 1], DEFAULT_APP_STANDBY_QUOTAS[i]));
                 }
                 updateAllowWhileIdleWhitelistDurationLocked();
             }
         }
 
-        void dump(PrintWriter pw) {
-            pw.println("  Settings:");
+        void dump(PrintWriter pw, String prefix) {
+            dump(new IndentingPrintWriter(pw, "  ").setIndent(prefix));
+        }
 
-            pw.print("    "); pw.print(KEY_MIN_FUTURITY); pw.print("=");
+        void dump(IndentingPrintWriter pw) {
+            pw.println("Settings:");
+
+            pw.increaseIndent();
+
+            pw.print(KEY_MIN_FUTURITY); pw.print("=");
             TimeUtils.formatDuration(MIN_FUTURITY, pw);
             pw.println();
 
-            pw.print("    "); pw.print(KEY_MIN_INTERVAL); pw.print("=");
+            pw.print(KEY_MIN_INTERVAL); pw.print("=");
             TimeUtils.formatDuration(MIN_INTERVAL, pw);
             pw.println();
 
-            pw.print("    "); pw.print(KEY_MAX_INTERVAL); pw.print("=");
+            pw.print(KEY_MAX_INTERVAL); pw.print("=");
             TimeUtils.formatDuration(MAX_INTERVAL, pw);
             pw.println();
 
-            pw.print("    "); pw.print(KEY_LISTENER_TIMEOUT); pw.print("=");
+            pw.print(KEY_LISTENER_TIMEOUT); pw.print("=");
             TimeUtils.formatDuration(LISTENER_TIMEOUT, pw);
             pw.println();
 
-            pw.print("    "); pw.print(KEY_ALLOW_WHILE_IDLE_SHORT_TIME); pw.print("=");
+            pw.print(KEY_ALLOW_WHILE_IDLE_SHORT_TIME); pw.print("=");
             TimeUtils.formatDuration(ALLOW_WHILE_IDLE_SHORT_TIME, pw);
             pw.println();
 
-            pw.print("    "); pw.print(KEY_ALLOW_WHILE_IDLE_LONG_TIME); pw.print("=");
+            pw.print(KEY_ALLOW_WHILE_IDLE_LONG_TIME); pw.print("=");
             TimeUtils.formatDuration(ALLOW_WHILE_IDLE_LONG_TIME, pw);
             pw.println();
 
-            pw.print("    "); pw.print(KEY_ALLOW_WHILE_IDLE_WHITELIST_DURATION); pw.print("=");
+            pw.print(KEY_ALLOW_WHILE_IDLE_WHITELIST_DURATION); pw.print("=");
             TimeUtils.formatDuration(ALLOW_WHILE_IDLE_WHITELIST_DURATION, pw);
             pw.println();
 
             for (int i = 0; i < KEYS_APP_STANDBY_DELAY.length; i++) {
-                pw.print("    "); pw.print(KEYS_APP_STANDBY_DELAY[i]); pw.print("=");
+                pw.print(KEYS_APP_STANDBY_DELAY[i]); pw.print("=");
                 TimeUtils.formatDuration(APP_STANDBY_MIN_DELAYS[i], pw);
                 pw.println();
             }
+
+            pw.print(KEY_APP_STANDBY_QUOTAS_ENABLED); pw.print("=");
+            pw.println(APP_STANDBY_QUOTAS_ENABLED);
+
+            pw.print(KEY_APP_STANDBY_WINDOW); pw.print("=");
+            TimeUtils.formatDuration(APP_STANDBY_WINDOW, pw);
+            pw.println();
+
+            for (int i = 0; i < KEYS_APP_STANDBY_QUOTAS.length; i++) {
+                pw.print(KEYS_APP_STANDBY_QUOTAS[i]); pw.print("=");
+                pw.println(APP_STANDBY_QUOTAS[i]);
+            }
+
+            pw.decreaseIndent();
         }
 
         void dumpProto(ProtoOutputStream proto, long fieldId) {
@@ -925,7 +1081,7 @@
                 if (targetPackages != null && !targetPackages.contains(packageUser)) {
                     continue;
                 }
-                if (adjustDeliveryTimeBasedOnStandbyBucketLocked(alarm)) {
+                if (adjustDeliveryTimeBasedOnBucketLocked(alarm)) {
                     batch.remove(alarm);
                     rescheduledAlarms.add(alarm);
                 }
@@ -1300,6 +1456,7 @@
         synchronized (mLock) {
             mHandler = new AlarmHandler();
             mConstants = new Constants(mHandler);
+            mAppWakeupHistory = new AppWakeupHistory(Constants.DEFAULT_APP_STANDBY_WINDOW);
 
             mNextWakeup = mNextNonWakeup = 0;
 
@@ -1583,6 +1740,27 @@
     }
 
     /**
+     * Returns the maximum alarms that an app in the specified bucket can receive in a rolling time
+     * window given by {@link Constants#APP_STANDBY_WINDOW}
+     */
+    @VisibleForTesting
+    int getQuotaForBucketLocked(int bucket) {
+        final int index;
+        if (bucket <= UsageStatsManager.STANDBY_BUCKET_ACTIVE) {
+            index = ACTIVE_INDEX;
+        } else if (bucket <= UsageStatsManager.STANDBY_BUCKET_WORKING_SET) {
+            index = WORKING_INDEX;
+        } else if (bucket <= UsageStatsManager.STANDBY_BUCKET_FREQUENT) {
+            index = FREQUENT_INDEX;
+        } else if (bucket < UsageStatsManager.STANDBY_BUCKET_NEVER) {
+            index = RARE_INDEX;
+        } else {
+            index = NEVER_INDEX;
+        }
+        return mConstants.APP_STANDBY_QUOTAS[index];
+    }
+
+    /**
      * Return the minimum time that should elapse before an app in the specified bucket
      * can receive alarms again
      */
@@ -1608,7 +1786,7 @@
      * @param alarm The alarm to adjust
      * @return true if the alarm delivery time was updated.
      */
-    private boolean adjustDeliveryTimeBasedOnStandbyBucketLocked(Alarm alarm) {
+    private boolean adjustDeliveryTimeBasedOnBucketLocked(Alarm alarm) {
         if (isExemptFromAppStandby(alarm)) {
             return false;
         }
@@ -1629,18 +1807,49 @@
         final int standbyBucket = mUsageStatsManagerInternal.getAppStandbyBucket(
                 sourcePackage, sourceUserId, mInjector.getElapsedRealtime());
 
-        final Pair<String, Integer> packageUser = Pair.create(sourcePackage, sourceUserId);
-        final long lastElapsed = mLastAlarmDeliveredForPackage.getOrDefault(packageUser, 0L);
-        if (lastElapsed > 0) {
-            final long minElapsed = lastElapsed + getMinDelayForBucketLocked(standbyBucket);
-            if (alarm.expectedWhenElapsed < minElapsed) {
-                alarm.whenElapsed = alarm.maxWhenElapsed = minElapsed;
-            } else {
-                // app is now eligible to run alarms at the originally requested window.
+        if (mConstants.APP_STANDBY_QUOTAS_ENABLED) {
+            // Quota deferring implementation:
+            final int wakeupsInWindow = mAppWakeupHistory.getTotalWakeupsInWindow(sourcePackage,
+                    sourceUserId);
+            final int quotaForBucket = getQuotaForBucketLocked(standbyBucket);
+            boolean deferred = false;
+            if (wakeupsInWindow >= quotaForBucket) {
+                final long minElapsed;
+                if (quotaForBucket <= 0) {
+                    // Just keep deferring for a day till the quota changes
+                    minElapsed = mInjector.getElapsedRealtime() + MILLIS_IN_DAY;
+                } else {
+                    // Suppose the quota for window was q, and the qth last delivery time for this
+                    // package was t(q) then the next delivery must be after t(q) + <window_size>
+                    final long t = mAppWakeupHistory.getLastWakeupForPackage(sourcePackage,
+                            sourceUserId, quotaForBucket);
+                    minElapsed = t + 1 + mConstants.APP_STANDBY_WINDOW;
+                }
+                if (alarm.expectedWhenElapsed < minElapsed) {
+                    alarm.whenElapsed = alarm.maxWhenElapsed = minElapsed;
+                    deferred = true;
+                }
+            }
+            if (!deferred) {
                 // Restore original requirements in case they were changed earlier.
                 alarm.whenElapsed = alarm.expectedWhenElapsed;
                 alarm.maxWhenElapsed = alarm.expectedMaxWhenElapsed;
             }
+        } else {
+            // Minimum delay deferring implementation:
+            final long lastElapsed = mAppWakeupHistory.getLastWakeupForPackage(sourcePackage,
+                    sourceUserId, 1);
+            if (lastElapsed > 0) {
+                final long minElapsed = lastElapsed + getMinDelayForBucketLocked(standbyBucket);
+                if (alarm.expectedWhenElapsed < minElapsed) {
+                    alarm.whenElapsed = alarm.maxWhenElapsed = minElapsed;
+                } else {
+                    // app is now eligible to run alarms at the originally requested window.
+                    // Restore original requirements in case they were changed earlier.
+                    alarm.whenElapsed = alarm.expectedWhenElapsed;
+                    alarm.maxWhenElapsed = alarm.expectedMaxWhenElapsed;
+                }
+            }
         }
         return (oldWhenElapsed != alarm.whenElapsed || oldMaxWhenElapsed != alarm.maxWhenElapsed);
     }
@@ -1696,7 +1905,7 @@
                 mAllowWhileIdleDispatches.add(ent);
             }
         }
-        adjustDeliveryTimeBasedOnStandbyBucketLocked(a);
+        adjustDeliveryTimeBasedOnBucketLocked(a);
         insertAndBatchAlarmLocked(a);
 
         if (a.alarmClock != null) {
@@ -1915,7 +2124,7 @@
     void dumpImpl(PrintWriter pw) {
         synchronized (mLock) {
             pw.println("Current Alarm Manager state:");
-            mConstants.dump(pw);
+            mConstants.dump(pw, "  ");
             pw.println();
 
             if (mAppStateTracker != null) {
@@ -2065,14 +2274,7 @@
                 pw.println("    none");
             }
 
-            pw.println("  mLastAlarmDeliveredForPackage:");
-            for (int i = 0; i < mLastAlarmDeliveredForPackage.size(); i++) {
-                Pair<String, Integer> packageUser = mLastAlarmDeliveredForPackage.keyAt(i);
-                pw.print("    Package " + packageUser.first + ", User " + packageUser.second + ":");
-                TimeUtils.formatDuration(mLastAlarmDeliveredForPackage.valueAt(i), nowELAPSED, pw);
-                pw.println();
-            }
-            pw.println();
+            mAppWakeupHistory.dump(pw, "  ", nowELAPSED);
 
             if (mPendingIdleUntil != null || mPendingWhileIdleAlarms.size() > 0) {
                 pw.println();
@@ -3862,6 +4064,7 @@
             obtainMessage(REMOVE_FOR_STOPPED, uid, 0).sendToTarget();
         }
 
+        @Override
         public void handleMessage(Message msg) {
             switch (msg.what) {
                 case ALARM_EVENT: {
@@ -4030,64 +4233,57 @@
         public void onReceive(Context context, Intent intent) {
             final int uid = intent.getIntExtra(Intent.EXTRA_UID, -1);
             synchronized (mLock) {
-                String action = intent.getAction();
                 String pkgList[] = null;
-                if (Intent.ACTION_QUERY_PACKAGE_RESTART.equals(action)) {
-                    pkgList = intent.getStringArrayExtra(Intent.EXTRA_PACKAGES);
-                    for (String packageName : pkgList) {
-                        if (lookForPackageLocked(packageName)) {
-                            setResultCode(Activity.RESULT_OK);
-                            return;
-                        }
-                    }
-                    return;
-                } else if (Intent.ACTION_EXTERNAL_APPLICATIONS_UNAVAILABLE.equals(action)) {
-                    pkgList = intent.getStringArrayExtra(Intent.EXTRA_CHANGED_PACKAGE_LIST);
-                } else if (Intent.ACTION_USER_STOPPED.equals(action)) {
-                    int userHandle = intent.getIntExtra(Intent.EXTRA_USER_HANDLE, -1);
-                    if (userHandle >= 0) {
-                        removeUserLocked(userHandle);
-                        for (int i = mLastAlarmDeliveredForPackage.size() - 1; i >= 0; i--) {
-                            final Pair<String, Integer> packageUser =
-                                    mLastAlarmDeliveredForPackage.keyAt(i);
-                            if (packageUser.second == userHandle) {
-                                mLastAlarmDeliveredForPackage.removeAt(i);
+                switch (intent.getAction()) {
+                    case Intent.ACTION_QUERY_PACKAGE_RESTART:
+                        pkgList = intent.getStringArrayExtra(Intent.EXTRA_PACKAGES);
+                        for (String packageName : pkgList) {
+                            if (lookForPackageLocked(packageName)) {
+                                setResultCode(Activity.RESULT_OK);
+                                return;
                             }
                         }
-                    }
-                } else if (Intent.ACTION_UID_REMOVED.equals(action)) {
-                    if (uid >= 0) {
-                        mLastAllowWhileIdleDispatch.delete(uid);
-                        mUseAllowWhileIdleShortTime.delete(uid);
-                    }
-                } else {
-                    if (Intent.ACTION_PACKAGE_REMOVED.equals(action)
-                            && intent.getBooleanExtra(Intent.EXTRA_REPLACING, false)) {
-                        // This package is being updated; don't kill its alarms.
                         return;
-                    }
-                    Uri data = intent.getData();
-                    if (data != null) {
-                        String pkg = data.getSchemeSpecificPart();
-                        if (pkg != null) {
-                            pkgList = new String[]{pkg};
+                    case Intent.ACTION_EXTERNAL_APPLICATIONS_UNAVAILABLE:
+                        pkgList = intent.getStringArrayExtra(Intent.EXTRA_CHANGED_PACKAGE_LIST);
+                        break;
+                    case Intent.ACTION_USER_STOPPED:
+                        final int userHandle = intent.getIntExtra(Intent.EXTRA_USER_HANDLE, -1);
+                        if (userHandle >= 0) {
+                            removeUserLocked(userHandle);
+                            mAppWakeupHistory.removeForUser(userHandle);
                         }
-                    }
+                        return;
+                    case Intent.ACTION_UID_REMOVED:
+                        if (uid >= 0) {
+                            mLastAllowWhileIdleDispatch.delete(uid);
+                            mUseAllowWhileIdleShortTime.delete(uid);
+                        }
+                        return;
+                    case Intent.ACTION_PACKAGE_REMOVED:
+                        if (intent.getBooleanExtra(Intent.EXTRA_REPLACING, false)) {
+                            // This package is being updated; don't kill its alarms.
+                            return;
+                        }
+                        // Intentional fall-through.
+                    case Intent.ACTION_PACKAGE_RESTARTED:
+                        final Uri data = intent.getData();
+                        if (data != null) {
+                            final String pkg = data.getSchemeSpecificPart();
+                            if (pkg != null) {
+                                pkgList = new String[]{pkg};
+                            }
+                        }
+                        break;
                 }
                 if (pkgList != null && (pkgList.length > 0)) {
-                    for (int i = mLastAlarmDeliveredForPackage.size() - 1; i >= 0; i--) {
-                        Pair<String, Integer> packageUser = mLastAlarmDeliveredForPackage.keyAt(i);
-                        if (ArrayUtils.contains(pkgList, packageUser.first)
-                                && packageUser.second == UserHandle.getUserId(uid)) {
-                            mLastAlarmDeliveredForPackage.removeAt(i);
-                        }
-                    }
                     for (String pkg : pkgList) {
                         if (uid >= 0) {
-                            // package-removed case
+                            // package-removed and package-restarted case
+                            mAppWakeupHistory.removeForPackage(pkg, UserHandle.getUserId(uid));
                             removeLocked(uid);
                         } else {
-                            // external-applications-unavailable etc case
+                            // external-applications-unavailable case
                             removeLocked(pkg);
                         }
                         mPriorities.remove(pkg);
@@ -4131,7 +4327,8 @@
     /**
      * Tracking of app assignments to standby buckets
      */
-    final class AppStandbyTracker extends UsageStatsManagerInternal.AppIdleStateChangeListener {
+    private final class AppStandbyTracker extends
+            UsageStatsManagerInternal.AppIdleStateChangeListener {
         @Override
         public void onAppIdleStateChanged(final String packageName, final @UserIdInt int userId,
                 boolean idle, int bucket, int reason) {
@@ -4474,7 +4671,8 @@
             if (!isExemptFromAppStandby(alarm)) {
                 final Pair<String, Integer> packageUser = Pair.create(alarm.sourcePackage,
                         UserHandle.getUserId(alarm.creatorUid));
-                mLastAlarmDeliveredForPackage.put(packageUser, nowELAPSED);
+                mAppWakeupHistory.recordAlarmForPackage(alarm.sourcePackage,
+                        UserHandle.getUserId(alarm.creatorUid), nowELAPSED);
             }
 
             final BroadcastStats bs = inflight.mBroadcastStats;
diff --git a/services/core/java/com/android/server/BluetoothManagerService.java b/services/core/java/com/android/server/BluetoothManagerService.java
index a333381..c4bc52c 100644
--- a/services/core/java/com/android/server/BluetoothManagerService.java
+++ b/services/core/java/com/android/server/BluetoothManagerService.java
@@ -208,6 +208,8 @@
     private int mErrorRecoveryRetryCounter;
     private final int mSystemUiUid;
 
+    private boolean mIsHearingAidProfileSupported;
+
     // Save a ProfileServiceConnections object for each of the bound
     // bluetooth profile services
     private final Map<Integer, ProfileServiceConnections> mProfileServices = new HashMap<>();
@@ -391,13 +393,19 @@
         mCallbacks = new RemoteCallbackList<IBluetoothManagerCallback>();
         mStateChangeCallbacks = new RemoteCallbackList<IBluetoothStateChangeCallback>();
 
+        mIsHearingAidProfileSupported = context.getResources()
+                .getBoolean(com.android.internal.R.bool.config_hearing_aid_profile_supported);
+
         // TODO: We need a more generic way to initialize the persist keys of FeatureFlagUtils
-        boolean isHearingAidEnabled;
         String value = SystemProperties.get(FeatureFlagUtils.PERSIST_PREFIX + FeatureFlagUtils.HEARING_AID_SETTINGS);
         if (!TextUtils.isEmpty(value)) {
-            isHearingAidEnabled = Boolean.parseBoolean(value);
+            boolean isHearingAidEnabled = Boolean.parseBoolean(value);
             Log.v(TAG, "set feature flag HEARING_AID_SETTINGS to " + isHearingAidEnabled);
             FeatureFlagUtils.setEnabled(context, FeatureFlagUtils.HEARING_AID_SETTINGS, isHearingAidEnabled);
+            if (isHearingAidEnabled && !mIsHearingAidProfileSupported) {
+                // Overwrite to enable support by FeatureFlag
+                mIsHearingAidProfileSupported = true;
+            }
         }
 
         IntentFilter filter = new IntentFilter();
@@ -679,6 +687,11 @@
         return false;
     }
 
+    @Override
+    public boolean isHearingAidProfileSupported() {
+        return mIsHearingAidProfileSupported;
+    }
+
     // Monitor change of BLE scan only mode settings.
     private void registerForBleScanModeChange() {
         ContentObserver contentObserver = new ContentObserver(null) {
diff --git a/services/core/java/com/android/server/ConnectivityService.java b/services/core/java/com/android/server/ConnectivityService.java
index aa74600..fda7279 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;
@@ -6185,6 +6186,17 @@
     }
 
     @Override
+    public void startNattKeepaliveWithFd(Network network, FileDescriptor fd, int resourceId,
+            int intervalSeconds, Messenger messenger, IBinder binder, String srcAddr,
+            String dstAddr) {
+        enforceKeepalivePermission();
+        mKeepaliveTracker.startNattKeepalive(
+                getNetworkAgentInfoForNetwork(network), fd, resourceId,
+                intervalSeconds, messenger, binder,
+                srcAddr, dstAddr, NattSocketKeepalive.NATT_PORT);
+    }
+
+    @Override
     public void stopKeepalive(Network network, int slot) {
         mHandler.sendMessage(mHandler.obtainMessage(
                 NetworkAgent.CMD_STOP_PACKET_KEEPALIVE, slot, PacketKeepalive.SUCCESS, network));
diff --git a/services/core/java/com/android/server/LocationManagerService.java b/services/core/java/com/android/server/LocationManagerService.java
index 8dcc1d5..d2c6354 100644
--- a/services/core/java/com/android/server/LocationManagerService.java
+++ b/services/core/java/com/android/server/LocationManagerService.java
@@ -220,6 +220,8 @@
 
     private final ArraySet<String> mBackgroundThrottlePackageWhitelist = new ArraySet<>();
 
+    private final ArraySet<String> mIgnoreSettingsPackageWhitelist = new ArraySet<>();
+
     @GuardedBy("mLock")
     private final ArrayMap<IBinder, Identity> mGnssMeasurementsListeners = new ArrayMap<>();
 
@@ -353,6 +355,18 @@
                         }
                     }
                 }, UserHandle.USER_ALL);
+        mContext.getContentResolver().registerContentObserver(
+                Settings.Global.getUriFor(
+                        Settings.Global.LOCATION_IGNORE_SETTINGS_PACKAGE_WHITELIST),
+                true,
+                new ContentObserver(mHandler) {
+                    @Override
+                    public void onChange(boolean selfChange) {
+                        synchronized (mLock) {
+                            onIgnoreSettingsWhitelistChangedLocked();
+                        }
+                    }
+                }, UserHandle.USER_ALL);
 
         new PackageMonitor() {
             @Override
@@ -550,6 +564,25 @@
         }
     }
 
+    @GuardedBy("lock")
+    private void onIgnoreSettingsWhitelistChangedLocked() {
+        String setting = Settings.Global.getString(
+                mContext.getContentResolver(),
+                Settings.Global.LOCATION_IGNORE_SETTINGS_PACKAGE_WHITELIST);
+        if (setting == null) {
+            setting = "";
+        }
+
+        mIgnoreSettingsPackageWhitelist.clear();
+        mIgnoreSettingsPackageWhitelist.addAll(
+                SystemConfig.getInstance().getAllowIgnoreLocationSettings());
+        mIgnoreSettingsPackageWhitelist.addAll(Arrays.asList(setting.split(",")));
+
+        for (LocationProvider p : mProviders) {
+            applyRequirementsLocked(p);
+        }
+    }
+
     @GuardedBy("mLock")
     private void onUserProfilesChangedLocked() {
         mCurrentUserProfiles = mUserManager.getProfileIdsWithDisabled(mCurrentUserId);
@@ -1299,8 +1332,7 @@
                     if (provider == null) {
                         continue;
                     }
-                    if (!provider.isUseableLocked()
-                            && !updateRecord.mRealRequest.isLocationSettingsIgnored()) {
+                    if (!provider.isUseableLocked() && !isSettingsExemptLocked(updateRecord)) {
                         continue;
                     }
 
@@ -1988,7 +2020,7 @@
                 }
 
                 // requests that ignore location settings will never provider notifications
-                if (record.mRealRequest.isLocationSettingsIgnored()) {
+                if (isSettingsExemptLocked(record)) {
                     continue;
                 }
 
@@ -2052,8 +2084,7 @@
                         record.mReceiver.mAllowedResolutionLevel)) {
                     continue;
                 }
-                if (!provider.isUseableLocked()
-                        && !record.mRealRequest.isLocationSettingsIgnored()) {
+                if (!provider.isUseableLocked() && !isSettingsExemptLocked(record)) {
                     continue;
                 }
 
@@ -2163,6 +2194,25 @@
         return false;
     }
 
+    @GuardedBy("mLock")
+    private boolean isSettingsExemptLocked(UpdateRecord record) {
+        if (!record.mRealRequest.isLocationSettingsIgnored()) {
+            return false;
+        }
+
+        if (mBackgroundThrottlePackageWhitelist.contains(record.mReceiver.mIdentity.mPackageName)) {
+            return true;
+        }
+
+        for (LocationProvider provider : mProviders) {
+            if (record.mReceiver.mIdentity.mPackageName.equals(provider.getPackageLocked())) {
+                return true;
+            }
+        }
+
+        return false;
+    }
+
     private class UpdateRecord {
         final String mProvider;
         private final LocationRequest mRealRequest;  // original request from client
@@ -2306,7 +2356,7 @@
         }
         // make getFastestInterval() the minimum of interval and fastest interval
         if (sanitizedRequest.getFastestInterval() > sanitizedRequest.getInterval()) {
-            request.setFastestInterval(request.getInterval());
+            sanitizedRequest.setFastestInterval(request.getInterval());
         }
         return sanitizedRequest;
     }
@@ -2418,7 +2468,7 @@
             oldRecord.disposeLocked(false);
         }
 
-        if (!provider.isUseableLocked() && !request.isLocationSettingsIgnored()) {
+        if (!provider.isUseableLocked() && !isSettingsExemptLocked(record)) {
             // Notify the listener that updates are currently disabled - but only if the request
             // does not ignore location settings
             receiver.callProviderEnabledLocked(name, false);
@@ -3056,7 +3106,7 @@
             Receiver receiver = r.mReceiver;
             boolean receiverDead = false;
 
-            if (!provider.isUseableLocked() && !r.mRealRequest.isLocationSettingsIgnored()) {
+            if (!provider.isUseableLocked() && !isSettingsExemptLocked(r)) {
                 continue;
             }
 
diff --git a/services/core/java/com/android/server/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/am/ProcessList.java b/services/core/java/com/android/server/am/ProcessList.java
index c1be387..c698039 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.
@@ -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,
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/notification/NotificationManagerService.java b/services/core/java/com/android/server/notification/NotificationManagerService.java
index 47a5597..a164686 100644
--- a/services/core/java/com/android/server/notification/NotificationManagerService.java
+++ b/services/core/java/com/android/server/notification/NotificationManagerService.java
@@ -845,10 +845,12 @@
                         reportSeen(r);
                     }
                     r.setVisibility(true, nv.rank, nv.count);
+                    boolean isHun = (nv.location
+                            == NotificationVisibility.NotificationLocation.LOCATION_FIRST_HEADS_UP);
                     // hasBeenVisiblyExpanded must be called after updating the expansion state of
                     // the NotificationRecord to ensure the expansion state is up-to-date.
-                    if (r.hasBeenVisiblyExpanded()) {
-                        logSmartSuggestionsVisible(r);
+                    if (isHun || r.hasBeenVisiblyExpanded()) {
+                        logSmartSuggestionsVisible(r, nv.location.toMetricsEventEnum());
                     }
                     maybeRecordInterruptionLocked(r);
                     nv.recycle();
@@ -876,7 +878,7 @@
                     // hasBeenVisiblyExpanded must be called after updating the expansion state of
                     // the NotificationRecord to ensure the expansion state is up-to-date.
                     if (r.hasBeenVisiblyExpanded()) {
-                        logSmartSuggestionsVisible(r);
+                        logSmartSuggestionsVisible(r, notificationLocation);
                     }
                     if (userAction) {
                         MetricsLogger.action(r.getItemLogMaker()
@@ -952,7 +954,7 @@
     };
 
     @VisibleForTesting
-    void logSmartSuggestionsVisible(NotificationRecord r) {
+    void logSmartSuggestionsVisible(NotificationRecord r, int notificationLocation) {
         // If the newly visible notification has smart suggestions
         // then log that the user has seen them.
         if ((r.getNumSmartRepliesAdded() > 0 || r.getNumSmartActionsAdded() > 0)
@@ -966,7 +968,10 @@
                             r.getNumSmartActionsAdded())
                     .addTaggedData(
                             MetricsEvent.NOTIFICATION_SMART_SUGGESTION_ASSISTANT_GENERATED,
-                            r.getSuggestionsGeneratedByAssistant() ? 1 : 0);
+                            r.getSuggestionsGeneratedByAssistant() ? 1 : 0)
+                    // The fields in the NotificationVisibility.NotificationLocation enum map
+                    // directly to the fields in the MetricsEvent.NotificationLocation enum.
+                    .addTaggedData(MetricsEvent.NOTIFICATION_LOCATION, notificationLocation);
             mMetricsLogger.write(logMaker);
         }
     }
diff --git a/services/core/java/com/android/server/pm/PackageManagerService.java b/services/core/java/com/android/server/pm/PackageManagerService.java
index b8342cf..9100f6a 100644
--- a/services/core/java/com/android/server/pm/PackageManagerService.java
+++ b/services/core/java/com/android/server/pm/PackageManagerService.java
@@ -10574,8 +10574,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());
@@ -20672,7 +20670,6 @@
         storage.registerListener(mStorageListener);
 
         mInstallerService.systemReady();
-        mDexManager.systemReady();
         mPackageDexOptimizer.systemReady();
 
         getStorageManagerInternal().addExternalStoragePolicy(
diff --git a/services/core/java/com/android/server/pm/PackageManagerShellCommand.java b/services/core/java/com/android/server/pm/PackageManagerShellCommand.java
index 022c1aa..3562630 100644
--- a/services/core/java/com/android/server/pm/PackageManagerShellCommand.java
+++ b/services/core/java/com/android/server/pm/PackageManagerShellCommand.java
@@ -40,10 +40,10 @@
 import android.content.pm.PackageInfo;
 import android.content.pm.PackageInstaller;
 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;
@@ -634,9 +634,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) {
diff --git a/services/core/java/com/android/server/pm/dex/DexManager.java b/services/core/java/com/android/server/pm/dex/DexManager.java
index 1a2b115..7ac7395 100644
--- a/services/core/java/com/android/server/pm/dex/DexManager.java
+++ b/services/core/java/com/android/server/pm/dex/DexManager.java
@@ -16,31 +16,28 @@
 
 package com.android.server.pm.dex;
 
+import static android.provider.DeviceConfig.FsiBoot;
+
 import static com.android.server.pm.InstructionSets.getAppDexInstructionSets;
 import static com.android.server.pm.dex.PackageDexUsage.DexUseInfo;
 import static com.android.server.pm.dex.PackageDexUsage.PackageUseInfo;
 
-import android.content.ContentResolver;
 import android.content.Context;
 import android.content.pm.ApplicationInfo;
 import android.content.pm.IPackageManager;
 import android.content.pm.PackageInfo;
-import android.content.pm.PackageParser;
-import android.database.ContentObserver;
-import android.os.Build;
 import android.os.FileUtils;
 import android.os.RemoteException;
 import android.os.SystemProperties;
 import android.os.UserHandle;
 import android.os.storage.StorageManager;
-import android.provider.Settings.Global;
+import android.provider.DeviceConfig;
 import android.util.Log;
 import android.util.Slog;
 import android.util.jar.StrictJarFile;
 
 import com.android.internal.annotations.GuardedBy;
 import com.android.internal.annotations.VisibleForTesting;
-import com.android.internal.util.ArrayUtils;
 import com.android.server.pm.Installer;
 import com.android.server.pm.Installer.InstallerException;
 import com.android.server.pm.PackageDexOptimizer;
@@ -136,10 +133,6 @@
         return mDexLogger;
     }
 
-    public void systemReady() {
-        registerSettingObserver();
-    }
-
     /**
      * Notify about dex files loads.
      * Note that this method is invoked when apps load dex files and it should
@@ -699,47 +692,10 @@
         mDexLogger.writeNow();
     }
 
-    private void registerSettingObserver() {
-        final ContentResolver resolver = mContext.getContentResolver();
-
-        // This observer provides a one directional mapping from Global.PRIV_APP_OOB_ENABLED to
-        // pm.dexopt.priv-apps-oob property. This is only for experiment and should be removed once
-        // it is done.
-        ContentObserver privAppOobObserver = new ContentObserver(null) {
-            @Override
-            public void onChange(boolean selfChange) {
-                int oobEnabled = Global.getInt(resolver, Global.PRIV_APP_OOB_ENABLED, 0);
-                SystemProperties.set(PROPERTY_NAME_PM_DEXOPT_PRIV_APPS_OOB,
-                        oobEnabled == 1 ? "true" : "false");
-            }
-        };
-        resolver.registerContentObserver(
-                Global.getUriFor(Global.PRIV_APP_OOB_ENABLED), false, privAppOobObserver,
-                UserHandle.USER_SYSTEM);
-        // At boot, restore the value from the setting, which persists across reboot.
-        privAppOobObserver.onChange(true);
-
-        ContentObserver privAppOobListObserver = new ContentObserver(null) {
-            @Override
-            public void onChange(boolean selfChange) {
-                String oobList = Global.getString(resolver, Global.PRIV_APP_OOB_LIST);
-                if (oobList == null) {
-                    oobList = "ALL";
-                }
-                SystemProperties.set(PROPERTY_NAME_PM_DEXOPT_PRIV_APPS_OOB_LIST, oobList);
-            }
-        };
-        resolver.registerContentObserver(
-                Global.getUriFor(Global.PRIV_APP_OOB_LIST), false, privAppOobListObserver,
-                UserHandle.USER_SYSTEM);
-        // At boot, restore the value from the setting, which persists across reboot.
-        privAppOobListObserver.onChange(true);
-    }
-
     /**
      * Returns whether the given package is in the list of privilaged apps that should run out of
-     * box. This only makes sense if PROPERTY_NAME_PM_DEXOPT_PRIV_APPS_OOB is true. Note that when
-     * the the OOB list is empty, all priv apps will run in OOB mode.
+     * box. This only makes sense if the feature is enabled. Note that when the the OOB list is
+     * empty, all priv apps will run in OOB mode.
      */
     public static boolean isPackageSelectedToRunOob(String packageName) {
         return isPackageSelectedToRunOob(Arrays.asList(packageName));
@@ -747,19 +703,35 @@
 
     /**
      * Returns whether any of the given packages are in the list of privilaged apps that should run
-     * out of box. This only makes sense if PROPERTY_NAME_PM_DEXOPT_PRIV_APPS_OOB is true. Note that
-     * when the the OOB list is empty, all priv apps will run in OOB mode.
+     * out of box. This only makes sense if the feature is enabled. Note that when the the OOB list
+     * is empty, all priv apps will run in OOB mode.
      */
     public static boolean isPackageSelectedToRunOob(Collection<String> packageNamesInSameProcess) {
-        if (!SystemProperties.getBoolean(PROPERTY_NAME_PM_DEXOPT_PRIV_APPS_OOB, false)) {
+        return isPackageSelectedToRunOobInternal(
+                SystemProperties.getBoolean(PROPERTY_NAME_PM_DEXOPT_PRIV_APPS_OOB, false),
+                SystemProperties.get(PROPERTY_NAME_PM_DEXOPT_PRIV_APPS_OOB_LIST, "ALL"),
+                DeviceConfig.getProperty(FsiBoot.NAMESPACE, FsiBoot.OOB_ENABLED),
+                DeviceConfig.getProperty(FsiBoot.NAMESPACE, FsiBoot.OOB_WHITELIST),
+                packageNamesInSameProcess);
+    }
+
+    @VisibleForTesting
+    /* package */ static boolean isPackageSelectedToRunOobInternal(
+            boolean isDefaultEnabled, String defaultWhitelist, String overrideEnabled,
+            String overrideWhitelist, Collection<String> packageNamesInSameProcess) {
+        // Allow experiment (if exists) to override device configuration.
+        boolean enabled = overrideEnabled != null ? overrideEnabled.equals("true")
+                : isDefaultEnabled;
+        if (!enabled) {
             return false;
         }
-        String oobListProperty = SystemProperties.get(
-                PROPERTY_NAME_PM_DEXOPT_PRIV_APPS_OOB_LIST, "ALL");
-        if ("ALL".equals(oobListProperty)) {
+
+        // Similarly, experiment flag can override the whitelist.
+        String whitelist = overrideWhitelist != null ? overrideWhitelist : defaultWhitelist;
+        if ("ALL".equals(whitelist)) {
             return true;
         }
-        for (String oobPkgName : oobListProperty.split(",")) {
+        for (String oobPkgName : whitelist.split(",")) {
             if (packageNamesInSameProcess.contains(oobPkgName)) {
                 return true;
             }
@@ -768,32 +740,6 @@
     }
 
     /**
-     * Generates package related log if the package has code stored in unexpected way.
-     */
-    public static void maybeLogUnexpectedPackageDetails(PackageParser.Package pkg) {
-        if (!Build.IS_DEBUGGABLE) {
-            return;
-        }
-
-        if (pkg.isPrivileged() && isPackageSelectedToRunOob(pkg.packageName)) {
-            logIfPackageHasUncompressedCode(pkg);
-        }
-    }
-
-    /**
-     * Generates log if the APKs in the given package have uncompressed dex file and so
-     * files that can be direclty mapped.
-     */
-    private static void logIfPackageHasUncompressedCode(PackageParser.Package pkg) {
-        auditUncompressedCodeInApk(pkg.baseCodePath);
-        if (!ArrayUtils.isEmpty(pkg.splitCodePaths)) {
-            for (int i = 0; i < pkg.splitCodePaths.length; i++) {
-                auditUncompressedCodeInApk(pkg.splitCodePaths[i]);
-            }
-        }
-    }
-
-    /**
      * Generates log if the archive located at {@code fileName} has uncompressed dex file and so
      * files that can be direclty mapped.
      */
diff --git a/services/core/java/com/android/server/policy/PhoneWindowManager.java b/services/core/java/com/android/server/policy/PhoneWindowManager.java
index 41cab2d..0e40a00 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);
             }
 
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/stats/StatsCompanionService.java b/services/core/java/com/android/server/stats/StatsCompanionService.java
index 40664fe..acede7d 100644
--- a/services/core/java/com/android/server/stats/StatsCompanionService.java
+++ b/services/core/java/com/android/server/stats/StatsCompanionService.java
@@ -1705,6 +1705,27 @@
         }
     }
 
+    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);
+        }
+    }
+
     /**
      * Pulls various data.
      */
@@ -1867,6 +1888,10 @@
                 pullDeviceCalculatedPowerBlameOther(tagId, elapsedNanos, wallClockNanos, ret);
                 break;
             }
+            case StatsLog.TEMPERATURE: {
+                pullTemperature(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/wm/ActivityTaskManagerService.java b/services/core/java/com/android/server/wm/ActivityTaskManagerService.java
index 2affa97..ea6f4cc 100644
--- a/services/core/java/com/android/server/wm/ActivityTaskManagerService.java
+++ b/services/core/java/com/android/server/wm/ActivityTaskManagerService.java
@@ -6222,30 +6222,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
diff --git a/services/core/java/com/android/server/wm/AppTransition.java b/services/core/java/com/android/server/wm/AppTransition.java
index 5f393ef..f3a363a 100644
--- a/services/core/java/com/android/server/wm/AppTransition.java
+++ b/services/core/java/com/android/server/wm/AppTransition.java
@@ -174,6 +174,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 +319,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 +415,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 +423,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 +499,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;
     }
@@ -2128,6 +2126,8 @@
                     pw.println(mLastOpeningApp);
             pw.print(prefix); pw.print("mLastClosingApp=");
                     pw.println(mLastClosingApp);
+            pw.print(prefix); pw.print("mLastChangingApp=");
+            pw.println(mLastChangingApp);
         }
     }
 
@@ -2226,14 +2226,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..49308b8 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;
@@ -89,8 +89,9 @@
      * 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 +109,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 +146,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 +157,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 +174,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 +193,7 @@
 
         mDisplayContent.mOpeningApps.clear();
         mDisplayContent.mClosingApps.clear();
+        mDisplayContent.mChangingApps.clear();
         mDisplayContent.mUnknownAppVisibilityController.clear();
 
         // This has changed the visibility of windows, so perform
@@ -191,8 +202,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);
 
@@ -237,29 +248,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 +279,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 +379,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 +416,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 +440,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..29645f6 100644
--- a/services/core/java/com/android/server/wm/AppWindowThumbnail.java
+++ b/services/core/java/com/android/server/wm/AppWindowThumbnail.java
@@ -50,9 +50,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();
@@ -86,6 +100,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 +118,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 +171,9 @@
     @Override
     public void onAnimationLeashCreated(Transaction t, SurfaceControl leash) {
         t.setLayer(leash, Integer.MAX_VALUE);
+        if (mRelative) {
+            t.reparent(leash, mAppToken.getSurfaceControl());
+        }
     }
 
     @Override
diff --git a/services/core/java/com/android/server/wm/AppWindowToken.java b/services/core/java/com/android/server/wm/AppWindowToken.java
index 750c5ca..65b36a0 100644
--- a/services/core/java/com/android/server/wm/AppWindowToken.java
+++ b/services/core/java/com/android/server/wm/AppWindowToken.java
@@ -16,6 +16,7 @@
 
 package com.android.server.wm;
 
+import static android.app.WindowConfiguration.WINDOWING_MODE_FREEFORM;
 import static android.app.WindowConfiguration.WINDOWING_MODE_PINNED;
 import static android.app.WindowConfiguration.WINDOWING_MODE_SPLIT_SCREEN_PRIMARY;
 import static android.app.WindowConfiguration.WINDOWING_MODE_UNDEFINED;
@@ -33,6 +34,7 @@
 import static android.view.WindowManager.LayoutParams.TYPE_APPLICATION_STARTING;
 import static android.view.WindowManager.LayoutParams.TYPE_BASE_APPLICATION;
 import static android.view.WindowManager.TRANSIT_DOCK_TASK_FROM_RECENTS;
+import static android.view.WindowManager.TRANSIT_TASK_CHANGE_WINDOWING_MODE;
 import static android.view.WindowManager.TRANSIT_UNSET;
 import static android.view.WindowManager.TRANSIT_WALLPAPER_OPEN;
 
@@ -97,6 +99,7 @@
 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;
@@ -117,6 +120,7 @@
 import com.android.server.display.ColorDisplayService;
 import com.android.server.policy.WindowManagerPolicy;
 import com.android.server.policy.WindowManagerPolicy.StartingSurface;
+import com.android.server.wm.RemoteAnimationController.RemoteAnimationRecord;
 import com.android.server.wm.WindowManagerService.H;
 
 import java.io.PrintWriter;
@@ -261,7 +265,18 @@
     /** Whether our surface was set to be showing in the last call to {@link #prepareSurfaces} */
     private boolean mLastSurfaceShowing = true;
 
+    /**
+     * This gets used during some open/close transitions as well as during a change transition
+     * where it represents the starting-state snapshot.
+     */
     private AppWindowThumbnail mThumbnail;
+    private final Rect mTransitStartRect = new Rect();
+
+    /**
+     * This leash is used to "freeze" the app surface in place after the state change, but before
+     * the animation is ready to start.
+     */
+    private SurfaceControl mTransitChangeLeash = null;
 
     /** Have we been asked to have this token keep the screen frozen? */
     private boolean mFreezingScreen;
@@ -272,6 +287,7 @@
 
     private final Point mTmpPoint = new Point();
     private final Rect mTmpRect = new Rect();
+    private final Rect mTmpPrevBounds = new Rect();
     private RemoteAnimationDefinition mRemoteAnimationDefinition;
     private AnimatingAppWindowTokenRegistry mAnimatingAppWindowTokenRegistry;
 
@@ -810,6 +826,7 @@
         boolean delayed = commitVisibility(null, false, TRANSIT_UNSET, true, mVoiceInteraction);
 
         getDisplayContent().mOpeningApps.remove(this);
+        getDisplayContent().mChangingApps.remove(this);
         getDisplayContent().mUnknownAppVisibilityController.appRemovedOrHidden(this);
         mWmService.mTaskSnapshotController.onAppRemoved(this);
         waitingToShow = false;
@@ -1528,6 +1545,7 @@
     @Override
     public void onConfigurationChanged(Configuration newParentConfig) {
         final int prevWinMode = getWindowingMode();
+        mTmpPrevBounds.set(getBounds());
         super.onConfigurationChanged(newParentConfig);
         final int winMode = getWindowingMode();
 
@@ -1559,9 +1577,68 @@
                 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);
+
+        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);
+    }
+
     @Override
     void checkAppWindowsReadyToShow() {
         if (allDrawn == mLastAllDrawn) {
@@ -2242,6 +2319,15 @@
         return getBounds();
     }
 
+    private static boolean isChangeTransition(int transit) {
+        return transit == TRANSIT_TASK_CHANGE_WINDOWING_MODE;
+    }
+
+    private int getDefaultChangeTransitionDuration() {
+        return (int) (AppTransition.DEFAULT_APP_TRANSITION_DURATION
+                        * mWmService.getTransitionAnimationScaleLocked());
+    }
+
     boolean applyAnimationLocked(WindowManager.LayoutParams lp, int transit, boolean enter,
             boolean isVoiceInteraction) {
 
@@ -2260,13 +2346,35 @@
         Trace.traceBegin(TRACE_TAG_WINDOW_MANAGER, "AWT#applyAnimationLocked");
         if (okToAnimate()) {
             final AnimationAdapter adapter;
+            AnimationAdapter thumbnailAdapter = null;
             getAnimationBounds(mTmpPoint, mTmpRect);
 
+            boolean isChanging = isChangeTransition(transit) && mThumbnail != null;
+
             // 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) {
+                int duration = getDefaultChangeTransitionDuration();
+                mTmpRect.offsetTo(mTmpPoint.x, mTmpPoint.y);
+                adapter = new LocalAnimationAdapter(
+                        new WindowChangeAnimationSpec(mTransitStartRect, mTmpRect,
+                                getDisplayContent().getDisplayInfo(), duration,
+                                true /* isAppAnimation */, false /* isThumbnail */),
+                        mWmService.mSurfaceAnimationRunner);
+                thumbnailAdapter = new LocalAnimationAdapter(
+                        new WindowChangeAnimationSpec(mTransitStartRect, mTmpRect,
+                                getDisplayContent().getDisplayInfo(), duration,
+                                true /* isAppAnimation */, true /* isThumbnail */),
+                        mWmService.mSurfaceAnimationRunner);
+                mTransit = transit;
+                mTransitFlags = getDisplayContent().mAppTransition.getTransitFlags();
             } else {
                 final int appStackClipMode =
                         getDisplayContent().mAppTransition.getAppStackClipMode();
@@ -2294,6 +2402,10 @@
                 if (adapter.getShowWallpaper()) {
                     mDisplayContent.pendingLayoutChanges |= FINISH_LAYOUT_REDO_WALLPAPER;
                 }
+                if (thumbnailAdapter != null) {
+                    mThumbnail.startAnimation(
+                            getPendingTransaction(), thumbnailAdapter, !isVisible());
+                }
             }
         } else {
             cancelAnimation();
@@ -2429,6 +2541,17 @@
 
         final DisplayContent dc = getDisplayContent();
         dc.assignStackOrdering();
+
+        if (leash == mTransitChangeLeash) {
+            // This is a temporary state so skip any animation notifications
+            return;
+        } else if (mTransitChangeLeash != null) {
+            // unparent mTransitChangeLeash for clean-up
+            t.hide(mTransitChangeLeash);
+            t.reparent(mTransitChangeLeash, null);
+            mTransitChangeLeash = null;
+        }
+
         if (mAnimatingAppWindowTokenRegistry != null) {
             mAnimatingAppWindowTokenRegistry.notifyStarting(this);
         }
@@ -2487,6 +2610,12 @@
                 + " okToAnimate=" + okToAnimate()
                 + " startingDisplayed=" + startingDisplayed);
 
+        // clean up thumbnail window
+        if (mThumbnail != null) {
+            mThumbnail.destroy();
+            mThumbnail = null;
+        }
+
         // WindowState.onExitAnimationDone might modify the children list, so make a copy and then
         // traverse the copy.
         final ArrayList<WindowState> children = new ArrayList<>(mChildren);
@@ -2518,14 +2647,30 @@
 
     @Override
     void cancelAnimation() {
-        super.cancelAnimation();
+        cancelAnimationOnly();
         clearThumbnail();
+        if (mTransitChangeLeash != null) {
+            getPendingTransaction().hide(mTransitChangeLeash);
+            getPendingTransaction().reparent(mTransitChangeLeash, null);
+            mTransitChangeLeash = null;
+        }
+    }
+
+    /**
+     * This only cancels the animation. It doesn't do other teardown like cleaning-up thumbnail
+     * or interim leashes.
+     * <p>
+     * Used when canceling in preparation for starting a new animation.
+     */
+    void cancelAnimationOnly() {
+        super.cancelAnimation();
     }
 
     boolean isWaitingForTransitionStart() {
         return getDisplayContent().mAppTransition.isTransitionSet()
                 && (getDisplayContent().mOpeningApps.contains(this)
-                    || getDisplayContent().mClosingApps.contains(this));
+                    || getDisplayContent().mClosingApps.contains(this)
+                    || getDisplayContent().mChangingApps.contains(this));
     }
 
     public int getTransit() {
@@ -2835,6 +2980,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/DisplayContent.java b/services/core/java/com/android/server/wm/DisplayContent.java
index 8fefd35..8f976e7 100644
--- a/services/core/java/com/android/server/wm/DisplayContent.java
+++ b/services/core/java/com/android/server/wm/DisplayContent.java
@@ -250,6 +250,7 @@
 
     final ArraySet<AppWindowToken> mOpeningApps = new ArraySet<>();
     final ArraySet<AppWindowToken> mClosingApps = new ArraySet<>();
+    final ArraySet<AppWindowToken> mChangingApps = new ArraySet<>();
     final UnknownAppVisibilityController mUnknownAppVisibilityController;
     BoundsAnimationController mBoundsAnimationController;
 
@@ -2454,6 +2455,7 @@
             // Clear all transitions & screen frozen states when removing display.
             mOpeningApps.clear();
             mClosingApps.clear();
+            mChangingApps.clear();
             mUnknownAppVisibilityController.clear();
             mAppTransition.removeAppTransitionTimeoutCallbacks();
             handleAnimatingStoppedAndTransition();
@@ -3284,7 +3286,7 @@
             }
         }
 
-        if (!mOpeningApps.isEmpty() || !mClosingApps.isEmpty()) {
+        if (!mOpeningApps.isEmpty() || !mClosingApps.isEmpty() || !mChangingApps.isEmpty()) {
             pw.println();
             if (mOpeningApps.size() > 0) {
                 pw.print("  mOpeningApps="); pw.println(mOpeningApps);
@@ -3292,6 +3294,9 @@
             if (mClosingApps.size() > 0) {
                 pw.print("  mClosingApps="); pw.println(mClosingApps);
             }
+            if (mChangingApps.size() > 0) {
+                pw.print("  mChangingApps="); pw.println(mChangingApps);
+            }
         }
 
         mUnknownAppVisibilityController.dump(pw, "  ");
diff --git a/services/core/java/com/android/server/wm/DockedStackDividerController.java b/services/core/java/com/android/server/wm/DockedStackDividerController.java
index 7ea88bb..ea65dd9 100644
--- a/services/core/java/com/android/server/wm/DockedStackDividerController.java
+++ b/services/core/java/com/android/server/wm/DockedStackDividerController.java
@@ -36,7 +36,6 @@
 import static com.android.server.wm.DockedStackDividerControllerProto.MINIMIZED_DOCK;
 import static com.android.server.wm.WindowManagerDebugConfig.TAG_WITH_CLASS_NAME;
 import static com.android.server.wm.WindowManagerDebugConfig.TAG_WM;
-import static com.android.server.wm.WindowManagerService.H.NOTIFY_DOCKED_STACK_MINIMIZED_CHANGED;
 import static com.android.server.wm.WindowManagerService.LAYER_OFFSET_DIM;
 
 import android.content.Context;
@@ -566,9 +565,7 @@
             mMaximizeMeetFraction = getClipRevealMeetFraction(stack);
             animDuration = (long) (mAnimationDuration * mMaximizeMeetFraction);
         }
-        mService.mH.removeMessages(NOTIFY_DOCKED_STACK_MINIMIZED_CHANGED);
-        mService.mH.obtainMessage(NOTIFY_DOCKED_STACK_MINIMIZED_CHANGED,
-                minimizedDock ? 1 : 0, 0).sendToTarget();
+        mService.mAtmInternal.notifyDockedStackMinimizedChanged(minimizedDock);
         final int size = mDockedStackListeners.beginBroadcast();
         for (int i = 0; i < size; ++i) {
             final IDockedStackListener listener = mDockedStackListeners.getBroadcastItem(i);
diff --git a/services/core/java/com/android/server/wm/InputMonitor.java b/services/core/java/com/android/server/wm/InputMonitor.java
index 632db38..3c5d911 100644
--- a/services/core/java/com/android/server/wm/InputMonitor.java
+++ b/services/core/java/com/android/server/wm/InputMonitor.java
@@ -46,7 +46,6 @@
 import android.view.InputEventReceiver;
 import android.view.InputWindowHandle;
 import android.view.SurfaceControl;
-import android.view.animation.Animation;
 
 import com.android.server.AnimationThread;
 import com.android.server.policy.WindowManagerPolicy;
@@ -70,8 +69,7 @@
 
     private boolean mDisableWallpaperTouchEvents;
     private final Rect mTmpRect = new Rect();
-    private final UpdateInputForAllWindowsConsumer mUpdateInputForAllWindowsConsumer =
-            new UpdateInputForAllWindowsConsumer();
+    private final UpdateInputForAllWindowsConsumer mUpdateInputForAllWindowsConsumer;
 
     private final int mDisplayId;
     private final DisplayContent mDisplayContent;
@@ -165,6 +163,7 @@
         mDisplayId = displayId;
         mInputTransaction = mDisplayContent.getPendingTransaction();
         mHandler = AnimationThread.getHandler();
+        mUpdateInputForAllWindowsConsumer = new UpdateInputForAllWindowsConsumer();
     }
 
     void onDisplayRemoved() {
@@ -407,6 +406,9 @@
         boolean inDrag;
         WallpaperController wallpaperController;
 
+        // An invalid window handle that tells SurfaceFlinger not update the input info.
+        final InputWindowHandle mInvalidInputWindow = new InputWindowHandle(null, null, mDisplayId);
+
         private void updateInputWindows(boolean inDrag) {
             Trace.traceBegin(Trace.TRACE_TAG_WINDOW_MANAGER, "updateInputWindows");
 
@@ -445,6 +447,10 @@
             final InputWindowHandle inputWindowHandle = w.mInputWindowHandle;
             if (inputChannel == null || inputWindowHandle == null || w.mRemoved
                     || w.cantReceiveTouchInput()) {
+                if (w.mWinAnimator.hasSurface()) {
+                    mInputTransaction.setInputWindowInfo(
+                            w.mWinAnimator.mSurfaceController.mSurfaceControl, mInvalidInputWindow);
+                }
                 // Skip this window because it cannot possibly receive input.
                 return;
             }
diff --git a/services/core/java/com/android/server/wm/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..f760b39 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,84 @@
         }
     };
 
-    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);
+                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 +392,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 +403,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 +432,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 +444,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/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/WallpaperController.java b/services/core/java/com/android/server/wm/WallpaperController.java
index 15239c7..c15afc5 100644
--- a/services/core/java/com/android/server/wm/WallpaperController.java
+++ b/services/core/java/com/android/server/wm/WallpaperController.java
@@ -661,7 +661,8 @@
      * Adjusts the wallpaper windows if the input display has a pending wallpaper layout or one of
      * the opening apps should be a wallpaper target.
      */
-    void adjustWallpaperWindowsForAppTransitionIfNeeded(ArraySet<AppWindowToken> openingApps) {
+    void adjustWallpaperWindowsForAppTransitionIfNeeded(ArraySet<AppWindowToken> openingApps,
+            ArraySet<AppWindowToken> changingApps) {
         boolean adjust = false;
         if ((mDisplayContent.pendingLayoutChanges & FINISH_LAYOUT_REDO_WALLPAPER) != 0) {
             adjust = true;
@@ -673,6 +674,15 @@
                     break;
                 }
             }
+            if (!adjust) {
+                for (int i = changingApps.size() - 1; i >= 0; --i) {
+                    final AppWindowToken token = changingApps.valueAt(i);
+                    if (token.windowsCanBeWallpaperTarget()) {
+                        adjust = true;
+                        break;
+                    }
+                }
+            }
         }
 
         if (adjust) {
diff --git a/services/core/java/com/android/server/wm/WindowChangeAnimationSpec.java b/services/core/java/com/android/server/wm/WindowChangeAnimationSpec.java
new file mode 100644
index 0000000..7dd7c4f
--- /dev/null
+++ b/services/core/java/com/android/server/wm/WindowChangeAnimationSpec.java
@@ -0,0 +1,203 @@
+/*
+ * 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;
+
+    public WindowChangeAnimationSpec(Rect startBounds, Rect endBounds, DisplayInfo displayInfo,
+            long duration, boolean isAppAnimation, boolean isThumbnail) {
+        mStartBounds = new Rect(startBounds);
+        mEndBounds = new Rect(endBounds);
+        mIsAppAnimation = isAppAnimation;
+        mIsThumbnail = isThumbnail;
+        createBoundsInterpolator(duration, displayInfo);
+    }
+
+    @Override
+    public boolean getShowWallpaper() {
+        return false;
+    }
+
+    @Override
+    public int getBackgroundColor() {
+        return 0;
+    }
+
+    @Override
+    public long getDuration() {
+        return mAnimation.getDuration();
+    }
+
+    /**
+     * This animator behaves slightly differently depending on whether the window is growing
+     * or shrinking:
+     * If growing, it will do a clip-reveal after quicker fade-out/scale of the smaller (old)
+     * snapshot.
+     * If shrinking, it will do an opposite clip-reveal on the old snapshot followed by a quicker
+     * fade-out of the bigger (old) snapshot while simultaneously shrinking the new window into
+     * place.
+     * @param duration
+     * @param displayInfo
+     */
+    private void createBoundsInterpolator(long duration, DisplayInfo displayInfo) {
+        boolean growing = mEndBounds.width() - mStartBounds.width()
+                + mEndBounds.height() - mStartBounds.height() >= 0;
+        float scalePart = 0.7f;
+        long scalePeriod = (long) (duration * scalePart);
+        float startScaleX = scalePart * ((float) mStartBounds.width()) / mEndBounds.width()
+                + (1.f - scalePart);
+        float startScaleY = scalePart * ((float) mStartBounds.height()) / mEndBounds.height()
+                + (1.f - scalePart);
+        if (mIsThumbnail) {
+            AnimationSet animSet = new AnimationSet(true);
+            Animation anim = new AlphaAnimation(1.f, 0.f);
+            anim.setDuration(scalePeriod);
+            if (!growing) {
+                anim.setStartOffset(duration - scalePeriod);
+            }
+            animSet.addAnimation(anim);
+            float endScaleX = 1.f / startScaleX;
+            float endScaleY = 1.f / startScaleY;
+            anim = new ScaleAnimation(endScaleX, endScaleX, endScaleY, endScaleY);
+            anim.setDuration(duration);
+            animSet.addAnimation(anim);
+            mAnimation = animSet;
+            mAnimation.initialize(mStartBounds.width(), mStartBounds.height(),
+                    mEndBounds.width(), mEndBounds.height());
+        } else {
+            AnimationSet animSet = new AnimationSet(true);
+            final Animation scaleAnim = new ScaleAnimation(startScaleX, 1, startScaleY, 1);
+            scaleAnim.setDuration(scalePeriod);
+            if (!growing) {
+                scaleAnim.setStartOffset(duration - scalePeriod);
+            }
+            animSet.addAnimation(scaleAnim);
+            final Animation translateAnim = new TranslateAnimation(mStartBounds.left,
+                    mEndBounds.left, mStartBounds.top, mEndBounds.top);
+            translateAnim.setDuration(duration);
+            animSet.addAnimation(translateAnim);
+            Rect startClip = new Rect(mStartBounds);
+            Rect endClip = new Rect(mEndBounds);
+            startClip.offsetTo(0, 0);
+            endClip.offsetTo(0, 0);
+            final Animation clipAnim = new ClipRectAnimation(startClip, endClip);
+            clipAnim.setDuration(duration);
+            animSet.addAnimation(clipAnim);
+            mAnimation = animSet;
+            mAnimation.initialize(mStartBounds.width(), mStartBounds.height(),
+                    displayInfo.appWidth, displayInfo.appHeight);
+        }
+    }
+
+    @Override
+    public void apply(Transaction t, SurfaceControl leash, long currentPlayTime) {
+        final TmpValues tmp = mThreadLocalTmps.get();
+        if (mIsThumbnail) {
+            mAnimation.getTransformation(currentPlayTime, tmp.mTransformation);
+            t.setMatrix(leash, tmp.mTransformation.getMatrix(), tmp.mFloats);
+            t.setAlpha(leash, tmp.mTransformation.getAlpha());
+        } else {
+            mAnimation.getTransformation(currentPlayTime, tmp.mTransformation);
+            final Matrix matrix = tmp.mTransformation.getMatrix();
+            t.setMatrix(leash, matrix, tmp.mFloats);
+
+            // The following applies an inverse scale to the clip-rect so that it crops "after" the
+            // scale instead of before.
+            tmp.mVecs[1] = tmp.mVecs[2] = 0;
+            tmp.mVecs[0] = tmp.mVecs[3] = 1;
+            matrix.mapVectors(tmp.mVecs);
+            tmp.mVecs[0] = 1.f / tmp.mVecs[0];
+            tmp.mVecs[3] = 1.f / tmp.mVecs[3];
+            final Rect clipRect = tmp.mTransformation.getClipRect();
+            mTmpRect.left = (int) (clipRect.left * tmp.mVecs[0] + 0.5f);
+            mTmpRect.right = (int) (clipRect.right * tmp.mVecs[0] + 0.5f);
+            mTmpRect.top = (int) (clipRect.top * tmp.mVecs[3] + 0.5f);
+            mTmpRect.bottom = (int) (clipRect.bottom * tmp.mVecs[3] + 0.5f);
+            t.setWindowCrop(leash, mTmpRect);
+        }
+    }
+
+    @Override
+    public long calculateStatusBarTransitionStartTime() {
+        long uptime = SystemClock.uptimeMillis();
+        return Math.max(uptime, uptime + ((long) (((float) mAnimation.getDuration()) * 0.99f))
+                - STATUS_BAR_TRANSITION_DURATION);
+    }
+
+    @Override
+    public boolean canSkipFirstFrame() {
+        return false;
+    }
+
+    @Override
+    public boolean needsEarlyWakeup() {
+        return mIsAppAnimation;
+    }
+
+    @Override
+    public void dump(PrintWriter pw, String prefix) {
+        pw.print(prefix); pw.println(mAnimation.getDuration());
+    }
+
+    @Override
+    public void writeToProtoInner(ProtoOutputStream proto) {
+        final long token = proto.start(WINDOW);
+        proto.write(ANIMATION, mAnimation.toString());
+        proto.end(token);
+    }
+
+    private static class TmpValues {
+        final Transformation mTransformation = new Transformation();
+        final float[] mFloats = new float[9];
+        final float[] mVecs = new float[4];
+    }
+}
diff --git a/services/core/java/com/android/server/wm/WindowManagerInternal.java b/services/core/java/com/android/server/wm/WindowManagerInternal.java
index 5267e7e..e204697 100644
--- a/services/core/java/com/android/server/wm/WindowManagerInternal.java
+++ b/services/core/java/com/android/server/wm/WindowManagerInternal.java
@@ -118,8 +118,6 @@
          *
          * @param transit transition type indicating what kind of transition gets run, must be one
          *                of AppTransition.TRANSIT_* values
-         * @param openToken the token for the opening app
-         * @param closeToken the token for the closing app
          * @param duration the total duration of the transition
          * @param statusBarAnimationStartTime the desired start time for all visual animations in
          *        the status bar caused by this app transition in uptime millis
@@ -131,8 +129,8 @@
          * {@link WindowManagerPolicy#FINISH_LAYOUT_REDO_WALLPAPER},
          * or {@link WindowManagerPolicy#FINISH_LAYOUT_REDO_ANIM}.
          */
-        public int onAppTransitionStartingLocked(int transit, IBinder openToken, IBinder closeToken,
-                long duration, long statusBarAnimationStartTime, long statusBarAnimationDuration) {
+        public int onAppTransitionStartingLocked(int transit, long duration,
+                long statusBarAnimationStartTime, long statusBarAnimationDuration) {
             return 0;
         }
 
diff --git a/services/core/java/com/android/server/wm/WindowManagerService.java b/services/core/java/com/android/server/wm/WindowManagerService.java
index c6679a9..5eff7d8 100644
--- a/services/core/java/com/android/server/wm/WindowManagerService.java
+++ b/services/core/java/com/android/server/wm/WindowManagerService.java
@@ -184,7 +184,6 @@
 import android.util.Slog;
 import android.util.SparseArray;
 import android.util.SparseBooleanArray;
-import android.util.SparseIntArray;
 import android.util.TimeUtils;
 import android.util.TypedValue;
 import android.util.proto.ProtoOutputStream;
@@ -857,12 +856,12 @@
 
         @Override
         public void onAppTransitionCancelledLocked(int transit) {
-            mH.sendEmptyMessage(H.NOTIFY_APP_TRANSITION_CANCELLED);
+            mAtmInternal.notifyAppTransitionCancelled();
         }
 
         @Override
         public void onAppTransitionFinishedLocked(IBinder token) {
-            mH.sendEmptyMessage(H.NOTIFY_APP_TRANSITION_FINISHED);
+            mAtmInternal.notifyAppTransitionFinished();
             final AppWindowToken atoken = mRoot.getAppWindowToken(token);
             if (atoken == null) {
                 return;
@@ -2602,7 +2601,7 @@
 
     @Override
     public void notifyKeyguardTrustedChanged() {
-        mH.sendEmptyMessage(H.NOTIFY_KEYGUARD_TRUSTED_CHANGED);
+        mAtmInternal.notifyKeyguardTrustedChanged();
     }
 
     @Override
@@ -2667,11 +2666,7 @@
      * @param callback Runnable to be called when activity manager is done reevaluating visibilities
      */
     void notifyKeyguardFlagsChanged(@Nullable Runnable callback, int displayId) {
-        final Runnable wrappedCallback = callback != null
-                ? () -> { synchronized (mGlobalLock) { callback.run(); } }
-                : null;
-        mH.obtainMessage(H.NOTIFY_KEYGUARD_FLAGS_CHANGED, displayId, 0,
-                wrappedCallback).sendToTarget();
+        mAtmInternal.notifyKeyguardFlagsChanged(callback, displayId);
     }
 
     public boolean isKeyguardTrusted() {
@@ -4363,16 +4358,10 @@
 
         public static final int WINDOW_REPLACEMENT_TIMEOUT = 46;
 
-        public static final int NOTIFY_APP_TRANSITION_STARTING = 47;
-        public static final int NOTIFY_APP_TRANSITION_CANCELLED = 48;
-        public static final int NOTIFY_APP_TRANSITION_FINISHED = 49;
         public static final int UPDATE_ANIMATION_SCALE = 51;
         public static final int WINDOW_HIDE_TIMEOUT = 52;
-        public static final int NOTIFY_DOCKED_STACK_MINIMIZED_CHANGED = 53;
         public static final int SEAMLESS_ROTATION_TIMEOUT = 54;
         public static final int RESTORE_POINTER_ICON = 55;
-        public static final int NOTIFY_KEYGUARD_FLAGS_CHANGED = 56;
-        public static final int NOTIFY_KEYGUARD_TRUSTED_CHANGED = 57;
         public static final int SET_HAS_OVERLAY_UI = 58;
         public static final int SET_RUNNING_REMOTE_ANIMATION = 59;
         public static final int ANIMATION_FAILSAFE = 60;
@@ -4708,19 +4697,6 @@
                     }
                 }
                 break;
-                case NOTIFY_APP_TRANSITION_STARTING: {
-                    mAtmInternal.notifyAppTransitionStarting((SparseIntArray) msg.obj,
-                            msg.getWhen());
-                }
-                break;
-                case NOTIFY_APP_TRANSITION_CANCELLED: {
-                    mAtmInternal.notifyAppTransitionCancelled();
-                }
-                break;
-                case NOTIFY_APP_TRANSITION_FINISHED: {
-                    mAtmInternal.notifyAppTransitionFinished();
-                }
-                break;
                 case WINDOW_HIDE_TIMEOUT: {
                     final WindowState window = (WindowState) msg.obj;
                     synchronized (mGlobalLock) {
@@ -4742,10 +4718,6 @@
                     }
                 }
                 break;
-                case NOTIFY_DOCKED_STACK_MINIMIZED_CHANGED: {
-                    mAtmInternal.notifyDockedStackMinimizedChanged(msg.arg1 == 1);
-                }
-                break;
                 case RESTORE_POINTER_ICON: {
                     synchronized (mGlobalLock) {
                         restorePointerIconLocked((DisplayContent)msg.obj, msg.arg1, msg.arg2);
@@ -4759,14 +4731,6 @@
                     }
                 }
                 break;
-                case NOTIFY_KEYGUARD_FLAGS_CHANGED: {
-                    mAtmInternal.notifyKeyguardFlagsChanged((Runnable) msg.obj, msg.arg1);
-                }
-                break;
-                case NOTIFY_KEYGUARD_TRUSTED_CHANGED: {
-                    mAtmInternal.notifyKeyguardTrustedChanged();
-                }
-                break;
                 case SET_HAS_OVERLAY_UI: {
                     mAmInternal.setHasOverlayUi(msg.arg1, msg.arg2 == 1);
                 }
diff --git a/services/core/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/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/tests/mockingservicestests/src/com/android/server/AlarmManagerServiceTest.java b/services/tests/mockingservicestests/src/com/android/server/AlarmManagerServiceTest.java
index 6a153d5..6386b3b3 100644
--- a/services/tests/mockingservicestests/src/com/android/server/AlarmManagerServiceTest.java
+++ b/services/tests/mockingservicestests/src/com/android/server/AlarmManagerServiceTest.java
@@ -28,14 +28,18 @@
 import static com.android.dx.mockito.inline.extended.ExtendedMockito.spyOn;
 import static com.android.dx.mockito.inline.extended.ExtendedMockito.verify;
 import static com.android.dx.mockito.inline.extended.ExtendedMockito.when;
+import static com.android.server.AlarmManagerService.ACTIVE_INDEX;
+import static com.android.server.AlarmManagerService.AlarmHandler.APP_STANDBY_BUCKET_CHANGED;
+import static com.android.server.AlarmManagerService.AlarmHandler.APP_STANDBY_PAROLE_CHANGED;
 import static com.android.server.AlarmManagerService.Constants.KEY_ALLOW_WHILE_IDLE_LONG_TIME;
 import static com.android.server.AlarmManagerService.Constants.KEY_ALLOW_WHILE_IDLE_SHORT_TIME;
-import static com.android.server.AlarmManagerService.Constants
-        .KEY_ALLOW_WHILE_IDLE_WHITELIST_DURATION;
+import static com.android.server.AlarmManagerService.Constants.KEY_ALLOW_WHILE_IDLE_WHITELIST_DURATION;
+import static com.android.server.AlarmManagerService.Constants.KEY_APP_STANDBY_QUOTAS_ENABLED;
 import static com.android.server.AlarmManagerService.Constants.KEY_LISTENER_TIMEOUT;
 import static com.android.server.AlarmManagerService.Constants.KEY_MAX_INTERVAL;
 import static com.android.server.AlarmManagerService.Constants.KEY_MIN_FUTURITY;
 import static com.android.server.AlarmManagerService.Constants.KEY_MIN_INTERVAL;
+import static com.android.server.AlarmManagerService.WORKING_INDEX;
 
 import static org.junit.Assert.assertEquals;
 import static org.junit.Assert.assertNotNull;
@@ -58,6 +62,7 @@
 import android.content.Intent;
 import android.os.Handler;
 import android.os.Looper;
+import android.os.Message;
 import android.os.PowerManager;
 import android.os.UserHandle;
 import android.platform.test.annotations.Presubmit;
@@ -65,7 +70,6 @@
 import android.util.Log;
 import android.util.SparseArray;
 
-import androidx.test.filters.SmallTest;
 import androidx.test.runner.AndroidJUnit4;
 
 import com.android.internal.annotations.GuardedBy;
@@ -83,7 +87,6 @@
 import java.util.ArrayList;
 
 @Presubmit
-@SmallTest
 @RunWith(AndroidJUnit4.class)
 public class AlarmManagerServiceTest {
     private static final String TAG = AlarmManagerServiceTest.class.getSimpleName();
@@ -91,7 +94,9 @@
     private static final int SYSTEM_UI_UID = 123456789;
     private static final int TEST_CALLING_UID = 12345;
 
+    private long mAppStandbyWindow;
     private AlarmManagerService mService;
+    private UsageStatsManagerInternal.AppIdleStateChangeListener mAppStandbyListener;
     @Mock
     private ContentResolver mMockResolver;
     @Mock
@@ -229,16 +234,23 @@
         mService = new AlarmManagerService(mMockContext, mInjector);
         spyOn(mService);
         doNothing().when(mService).publishBinderService(any(), any());
-        mService.onStart();
-        mService.onBootPhase(SystemService.PHASE_SYSTEM_SERVICES_READY);
-        spyOn(mService.mHandler);
 
-        assertEquals(0, mService.mConstants.MIN_FUTURITY);
+        mService.onStart();
+        spyOn(mService.mHandler);
         assertEquals(mService.mSystemUiUid, SYSTEM_UI_UID);
         assertEquals(mService.mClockReceiver, mClockReceiver);
         assertEquals(mService.mWakeLock, mWakeLock);
         verify(mIActivityManager).registerUidObserver(any(IUidObserver.class), anyInt(), anyInt(),
                 isNull());
+
+        // Other boot phases don't matter
+        mService.onBootPhase(SystemService.PHASE_SYSTEM_SERVICES_READY);
+        assertEquals(0, mService.mConstants.MIN_FUTURITY);
+        mAppStandbyWindow = mService.mConstants.APP_STANDBY_WINDOW;
+        ArgumentCaptor<UsageStatsManagerInternal.AppIdleStateChangeListener> captor =
+                ArgumentCaptor.forClass(UsageStatsManagerInternal.AppIdleStateChangeListener.class);
+        verify(mUsageStatsManagerInternal).addAppIdleStateChangeListener(captor.capture());
+        mAppStandbyListener = captor.getValue();
     }
 
     private void setTestAlarm(int type, long triggerTime, PendingIntent operation) {
@@ -254,6 +266,28 @@
         return mockPi;
     }
 
+    /**
+     * Careful while calling as this will replace any existing settings for the calling test.
+     */
+    private void setQuotasEnabled(boolean enabled) {
+        final StringBuilder constantsBuilder = new StringBuilder();
+        constantsBuilder.append(KEY_MIN_FUTURITY);
+        constantsBuilder.append("=0,");
+        // Capping active and working quotas to make testing feasible.
+        constantsBuilder.append(mService.mConstants.KEYS_APP_STANDBY_QUOTAS[ACTIVE_INDEX]);
+        constantsBuilder.append("=8,");
+        constantsBuilder.append(mService.mConstants.KEYS_APP_STANDBY_QUOTAS[WORKING_INDEX]);
+        constantsBuilder.append("=5,");
+        if (!enabled) {
+            constantsBuilder.append(KEY_APP_STANDBY_QUOTAS_ENABLED);
+            constantsBuilder.append("=false,");
+        }
+        doReturn(constantsBuilder.toString()).when(() -> Settings.Global.getString(mMockResolver,
+                Settings.Global.ALARM_MANAGER_CONSTANTS));
+        mService.mConstants.onChange(false, null);
+        assertEquals(mService.mConstants.APP_STANDBY_QUOTAS_ENABLED, enabled);
+    }
+
     @Test
     public void testSingleAlarmSet() {
         final long triggerTime = mNowElapsedTest + 5000;
@@ -346,6 +380,7 @@
 
     @Test
     public void testStandbyBucketDelay_workingSet() throws Exception {
+        setQuotasEnabled(false);
         setTestAlarm(ELAPSED_REALTIME_WAKEUP, mNowElapsedTest + 5, getNewMockPendingIntent());
         setTestAlarm(ELAPSED_REALTIME_WAKEUP, mNowElapsedTest + 6, getNewMockPendingIntent());
         assertEquals(mNowElapsedTest + 5, mTestTimer.getElapsed());
@@ -366,6 +401,7 @@
 
     @Test
     public void testStandbyBucketDelay_frequent() throws Exception {
+        setQuotasEnabled(false);
         setTestAlarm(ELAPSED_REALTIME_WAKEUP, mNowElapsedTest + 5, getNewMockPendingIntent());
         setTestAlarm(ELAPSED_REALTIME_WAKEUP, mNowElapsedTest + 6, getNewMockPendingIntent());
         assertEquals(mNowElapsedTest + 5, mTestTimer.getElapsed());
@@ -385,6 +421,7 @@
 
     @Test
     public void testStandbyBucketDelay_rare() throws Exception {
+        setQuotasEnabled(false);
         setTestAlarm(ELAPSED_REALTIME_WAKEUP, mNowElapsedTest + 5, getNewMockPendingIntent());
         setTestAlarm(ELAPSED_REALTIME_WAKEUP, mNowElapsedTest + 6, getNewMockPendingIntent());
         assertEquals(mNowElapsedTest + 5, mTestTimer.getElapsed());
@@ -402,6 +439,253 @@
         assertEquals("Incorrect next alarm trigger.", expectedNextTrigger, mTestTimer.getElapsed());
     }
 
+    private void testQuotasDeferralOnSet(int standbyBucket) throws Exception {
+        final int quota = mService.getQuotaForBucketLocked(standbyBucket);
+        when(mUsageStatsManagerInternal.getAppStandbyBucket(eq(TEST_CALLING_PACKAGE), anyInt(),
+                anyLong())).thenReturn(standbyBucket);
+        final long firstTrigger = mNowElapsedTest + 10;
+        for (int i = 0; i < quota; i++) {
+            setTestAlarm(ELAPSED_REALTIME_WAKEUP, mNowElapsedTest + 10 + i,
+                    getNewMockPendingIntent());
+            mNowElapsedTest = mTestTimer.getElapsed();
+            mTestTimer.expire();
+        }
+        // This one should get deferred on set
+        setTestAlarm(ELAPSED_REALTIME_WAKEUP, mNowElapsedTest + quota + 10,
+                getNewMockPendingIntent());
+        final long expectedNextTrigger = firstTrigger + 1 + mAppStandbyWindow;
+        assertEquals("Incorrect next alarm trigger", expectedNextTrigger, mTestTimer.getElapsed());
+    }
+
+    private void testQuotasDeferralOnExpiration(int standbyBucket) throws Exception {
+        final int quota = mService.getQuotaForBucketLocked(standbyBucket);
+        when(mUsageStatsManagerInternal.getAppStandbyBucket(eq(TEST_CALLING_PACKAGE), anyInt(),
+                anyLong())).thenReturn(standbyBucket);
+        final long firstTrigger = mNowElapsedTest + 10;
+        for (int i = 0; i < quota; i++) {
+            setTestAlarm(ELAPSED_REALTIME_WAKEUP, mNowElapsedTest + 10 + i,
+                    getNewMockPendingIntent());
+        }
+        // This one should get deferred after the latest alarm expires
+        setTestAlarm(ELAPSED_REALTIME_WAKEUP, mNowElapsedTest + quota + 10,
+                getNewMockPendingIntent());
+        for (int i = 0; i < quota; i++) {
+            mNowElapsedTest = mTestTimer.getElapsed();
+            mTestTimer.expire();
+        }
+        final long expectedNextTrigger = firstTrigger + 1 + mAppStandbyWindow;
+        assertEquals("Incorrect next alarm trigger", expectedNextTrigger, mTestTimer.getElapsed());
+    }
+
+    private void testQuotasNoDeferral(int standbyBucket) throws Exception {
+        final int quota = mService.getQuotaForBucketLocked(standbyBucket);
+        when(mUsageStatsManagerInternal.getAppStandbyBucket(eq(TEST_CALLING_PACKAGE), anyInt(),
+                anyLong())).thenReturn(standbyBucket);
+        final long firstTrigger = mNowElapsedTest + 10;
+        for (int i = 0; i < quota; i++) {
+            setTestAlarm(ELAPSED_REALTIME_WAKEUP, mNowElapsedTest + 10 + i,
+                    getNewMockPendingIntent());
+        }
+        // This delivery time maintains the quota invariant. Should not be deferred.
+        final long expectedNextTrigger = firstTrigger + mAppStandbyWindow + 5;
+        setTestAlarm(ELAPSED_REALTIME_WAKEUP, expectedNextTrigger, getNewMockPendingIntent());
+        for (int i = 0; i < quota; i++) {
+            mNowElapsedTest = mTestTimer.getElapsed();
+            mTestTimer.expire();
+        }
+        assertEquals("Incorrect next alarm trigger", expectedNextTrigger, mTestTimer.getElapsed());
+    }
+
+    @Test
+    public void testActiveQuota_deferredOnSet() throws Exception {
+        setQuotasEnabled(true);
+        testQuotasDeferralOnSet(STANDBY_BUCKET_ACTIVE);
+    }
+
+    @Test
+    public void testActiveQuota_deferredOnExpiration() throws Exception {
+        setQuotasEnabled(true);
+        testQuotasDeferralOnExpiration(STANDBY_BUCKET_ACTIVE);
+    }
+
+    @Test
+    public void testActiveQuota_notDeferred() throws Exception {
+        setQuotasEnabled(true);
+        testQuotasNoDeferral(STANDBY_BUCKET_ACTIVE);
+    }
+
+    @Test
+    public void testWorkingQuota_deferredOnSet() throws Exception {
+        setQuotasEnabled(true);
+        testQuotasDeferralOnSet(STANDBY_BUCKET_WORKING_SET);
+    }
+
+    @Test
+    public void testWorkingQuota_deferredOnExpiration() throws Exception {
+        setQuotasEnabled(true);
+        testQuotasDeferralOnExpiration(STANDBY_BUCKET_WORKING_SET);
+    }
+
+    @Test
+    public void testWorkingQuota_notDeferred() throws Exception {
+        setQuotasEnabled(true);
+        testQuotasNoDeferral(STANDBY_BUCKET_WORKING_SET);
+    }
+
+    @Test
+    public void testFrequentQuota_deferredOnSet() throws Exception {
+        setQuotasEnabled(true);
+        testQuotasDeferralOnSet(STANDBY_BUCKET_FREQUENT);
+    }
+
+    @Test
+    public void testFrequentQuota_deferredOnExpiration() throws Exception {
+        setQuotasEnabled(true);
+        testQuotasDeferralOnExpiration(STANDBY_BUCKET_FREQUENT);
+    }
+
+    @Test
+    public void testFrequentQuota_notDeferred() throws Exception {
+        setQuotasEnabled(true);
+        testQuotasNoDeferral(STANDBY_BUCKET_FREQUENT);
+    }
+
+    @Test
+    public void testRareQuota_deferredOnSet() throws Exception {
+        setQuotasEnabled(true);
+        testQuotasDeferralOnSet(STANDBY_BUCKET_RARE);
+    }
+
+    @Test
+    public void testRareQuota_deferredOnExpiration() throws Exception {
+        setQuotasEnabled(true);
+        testQuotasDeferralOnExpiration(STANDBY_BUCKET_RARE);
+    }
+
+    @Test
+    public void testRareQuota_notDeferred() throws Exception {
+        setQuotasEnabled(true);
+        testQuotasNoDeferral(STANDBY_BUCKET_RARE);
+    }
+
+    private void sendAndHandleBucketChanged(int bucket) {
+        when(mUsageStatsManagerInternal.getAppStandbyBucket(eq(TEST_CALLING_PACKAGE), anyInt(),
+                anyLong())).thenReturn(bucket);
+        // Stubbing the handler call to simulate it synchronously here.
+        doReturn(true).when(mService.mHandler).sendMessage(any(Message.class));
+        mAppStandbyListener.onAppIdleStateChanged(TEST_CALLING_PACKAGE,
+                UserHandle.getUserId(TEST_CALLING_UID), false, bucket, 0);
+        final ArgumentCaptor<Message> messageCaptor = ArgumentCaptor.forClass(Message.class);
+        verify(mService.mHandler, atLeastOnce()).sendMessage(messageCaptor.capture());
+        final Message lastMessage = messageCaptor.getValue();
+        assertEquals("Unexpected message send to handler", lastMessage.what,
+                APP_STANDBY_BUCKET_CHANGED);
+        mService.mHandler.handleMessage(lastMessage);
+    }
+
+    @Test
+    public void testQuotaDowngrade() throws Exception {
+        setQuotasEnabled(true);
+        final int workingQuota = mService.getQuotaForBucketLocked(STANDBY_BUCKET_WORKING_SET);
+        when(mUsageStatsManagerInternal.getAppStandbyBucket(eq(TEST_CALLING_PACKAGE), anyInt(),
+                anyLong())).thenReturn(STANDBY_BUCKET_WORKING_SET);
+
+        final long firstTrigger = mNowElapsedTest + 10;
+        for (int i = 0; i < workingQuota; i++) {
+            setTestAlarm(ELAPSED_REALTIME_WAKEUP, firstTrigger + i, getNewMockPendingIntent());
+        }
+        // No deferrals now.
+        for (int i = 0; i < workingQuota - 1; i++) {
+            mNowElapsedTest = mTestTimer.getElapsed();
+            assertEquals(firstTrigger + i, mNowElapsedTest);
+            mTestTimer.expire();
+        }
+        // The next upcoming alarm in queue should also be set as expected.
+        assertEquals(firstTrigger + workingQuota - 1, mTestTimer.getElapsed());
+        // Downgrading the bucket now
+        sendAndHandleBucketChanged(STANDBY_BUCKET_RARE);
+        final int rareQuota = mService.getQuotaForBucketLocked(STANDBY_BUCKET_RARE);
+        // The last alarm should now be deferred.
+        final long expectedNextTrigger = (firstTrigger + workingQuota - 1 - rareQuota)
+                + mAppStandbyWindow + 1;
+        assertEquals("Incorrect next alarm trigger", expectedNextTrigger, mTestTimer.getElapsed());
+    }
+
+    @Test
+    public void testQuotaUpgrade() throws Exception {
+        setQuotasEnabled(true);
+        final int frequentQuota = mService.getQuotaForBucketLocked(STANDBY_BUCKET_FREQUENT);
+        when(mUsageStatsManagerInternal.getAppStandbyBucket(eq(TEST_CALLING_PACKAGE), anyInt(),
+                anyLong())).thenReturn(STANDBY_BUCKET_FREQUENT);
+
+        final long firstTrigger = mNowElapsedTest + 10;
+        for (int i = 0; i < frequentQuota + 1; i++) {
+            setTestAlarm(ELAPSED_REALTIME_WAKEUP, firstTrigger + i, getNewMockPendingIntent());
+            if (i < frequentQuota) {
+                mNowElapsedTest = mTestTimer.getElapsed();
+                mTestTimer.expire();
+            }
+        }
+        // The last alarm should be deferred due to exceeding the quota
+        final long deferredTrigger = firstTrigger + 1 + mAppStandbyWindow;
+        assertEquals(deferredTrigger, mTestTimer.getElapsed());
+
+        // Upgrading the bucket now
+        sendAndHandleBucketChanged(STANDBY_BUCKET_ACTIVE);
+        // The last alarm should now be rescheduled to go as per original expectations
+        final long originalTrigger = firstTrigger + frequentQuota;
+        assertEquals("Incorrect next alarm trigger", originalTrigger, mTestTimer.getElapsed());
+    }
+
+    private void sendAndHandleParoleChanged(boolean parole) {
+        // Stubbing the handler call to simulate it synchronously here.
+        doReturn(true).when(mService.mHandler).sendMessage(any(Message.class));
+        mAppStandbyListener.onParoleStateChanged(parole);
+        final ArgumentCaptor<Message> messageCaptor = ArgumentCaptor.forClass(Message.class);
+        verify(mService.mHandler, atLeastOnce()).sendMessage(messageCaptor.capture());
+        final Message lastMessage = messageCaptor.getValue();
+        assertEquals("Unexpected message send to handler", lastMessage.what,
+                APP_STANDBY_PAROLE_CHANGED);
+        mService.mHandler.handleMessage(lastMessage);
+    }
+
+    @Test
+    public void testParole() throws Exception {
+        setQuotasEnabled(true);
+        final int workingQuota = mService.getQuotaForBucketLocked(STANDBY_BUCKET_WORKING_SET);
+        when(mUsageStatsManagerInternal.getAppStandbyBucket(eq(TEST_CALLING_PACKAGE), anyInt(),
+                anyLong())).thenReturn(STANDBY_BUCKET_WORKING_SET);
+
+        final long firstTrigger = mNowElapsedTest + 10;
+        final int totalAlarms = workingQuota + 10;
+        for (int i = 0; i < totalAlarms; i++) {
+            setTestAlarm(ELAPSED_REALTIME_WAKEUP, firstTrigger + i, getNewMockPendingIntent());
+        }
+        // Use up the quota, no deferrals expected.
+        for (int i = 0; i < workingQuota; i++) {
+            mNowElapsedTest = mTestTimer.getElapsed();
+            assertEquals(firstTrigger + i, mNowElapsedTest);
+            mTestTimer.expire();
+        }
+        // Any subsequent alarms in queue should all be deferred
+        assertEquals(firstTrigger + mAppStandbyWindow + 1, mTestTimer.getElapsed());
+        // Paroling now
+        sendAndHandleParoleChanged(true);
+
+        // Subsequent alarms should now go off as per original expectations.
+        for (int i = 0; i < 5; i++) {
+            mNowElapsedTest = mTestTimer.getElapsed();
+            assertEquals(firstTrigger + workingQuota + i, mNowElapsedTest);
+            mTestTimer.expire();
+        }
+        // Come out of parole
+        sendAndHandleParoleChanged(false);
+
+        // Subsequent alarms should again get deferred
+        final long expectedNextTrigger = (firstTrigger + 5) + 1 + mAppStandbyWindow;
+        assertEquals("Incorrect next alarm trigger", expectedNextTrigger, mTestTimer.getElapsed());
+    }
+
     @Test
     public void testAlarmRestrictedInBatterSaver() throws Exception {
         final ArgumentCaptor<AppStateTracker.Listener> listenerArgumentCaptor =
diff --git a/services/tests/servicestests/src/com/android/server/power/batterysaver/BatterySaverStateMachineTest.java b/services/tests/mockingservicestests/src/com/android/server/power/batterysaver/BatterySaverStateMachineTest.java
similarity index 73%
rename from services/tests/servicestests/src/com/android/server/power/batterysaver/BatterySaverStateMachineTest.java
rename to services/tests/mockingservicestests/src/com/android/server/power/batterysaver/BatterySaverStateMachineTest.java
index f31ca55..5009d64 100644
--- a/services/tests/servicestests/src/com/android/server/power/batterysaver/BatterySaverStateMachineTest.java
+++ b/services/tests/mockingservicestests/src/com/android/server/power/batterysaver/BatterySaverStateMachineTest.java
@@ -19,51 +19,41 @@
 import static org.mockito.ArgumentMatchers.anyBoolean;
 import static org.mockito.ArgumentMatchers.anyInt;
 import static org.mockito.Mockito.doAnswer;
+import static org.mockito.Mockito.doReturn;
 import static org.mockito.Mockito.mock;
 import static org.mockito.Mockito.when;
 
+import android.app.NotificationManager;
 import android.content.ContentResolver;
+import android.content.Context;
 import android.content.res.Resources;
 import android.provider.Settings.Global;
-import android.test.mock.MockContext;
 
 import androidx.test.filters.SmallTest;
 import androidx.test.runner.AndroidJUnit4;
 
-import com.google.common.base.Objects;
-
 import org.junit.Before;
 import org.junit.Test;
 import org.junit.runner.RunWith;
 
 import java.util.HashMap;
+import java.util.Objects;
 
 /**
- atest $ANDROID_BUILD_TOP/frameworks/base/services/tests/servicestests/src/com/android/server/power/batterysaver/BatterySaverStateMachineTest.java
+ * atest com.android.server.power.batterysaver.BatterySaverStateMachineTest
  */
 @SmallTest
 @RunWith(AndroidJUnit4.class)
 public class BatterySaverStateMachineTest {
 
-    private MyMockContext mMockContext;
+    private Context mMockContext;
     private ContentResolver mMockContextResolver;
     private BatterySaverController mMockBatterySaverController;
+    private NotificationManager mMockNotificationManager;
     private Device mDevice;
     private TestableBatterySaverStateMachine mTarget;
     private Resources mMockResources;
 
-    private class MyMockContext extends MockContext {
-        @Override
-        public ContentResolver getContentResolver() {
-            return mMockContextResolver;
-        }
-
-        @Override
-        public Resources getResources() {
-            return mMockResources;
-        }
-    }
-
     private DevicePersistedState mPersistedState;
 
     private class DevicePersistedState {
@@ -116,6 +106,10 @@
                     mPersistedState.global.getOrDefault(Global.LOW_POWER_MODE, 0) != 0,
                     mPersistedState.global.getOrDefault(Global.LOW_POWER_MODE_STICKY, 0) != 0,
                     mDevice.getLowPowerModeTriggerLevel(),
+                    mPersistedState.global.getOrDefault(
+                            Global.LOW_POWER_MODE_STICKY_AUTO_DISABLE_ENABLED, 0) != 0,
+                    mPersistedState.global.getOrDefault(
+                            Global.LOW_POWER_MODE_STICKY_AUTO_DISABLE_LEVEL, 90),
                     mPersistedState.global.getOrDefault(Global.AUTOMATIC_POWER_SAVER_MODE, 0),
                     mPersistedState.global.getOrDefault(
                             Global.DYNAMIC_POWER_SAVINGS_ENABLED, 0) != 0,
@@ -137,13 +131,13 @@
      * Test target class.
      */
     private class TestableBatterySaverStateMachine extends BatterySaverStateMachine {
-        public TestableBatterySaverStateMachine() {
+        TestableBatterySaverStateMachine() {
             super(new Object(), mMockContext, mMockBatterySaverController);
         }
 
         @Override
         protected void putGlobalSetting(String key, int value) {
-            if (Objects.equal(mPersistedState.global.get(key), value)) {
+            if (Objects.equals(mPersistedState.global.get(key), value)) {
                 return;
             }
             mDevice.putGlobalSetting(key, value);
@@ -163,15 +157,25 @@
         void runOnBgThreadLazy(Runnable r, int delayMillis) {
             r.run();
         }
+
+        @Override
+        void triggerDynamicModeNotification() {
+            // Do nothing
+        }
     }
 
     @Before
     public void setUp() {
-        mMockContext = new MyMockContext();
+        mMockContext = mock(Context.class);
         mMockContextResolver = mock(ContentResolver.class);
         mMockBatterySaverController = mock(BatterySaverController.class);
+        mMockNotificationManager = mock(NotificationManager.class);
         mMockResources = mock(Resources.class);
 
+        doReturn(mMockContextResolver).when(mMockContext).getContentResolver();
+        doReturn(mMockResources).when(mMockContext).getResources();
+        doReturn(mMockNotificationManager).when(mMockContext)
+                .getSystemService(NotificationManager.class);
         doAnswer((inv) -> mDevice.batterySaverEnabled = inv.getArgument(0))
                 .when(mMockBatterySaverController).enableBatterySaver(anyBoolean(), anyInt());
         when(mMockBatterySaverController.isEnabled())
@@ -446,8 +450,9 @@
     }
 
     @Test
-    public void testAutoBatterySaver_withSticky() {
+    public void testAutoBatterySaver_withSticky_withAutoOffDisabled() {
         mDevice.putGlobalSetting(Global.LOW_POWER_MODE_TRIGGER_LEVEL, 50);
+        mDevice.putGlobalSetting(Global.LOW_POWER_MODE_STICKY_AUTO_DISABLE_ENABLED, 0);
 
         mTarget.setBatterySaverEnabledManually(true);
 
@@ -518,6 +523,197 @@
     }
 
     @Test
+    public void testAutoBatterySaver_withSticky_withAutoOffEnabled() {
+        mDevice.putGlobalSetting(Global.LOW_POWER_MODE_TRIGGER_LEVEL, 50);
+        mDevice.putGlobalSetting(Global.LOW_POWER_MODE_STICKY_AUTO_DISABLE_ENABLED, 1);
+        mDevice.putGlobalSetting(Global.LOW_POWER_MODE_STICKY_AUTO_DISABLE_LEVEL, 90);
+
+        // Scenario 1: User turns BS on manually above the threshold, it shouldn't turn off even
+        // with battery level change above threshold.
+        mDevice.setBatteryLevel(100);
+        mTarget.setBatterySaverEnabledManually(true);
+
+        assertEquals(true, mDevice.batterySaverEnabled);
+        assertEquals(100, mPersistedState.batteryLevel);
+        assertEquals(false, mPersistedState.batteryLow);
+
+        mDevice.setBatteryLevel(95);
+
+        assertEquals(true, mDevice.batterySaverEnabled); // Stays on.
+        assertEquals(95, mPersistedState.batteryLevel);
+        assertEquals(false, mPersistedState.batteryLow);
+
+        // Scenario 2: User turns BS on manually above the threshold then charges device. BS
+        // shouldn't turn back on.
+        mDevice.setPowered(true);
+
+        assertEquals(false, mDevice.batterySaverEnabled);
+        assertEquals(95, mPersistedState.batteryLevel);
+        assertEquals(false, mPersistedState.batteryLow);
+
+        mDevice.setBatteryLevel(97);
+        mDevice.setPowered(false);
+
+        assertEquals(false, mDevice.batterySaverEnabled); // Sticky BS no longer enabled.
+        assertEquals(97, mPersistedState.batteryLevel);
+        assertEquals(false, mPersistedState.batteryLow);
+
+        // Scenario 3: User turns BS on manually above the threshold. Device drains below
+        // threshold and then charged to below threshold. Sticky BS should activate.
+        mTarget.setBatterySaverEnabledManually(true);
+        mDevice.setBatteryLevel(30);
+
+        assertEquals(true, mDevice.batterySaverEnabled);
+        assertEquals(30, mPersistedState.batteryLevel);
+        assertEquals(true, mPersistedState.batteryLow);
+
+        mDevice.setPowered(true);
+        mDevice.setBatteryLevel(80);
+
+        assertEquals(false, mDevice.batterySaverEnabled);
+        assertEquals(80, mPersistedState.batteryLevel);
+        assertEquals(false, mPersistedState.batteryLow);
+
+        mDevice.setPowered(false);
+
+        assertEquals(true, mDevice.batterySaverEnabled); // Still enabled.
+        assertEquals(80, mPersistedState.batteryLevel);
+        assertEquals(false, mPersistedState.batteryLow);
+
+        mDevice.setBatteryLevel(30);
+
+        assertEquals(true, mDevice.batterySaverEnabled);
+        assertEquals(30, mPersistedState.batteryLevel);
+        assertEquals(true, mPersistedState.batteryLow);
+
+        // Scenario 4: User turns BS on manually above the threshold. Device drains below
+        // threshold and is eventually charged to above threshold. Sticky BS should turn off.
+        mDevice.setPowered(true);
+        mDevice.setBatteryLevel(90);
+
+        assertEquals(false, mDevice.batterySaverEnabled);
+        assertEquals(90, mPersistedState.batteryLevel);
+        assertEquals(false, mPersistedState.batteryLow);
+
+        mDevice.setPowered(false);
+
+        assertEquals(false, mDevice.batterySaverEnabled); // Sticky BS no longer enabled.
+        assertEquals(90, mPersistedState.batteryLevel);
+        assertEquals(false, mPersistedState.batteryLow);
+
+        // Scenario 5: User turns BS on manually below threshold and charges to below threshold.
+        // Sticky BS should activate.
+        mDevice.setBatteryLevel(70);
+
+        assertEquals(false, mDevice.batterySaverEnabled);
+        assertEquals(70, mPersistedState.batteryLevel);
+        assertEquals(false, mPersistedState.batteryLow);
+
+        mTarget.setBatterySaverEnabledManually(true);
+
+        assertEquals(true, mDevice.batterySaverEnabled);
+        assertEquals(70, mPersistedState.batteryLevel);
+        assertEquals(false, mPersistedState.batteryLow);
+
+        mDevice.setPowered(true);
+        mDevice.setBatteryLevel(80);
+
+        assertEquals(false, mDevice.batterySaverEnabled);
+        assertEquals(80, mPersistedState.batteryLevel);
+        assertEquals(false, mPersistedState.batteryLow);
+
+        mDevice.setPowered(false);
+
+        assertEquals(true, mDevice.batterySaverEnabled); // Sticky BS still on.
+        assertEquals(80, mPersistedState.batteryLevel);
+        assertEquals(false, mPersistedState.batteryLow);
+
+        // Scenario 6: User turns BS on manually below threshold and eventually charges to above
+        // threshold. Sticky BS should turn off.
+
+        mDevice.setPowered(true);
+        mDevice.setBatteryLevel(95);
+        mDevice.setPowered(false);
+
+        assertEquals(false, mDevice.batterySaverEnabled);
+        assertEquals(95, mPersistedState.batteryLevel);
+        assertEquals(false, mPersistedState.batteryLow);
+
+        // Scenario 7: User turns BS on above threshold and then reboots device. Sticky BS
+        // shouldn't activate.
+        mTarget.setBatterySaverEnabledManually(true);
+        mPersistedState.batteryLevel = 93;
+
+        initDevice();
+
+        assertEquals(false, mDevice.batterySaverEnabled);
+        assertEquals(93, mPersistedState.batteryLevel);
+        assertEquals(false, mPersistedState.batteryLow);
+
+        // Scenario 8: User turns BS on below threshold and then reboots device without charging.
+        // Sticky BS should activate.
+        mDevice.setBatteryLevel(75);
+        mTarget.setBatterySaverEnabledManually(true);
+        assertEquals(true, mDevice.batterySaverEnabled);
+        assertEquals(75, mPersistedState.batteryLevel);
+        assertEquals(false, mPersistedState.batteryLow);
+
+        initDevice();
+
+        assertEquals(true, mDevice.batterySaverEnabled);
+        assertEquals(75, mPersistedState.batteryLevel);
+        assertEquals(false, mPersistedState.batteryLow);
+
+        // Scenario 9: User turns BS on below threshold and then reboots device after charging
+        // above threshold. Sticky BS shouldn't activate.
+        mDevice.setBatteryLevel(80);
+        mTarget.setBatterySaverEnabledManually(true);
+        mPersistedState.batteryLevel = 100;
+
+        initDevice();
+
+        assertEquals(false, mDevice.batterySaverEnabled);
+        assertEquals(100, mPersistedState.batteryLevel);
+        assertEquals(false, mPersistedState.batteryLow);
+
+        // Scenario 10: Somehow autoDisableLevel is set to a value below lowPowerModeTriggerLevel
+        // and then user enables manually above both thresholds, discharges below
+        // autoDisableLevel and then charges up to between autoDisableLevel and
+        // lowPowerModeTriggerLevel. Sticky BS shouldn't activate, but BS should still be on
+        // because the level is below lowPowerModeTriggerLevel.
+        mDevice.putGlobalSetting(Global.LOW_POWER_MODE_TRIGGER_LEVEL, 75);
+        mDevice.putGlobalSetting(Global.LOW_POWER_MODE_STICKY_AUTO_DISABLE_ENABLED, 1);
+        mDevice.putGlobalSetting(Global.LOW_POWER_MODE_STICKY_AUTO_DISABLE_LEVEL, 60);
+        initDevice();
+
+        mDevice.setBatteryLevel(90);
+        mTarget.setBatterySaverEnabledManually(true);
+
+        assertEquals(true, mDevice.batterySaverEnabled);
+        assertEquals(90, mPersistedState.batteryLevel);
+        assertEquals(false, mPersistedState.batteryLow);
+
+        mDevice.setBatteryLevel(50);
+
+        assertEquals(true, mDevice.batterySaverEnabled);
+        assertEquals(50, mPersistedState.batteryLevel);
+        assertEquals(true, mPersistedState.batteryLow);
+
+        mDevice.setPowered(true);
+        mDevice.setBatteryLevel(65);
+
+        assertEquals(false, mDevice.batterySaverEnabled);
+        assertEquals(65, mPersistedState.batteryLevel);
+        assertEquals(true, mPersistedState.batteryLow);
+
+        mDevice.setPowered(false);
+
+        assertEquals(true, mDevice.batterySaverEnabled);
+        assertEquals(65, mPersistedState.batteryLevel);
+        assertEquals(true, mPersistedState.batteryLow);
+    }
+
+    @Test
     public void testAutoBatterySaver_withStickyDisabled() {
         when(mMockResources.getBoolean(
                 com.android.internal.R.bool.config_batterySaverStickyBehaviourDisabled))
diff --git a/services/tests/servicestests/src/com/android/server/accessibility/AccessibilityInputFilterTest.java b/services/tests/servicestests/src/com/android/server/accessibility/AccessibilityInputFilterTest.java
new file mode 100644
index 0000000..782dc3e
--- /dev/null
+++ b/services/tests/servicestests/src/com/android/server/accessibility/AccessibilityInputFilterTest.java
@@ -0,0 +1,275 @@
+/*
+ * Copyright (C) 2018 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.accessibility;
+
+import static android.view.Display.DEFAULT_DISPLAY;
+import static android.view.DisplayAdjustments.DEFAULT_DISPLAY_ADJUSTMENTS;
+import static android.view.WindowManagerPolicyConstants.FLAG_PASS_TO_USER;
+
+import static com.android.server.accessibility.AccessibilityInputFilter.FLAG_FEATURE_AUTOCLICK;
+import static com.android.server.accessibility.AccessibilityInputFilter.FLAG_FEATURE_CONTROL_SCREEN_MAGNIFIER;
+import static com.android.server.accessibility.AccessibilityInputFilter.FLAG_FEATURE_FILTER_KEY_EVENTS;
+import static com.android.server.accessibility.AccessibilityInputFilter.FLAG_FEATURE_INJECT_MOTION_EVENTS;
+import static com.android.server.accessibility.AccessibilityInputFilter.FLAG_FEATURE_TOUCH_EXPLORATION;
+import static com.android.server.accessibility.AccessibilityInputFilter.FLAG_FEATURE_TRIGGERED_SCREEN_MAGNIFIER;
+
+import static org.junit.Assert.assertEquals;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.spy;
+import static org.mockito.Mockito.when;
+
+import android.content.Context;
+import android.hardware.display.DisplayManagerGlobal;
+import android.os.Looper;
+import android.os.SystemClock;
+import android.util.SparseArray;
+import android.view.Display;
+import android.view.DisplayInfo;
+import android.view.InputDevice;
+import android.view.InputEvent;
+import android.view.KeyEvent;
+import android.view.MotionEvent;
+
+import androidx.test.InstrumentationRegistry;
+import androidx.test.runner.AndroidJUnit4;
+
+import org.junit.After;
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.mockito.MockitoAnnotations;
+
+import java.util.ArrayList;
+import java.util.List;
+
+/**
+ * Tests for AccessibilityInputFilterTest
+ */
+@RunWith(AndroidJUnit4.class)
+public class AccessibilityInputFilterTest {
+    private static int sNextDisplayId = DEFAULT_DISPLAY;
+    private static final int SECOND_DISPLAY = DEFAULT_DISPLAY + 1;
+    private static final float DEFAULT_X = 100f;
+    private static final float DEFAULT_Y = 100f;
+
+    private final SparseArray<EventStreamTransformation> mEventHandler = new SparseArray<>(0);
+    private final ArrayList<Display> mDisplayList = new ArrayList<>();
+    private final int mFeatures = FLAG_FEATURE_AUTOCLICK
+            | FLAG_FEATURE_TOUCH_EXPLORATION
+            | FLAG_FEATURE_CONTROL_SCREEN_MAGNIFIER
+            | FLAG_FEATURE_TRIGGERED_SCREEN_MAGNIFIER
+            | FLAG_FEATURE_INJECT_MOTION_EVENTS
+            | FLAG_FEATURE_FILTER_KEY_EVENTS;
+
+    // The expected order of EventStreamTransformations.
+    private final Class[] mExpectedEventHandlerTypes =
+            {KeyboardInterceptor.class, MotionEventInjector.class,
+                    MagnificationGestureHandler.class, TouchExplorer.class,
+                    AutoclickController.class, AccessibilityInputFilter.class};
+
+    private MagnificationController mMockMagnificationController;
+    private AccessibilityManagerService mAms;
+    private AccessibilityInputFilter mA11yInputFilter;
+    private EventCaptor mCaptor1;
+    private EventCaptor mCaptor2;
+    private long mLastDownTime = Integer.MIN_VALUE;
+
+    private class EventCaptor implements EventStreamTransformation {
+        List<InputEvent> mEvents = new ArrayList<>();
+
+        @Override
+        public void onMotionEvent(MotionEvent event, MotionEvent rawEvent, int policyFlags) {
+            mEvents.add(event.copy());
+        }
+
+        @Override
+        public void onKeyEvent(KeyEvent event, int policyFlags) {
+            mEvents.add(event.copy());
+        }
+
+        @Override
+        public void setNext(EventStreamTransformation next) {
+        }
+
+        @Override
+        public EventStreamTransformation getNext() {
+            return null;
+        }
+
+        @Override
+        public void clearEvents(int inputSource) {
+            clear();
+        }
+
+        private void clear() {
+            mEvents.clear();
+        }
+    }
+
+    @Before
+    public void setUp() {
+        MockitoAnnotations.initMocks(this);
+        Context context = InstrumentationRegistry.getContext();
+
+        setDisplayCount(1);
+        mAms = spy(new AccessibilityManagerService(context));
+        mMockMagnificationController = mock(MagnificationController.class);
+        mA11yInputFilter = new AccessibilityInputFilter(context, mAms, mEventHandler);
+        mA11yInputFilter.onInstalled();
+
+        when(mAms.getValidDisplayList()).thenReturn(mDisplayList);
+        when(mAms.getMagnificationController()).thenReturn(mMockMagnificationController);
+    }
+
+    @After
+    public void tearDown() {
+        mA11yInputFilter.onUninstalled();
+    }
+
+    @Test
+    public void testEventHandler_shouldChangeAfterSetUserAndEnabledFeatures() {
+        prepareLooper();
+
+        // Check if there is no mEventHandler when no feature is set.
+        assertEquals(0, mEventHandler.size());
+
+        // Check if mEventHandler is added/removed after setting a11y features.
+        mA11yInputFilter.setUserAndEnabledFeatures(0, mFeatures);
+        assertEquals(1, mEventHandler.size());
+
+        mA11yInputFilter.setUserAndEnabledFeatures(0, 0);
+        assertEquals(0, mEventHandler.size());
+    }
+
+    @Test
+    public void testEventHandler_shouldChangeAfterOnDisplayChanged() {
+        prepareLooper();
+
+        // Check if there is only one mEventHandler when there is one default display.
+        mA11yInputFilter.setUserAndEnabledFeatures(0, mFeatures);
+        assertEquals(1, mEventHandler.size());
+
+        // Check if it has correct numbers of mEventHandler for corresponding displays.
+        setDisplayCount(4);
+        mA11yInputFilter.onDisplayChanged();
+        assertEquals(4, mEventHandler.size());
+
+        setDisplayCount(2);
+        mA11yInputFilter.onDisplayChanged();
+        assertEquals(2, mEventHandler.size());
+    }
+
+    @Test
+    public void testEventHandler_shouldHaveCorrectOrderForEventStreamTransformation() {
+        prepareLooper();
+
+        setDisplayCount(2);
+        mA11yInputFilter.setUserAndEnabledFeatures(0, mFeatures);
+        assertEquals(2, mEventHandler.size());
+
+        // Check if mEventHandler for each display has correct order of the
+        // EventStreamTransformations.
+        EventStreamTransformation next = mEventHandler.get(DEFAULT_DISPLAY);
+        for (int i = 0; next != null; i++) {
+            assertEquals(next.getClass(), mExpectedEventHandlerTypes[i]);
+            next = next.getNext();
+        }
+
+        next = mEventHandler.get(SECOND_DISPLAY);
+        // Start from index 1 because KeyboardInterceptor only exists in EventHandler for
+        // DEFAULT_DISPLAY.
+        for (int i = 1; next != null; i++) {
+            assertEquals(next.getClass(), mExpectedEventHandlerTypes[i]);
+            next = next.getNext();
+        }
+    }
+
+    @Test
+    public void testInputEvent_shouldDispatchToCorrespondingEventHandlers() {
+        prepareLooper();
+
+        setDisplayCount(2);
+        mA11yInputFilter.setUserAndEnabledFeatures(0, mFeatures);
+        assertEquals(2, mEventHandler.size());
+
+        mCaptor1 = new EventCaptor();
+        mCaptor2 = new EventCaptor();
+        mEventHandler.put(DEFAULT_DISPLAY, mCaptor1);
+        mEventHandler.put(SECOND_DISPLAY, mCaptor2);
+
+        // InputEvent with different displayId should be dispatched to corresponding EventHandler.
+        send(downEvent(DEFAULT_DISPLAY, InputDevice.SOURCE_TOUCHSCREEN));
+        send(downEvent(SECOND_DISPLAY, InputDevice.SOURCE_TOUCHSCREEN));
+
+        assertEquals(1, mCaptor1.mEvents.size());
+        assertEquals(1, mCaptor2.mEvents.size());
+    }
+
+    @Test
+    public void testInputEvent_shouldClearEventsForAllEventHandlers() {
+        prepareLooper();
+
+        mA11yInputFilter.setUserAndEnabledFeatures(0, mFeatures);
+        assertEquals(1, mEventHandler.size());
+
+        mCaptor1 = new EventCaptor();
+        mEventHandler.put(DEFAULT_DISPLAY, mCaptor1);
+
+        send(downEvent(DEFAULT_DISPLAY, InputDevice.SOURCE_TOUCHSCREEN));
+        send(downEvent(DEFAULT_DISPLAY, InputDevice.SOURCE_TOUCHSCREEN));
+        assertEquals(2, mCaptor1.mEvents.size());
+
+        // InputEvent with different input source should trigger clearEvents() for each
+        // EventStreamTransformation in EventHandler.
+        send(downEvent(DEFAULT_DISPLAY, InputDevice.SOURCE_MOUSE));
+        assertEquals(1, mCaptor1.mEvents.size());
+    }
+
+    private static void prepareLooper() {
+        if (Looper.myLooper() == null) {
+            Looper.prepare();
+        }
+    }
+
+    private Display createStubDisplay(DisplayInfo displayInfo) {
+        final int displayId = sNextDisplayId++;
+        final Display display = new Display(DisplayManagerGlobal.getInstance(), displayId,
+                displayInfo, DEFAULT_DISPLAY_ADJUSTMENTS);
+        return display;
+    }
+
+    private void setDisplayCount(int count) {
+        sNextDisplayId = DEFAULT_DISPLAY;
+        mDisplayList.clear();
+        for (int i = 0; i < count; i++) {
+            mDisplayList.add(createStubDisplay(new DisplayInfo()));
+        }
+    }
+
+    private void send(InputEvent event) {
+        mA11yInputFilter.onInputEvent(event, /* policyFlags */ FLAG_PASS_TO_USER);
+    }
+
+    private MotionEvent downEvent(int displayId, int source) {
+        mLastDownTime = SystemClock.uptimeMillis();
+        final MotionEvent ev = MotionEvent.obtain(mLastDownTime, mLastDownTime,
+                MotionEvent.ACTION_DOWN, DEFAULT_X, DEFAULT_Y, 0);
+        ev.setDisplayId(displayId);
+        ev.setSource(source);
+        return ev;
+    }
+}
diff --git a/services/tests/servicestests/src/com/android/server/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/uiservicestests/src/com/android/server/notification/NotificationManagerServiceTest.java b/services/tests/uiservicestests/src/com/android/server/notification/NotificationManagerServiceTest.java
index c0f9b80..6222923 100644
--- a/services/tests/uiservicestests/src/com/android/server/notification/NotificationManagerServiceTest.java
+++ b/services/tests/uiservicestests/src/com/android/server/notification/NotificationManagerServiceTest.java
@@ -245,8 +245,8 @@
         }
 
         @Override
-        void logSmartSuggestionsVisible(NotificationRecord r) {
-            super.logSmartSuggestionsVisible(r);
+        void logSmartSuggestionsVisible(NotificationRecord r, int notificationLocation) {
+            super.logSmartSuggestionsVisible(r, notificationLocation);
             countLogSmartSuggestionsVisible++;
         }
 
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/RemoteAnimationControllerTest.java b/services/tests/wmtests/src/com/android/server/wm/RemoteAnimationControllerTest.java
index 413b6f4..9478be9 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;
@@ -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/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..d0b7a5e8 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;
@@ -61,6 +67,8 @@
 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;
@@ -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 =
@@ -134,6 +143,14 @@
     // Uploads logs only when the connection status is changes.
     private final HashMap<String, Boolean> mConnected = new HashMap<>();
 
+    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;
         try {
@@ -164,6 +181,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 +285,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 +509,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 +600,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 +613,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 +648,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 +783,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 +828,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 +888,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 +908,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 +944,19 @@
     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();
     }
 
     private void sendPortChangedBroadcastLocked(PortInfo portInfo) {
@@ -759,6 +1011,11 @@
                     }
                     break;
                 }
+                case MSG_SYSTEM_READY: {
+                    mNotificationManager = (NotificationManager)
+                            mContext.getSystemService(Context.NOTIFICATION_SERVICE);
+                    break;
+                }
             }
         }
     };
@@ -784,8 +1041,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 +1067,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 +1156,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 +1213,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/startop/OWNERS b/startop/OWNERS
index 762cd8e..5cf9582 100644
--- a/startop/OWNERS
+++ b/startop/OWNERS
@@ -2,5 +2,5 @@
 chriswailes@google.com
 eholk@google.com
 iam@google.com
-sehr@google.com
 mathieuc@google.com
+sehr@google.com
diff --git a/telecomm/java/android/telecom/CallScreeningService.java b/telecomm/java/android/telecom/CallScreeningService.java
index 826ad82..818ebd9 100644
--- a/telecomm/java/android/telecom/CallScreeningService.java
+++ b/telecomm/java/android/telecom/CallScreeningService.java
@@ -16,6 +16,7 @@
 
 package android.telecom;
 
+import android.annotation.IntDef;
 import android.annotation.NonNull;
 import android.annotation.SdkConstant;
 import android.app.Service;
@@ -32,6 +33,9 @@
 import com.android.internal.telecom.ICallScreeningAdapter;
 import com.android.internal.telecom.ICallScreeningService;
 
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+
 /**
  * This service can be implemented by the default dialer (see
  * {@link TelecomManager#getDefaultDialerPackage()}) or a third party app to allow or disallow
@@ -88,6 +92,128 @@
  * </pre>
  */
 public abstract class CallScreeningService extends Service {
+
+    /** @hide */
+    @Retention(RetentionPolicy.SOURCE)
+    @IntDef(
+            prefix = { "CALL_DURATION_" },
+            value = {CALL_DURATION_VERY_SHORT, CALL_DURATION_SHORT, CALL_DURATION_MEDIUM,
+                    CALL_DURATION_LONG})
+    public @interface CallDuration {}
+
+    /**
+     * Call duration reported with {@link #EXTRA_CALL_DURATION} to indicate to the
+     * {@link CallScreeningService} the duration of a call for which the user reported the nuisance
+     * status (see {@link TelecomManager#reportNuisanceCallStatus(Uri, boolean)}).  The
+     * {@link CallScreeningService} can use this as a signal for training nuisance detection
+     * algorithms.  The call duration is reported in coarse grained buckets to minimize exposure of
+     * identifying call log information to the {@link CallScreeningService}.
+     * <p>
+     * Indicates the call was < 3 seconds in duration.
+     */
+    public static final int CALL_DURATION_VERY_SHORT = 1;
+
+    /**
+     * Call duration reported with {@link #EXTRA_CALL_DURATION} to indicate to the
+     * {@link CallScreeningService} the duration of a call for which the user reported the nuisance
+     * status (see {@link TelecomManager#reportNuisanceCallStatus(Uri, boolean)}).  The
+     * {@link CallScreeningService} can use this as a signal for training nuisance detection
+     * algorithms.  The call duration is reported in coarse grained buckets to minimize exposure of
+     * identifying call log information to the {@link CallScreeningService}.
+     * <p>
+     * Indicates the call was greater than 3 seconds, but less than 60 seconds in duration.
+     */
+    public static final int CALL_DURATION_SHORT = 2;
+
+    /**
+     * Call duration reported with {@link #EXTRA_CALL_DURATION} to indicate to the
+     * {@link CallScreeningService} the duration of a call for which the user reported the nuisance
+     * status (see {@link TelecomManager#reportNuisanceCallStatus(Uri, boolean)}).  The
+     * {@link CallScreeningService} can use this as a signal for training nuisance detection
+     * algorithms.  The call duration is reported in coarse grained buckets to minimize exposure of
+     * identifying call log information to the {@link CallScreeningService}.
+     * <p>
+     * Indicates the call was greater than 60 seconds, but less than 120 seconds in duration.
+     */
+    public static final int CALL_DURATION_MEDIUM = 3;
+
+    /**
+     * Call duration reported with {@link #EXTRA_CALL_DURATION} to indicate to the
+     * {@link CallScreeningService} the duration of a call for which the user reported the nuisance
+     * status (see {@link TelecomManager#reportNuisanceCallStatus(Uri, boolean)}).  The
+     * {@link CallScreeningService} can use this as a signal for training nuisance detection
+     * algorithms.  The call duration is reported in coarse grained buckets to minimize exposure of
+     * identifying call log information to the {@link CallScreeningService}.
+     * <p>
+     * Indicates the call was greater than 120 seconds.
+     */
+    public static final int CALL_DURATION_LONG = 4;
+
+    /**
+     * Telecom sends this intent to the {@link CallScreeningService} which the user has chosen to
+     * fill the call screening role when the user indicates through the default dialer whether a
+     * call is a nuisance call or not (see
+     * {@link TelecomManager#reportNuisanceCallStatus(Uri, boolean)}).
+     * <p>
+     * The following extra values are provided for the call:
+     * <ol>
+     *     <li>{@link #EXTRA_CALL_HANDLE} - the handle of the call.</li>
+     *     <li>{@link #EXTRA_IS_NUISANCE} - {@code true} if the user reported the call as a nuisance
+     *     call, {@code false} otherwise.</li>
+     *     <li>{@link #EXTRA_CALL_TYPE} - reports the type of call (incoming, rejected, missed,
+     *     blocked).</li>
+     *     <li>{@link #EXTRA_CALL_DURATION} - the duration of the call (see
+     *     {@link #EXTRA_CALL_DURATION} for valid values).</li>
+     * </ol>
+     * <p>
+     * {@link CallScreeningService} implementations which want to track whether the user reports
+     * calls are nuisance calls should use declare a broadcast receiver in their manifest for this
+     * intent.
+     * <p>
+     * Note: Only {@link CallScreeningService} implementations which have provided
+     * {@link CallIdentification} information for calls at some point will receive this intent.
+     */
+    public static final String ACTION_NUISANCE_CALL_STATUS_CHANGED =
+            "android.telecom.action.NUISANCE_CALL_STATUS_CHANGED";
+
+    /**
+     * Extra used to provide the handle of the call for
+     * {@link #ACTION_NUISANCE_CALL_STATUS_CHANGED}.  The call handle is reported as a
+     * {@link Uri}.
+     */
+    public static final String EXTRA_CALL_HANDLE = "android.telecom.extra.CALL_HANDLE";
+
+    /**
+     * Boolean extra used to indicate whether the user reported a call as a nuisance call.
+     * When {@code true}, the user reported that a call was a nuisance call, {@code false}
+     * otherwise.  Sent with {@link #ACTION_NUISANCE_CALL_STATUS_CHANGED}.
+     */
+    public static final String EXTRA_IS_NUISANCE = "android.telecom.extra.IS_NUISANCE";
+
+    /**
+     * Integer extra used with {@link #ACTION_NUISANCE_CALL_STATUS_CHANGED} to report the type of
+     * call. Valid values are:
+     * <UL>
+     *   <li>{@link android.provider.CallLog.Calls#MISSED_TYPE}</li>
+     *   <li>{@link android.provider.CallLog.Calls#INCOMING_TYPE}</li>
+     *   <li>{@link android.provider.CallLog.Calls#BLOCKED_TYPE}</li>
+     *   <li>{@link android.provider.CallLog.Calls#REJECTED_TYPE}</li>
+     * </UL>
+     */
+    public static final String EXTRA_CALL_TYPE = "android.telecom.extra.CALL_TYPE";
+
+    /**
+     * Integer extra used to with {@link #ACTION_NUISANCE_CALL_STATUS_CHANGED} to report how long
+     * the call lasted.  Valid values are:
+     * <UL>
+     *     <LI>{@link #CALL_DURATION_VERY_SHORT}</LI>
+     *     <LI>{@link #CALL_DURATION_SHORT}</LI>
+     *     <LI>{@link #CALL_DURATION_MEDIUM}</LI>
+     *     <LI>{@link #CALL_DURATION_LONG}</LI>
+     * </UL>
+     */
+    public static final String EXTRA_CALL_DURATION = "android.telecom.extra.CALL_DURATION";
+
     /**
      * The {@link Intent} that must be declared as handled by the service.
      */
diff --git a/telecomm/java/android/telecom/TelecomManager.java b/telecomm/java/android/telecom/TelecomManager.java
index 0fe5e08..12a5344 100644
--- a/telecomm/java/android/telecom/TelecomManager.java
+++ b/telecomm/java/android/telecom/TelecomManager.java
@@ -1970,6 +1970,33 @@
     }
 
     /**
+     * Called by the default dialer to report to Telecom when the user has marked a previous
+     * incoming call as a nuisance call or not.
+     * <p>
+     * Where the user has chosen a {@link CallScreeningService} to fill the call screening role,
+     * Telecom will notify that {@link CallScreeningService} of the user's report.
+     * <p>
+     * Requires that the caller is the default dialer app.
+     *
+     * @param handle The phone number of an incoming call which the user is reporting as either a
+     *               nuisance of non-nuisance call.
+     * @param isNuisanceCall {@code true} if the user is reporting the call as a nuisance call,
+     *                       {@code false} if the user is reporting the call as a non-nuisance call.
+     */
+    @RequiresPermission(android.Manifest.permission.READ_PHONE_STATE)
+    public void reportNuisanceCallStatus(@NonNull Uri handle, boolean isNuisanceCall) {
+        ITelecomService service = getTelecomService();
+        if (service != null) {
+            try {
+                service.reportNuisanceCallStatus(handle, isNuisanceCall,
+                        mContext.getOpPackageName());
+            } catch (RemoteException e) {
+                Log.e(TAG, "Error calling ITelecomService#showCallScreen", e);
+            }
+        }
+    }
+
+    /**
      * Handles {@link Intent#ACTION_CALL} intents trampolined from UserCallActivity.
      * @param intent The {@link Intent#ACTION_CALL} intent to handle.
      * @hide
diff --git a/telecomm/java/com/android/internal/telecom/ITelecomService.aidl b/telecomm/java/com/android/internal/telecom/ITelecomService.aidl
index e1d5c17..5030f90 100644
--- a/telecomm/java/com/android/internal/telecom/ITelecomService.aidl
+++ b/telecomm/java/com/android/internal/telecom/ITelecomService.aidl
@@ -285,6 +285,8 @@
      */
     boolean isInEmergencyCall();
 
+    oneway void reportNuisanceCallStatus(in Uri address, boolean isNuisance, String callingPackage);
+
     /**
      * @see TelecomServiceImpl#handleCallIntent
      */
@@ -299,4 +301,5 @@
     void addOrRemoveTestCallCompanionApp(String packageName, boolean isAdded);
 
     void setTestAutoModeApp(String packageName);
+
 }
diff --git a/telephony/java/android/provider/Telephony.java b/telephony/java/android/provider/Telephony.java
index 15abdb7..5482270 100644
--- a/telephony/java/android/provider/Telephony.java
+++ b/telephony/java/android/provider/Telephony.java
@@ -2375,6 +2375,9 @@
 
         /**
          * Contains message parts.
+         *
+         * To avoid issues where applications might cache a part ID, the ID of a deleted part must
+         * not be reused to point at a new part.
          */
         public static final class Part implements BaseColumns {
 
@@ -2386,6 +2389,12 @@
             }
 
             /**
+             * The {@code content://} style URL for this table. Can be appended with a part ID to
+             * address individual parts.
+             */
+            public static final Uri CONTENT_URI = Uri.withAppendedPath(Mms.CONTENT_URI, "part");
+
+            /**
              * The identifier of the message which this part belongs to.
              * <P>Type: INTEGER</P>
              */
diff --git a/telephony/java/android/telephony/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/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/tests/net/java/com/android/server/ConnectivityServiceTest.java b/tests/net/java/com/android/server/ConnectivityServiceTest.java
index dda4481..b5d5f61 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;
@@ -122,6 +124,7 @@
 import android.net.NetworkStack;
 import android.net.NetworkUtils;
 import android.net.RouteInfo;
+import android.net.SocketKeepalive;
 import android.net.UidRange;
 import android.net.metrics.IpConnectivityLog;
 import android.net.shared.NetworkMonitorUtils;
@@ -186,6 +189,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 +407,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.
@@ -3548,6 +3553,80 @@
         }
     }
 
+    private static class TestSocketKeepaliveCallback extends SocketKeepalive.Callback {
+
+        public enum CallbackType { ON_STARTED, ON_STOPPED, ON_ERROR };
+
+        private class CallbackValue {
+            public CallbackType callbackType;
+            public int error;
+
+            CallbackValue(CallbackType type) {
+                this.callbackType = type;
+                this.error = SocketKeepalive.SUCCESS;
+                assertTrue("onError callback must have error", type != CallbackType.ON_ERROR);
+            }
+
+            CallbackValue(CallbackType type, int error) {
+                this.callbackType = type;
+                this.error = error;
+                assertEquals("error can only be set for onError", type, CallbackType.ON_ERROR);
+            }
+
+            @Override
+            public boolean equals(Object o) {
+                return o instanceof CallbackValue
+                        && this.callbackType == ((CallbackValue) o).callbackType
+                        && this.error == ((CallbackValue) o).error;
+            }
+
+            @Override
+            public String toString() {
+                return String.format("%s(%s, %d)", getClass().getSimpleName(), callbackType,
+                        error);
+            }
+        }
+
+        private LinkedBlockingQueue<CallbackValue> mCallbacks = new LinkedBlockingQueue<>();
+
+        @Override
+        public void onStarted() {
+            mCallbacks.add(new CallbackValue(CallbackType.ON_STARTED));
+        }
+
+        @Override
+        public void onStopped() {
+            mCallbacks.add(new CallbackValue(CallbackType.ON_STOPPED));
+        }
+
+        @Override
+        public void onError(int error) {
+            mCallbacks.add(new CallbackValue(CallbackType.ON_ERROR, error));
+        }
+
+        private void expectCallback(CallbackValue callbackValue) {
+            try {
+                assertEquals(
+                        callbackValue,
+                        mCallbacks.poll(TIMEOUT_MS, TimeUnit.MILLISECONDS));
+            } catch (InterruptedException e) {
+                fail(callbackValue.callbackType + " callback not seen after " + TIMEOUT_MS + " ms");
+            }
+        }
+
+        public void expectStarted() {
+            expectCallback(new CallbackValue(CallbackType.ON_STARTED));
+        }
+
+        public void expectStopped() {
+            expectCallback(new CallbackValue(CallbackType.ON_STOPPED));
+        }
+
+        public void expectError(int error) {
+            expectCallback(new CallbackValue(CallbackType.ON_ERROR, error));
+        }
+    }
+
     private Network connectKeepaliveNetwork(LinkProperties lp) {
         // Ensure the network is disconnected before we do anything.
         if (mWiFiNetworkAgent != null) {
@@ -3695,6 +3774,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);
diff --git a/tests/utils/testutils/java/com/android/test/filters/SelectTestTests.java b/tests/utils/testutils/java/com/android/test/filters/SelectTestTests.java
index a6b0102..163b00a 100644
--- a/tests/utils/testutils/java/com/android/test/filters/SelectTestTests.java
+++ b/tests/utils/testutils/java/com/android/test/filters/SelectTestTests.java
@@ -190,21 +190,22 @@
 
     @Test
     public void testSelectClassAndSamePackage() {
-        final Filter filter = mBuilder.withSelectTest(CLASS_A1, CLASS_A2, PACKAGE_A,
-                CLASS_C5, CLASS_C6, PACKAGE_C).build();
-        acceptTests(filter, TEST_PACKAGE_A, TEST_PACKAGE_C);
+        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_A1L, METHOD_A2M, CLASS_A1,
-                CLASS_B3, METHOD_B3P, METHOD_B3Q, METHOD_B4R).build();
+        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,
+        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);
     }
diff --git a/tools/aapt2/cmd/Convert_test.cpp b/tools/aapt2/cmd/Convert_test.cpp
index 2e43150..8da5bb8 100644
--- a/tools/aapt2/cmd/Convert_test.cpp
+++ b/tools/aapt2/cmd/Convert_test.cpp
@@ -53,7 +53,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 +91,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);
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/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/wifi/java/android/net/wifi/IWifiManager.aidl b/wifi/java/android/net/wifi/IWifiManager.aidl
index 46c4191..a0ce9dd 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);
@@ -210,4 +215,3 @@
 
     void stopDppSession();
 }
-
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/WifiManager.java b/wifi/java/android/net/wifi/WifiManager.java
index 1fd45e7..084bd09 100644
--- a/wifi/java/android/net/wifi/WifiManager.java
+++ b/wifi/java/android/net/wifi/WifiManager.java
@@ -4777,4 +4777,92 @@
             });
         }
     }
+
+    /**
+     * 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();
+        }
+    }
 }
diff --git a/wifi/java/android/net/wifi/WifiUsabilityStatsEntry.aidl b/wifi/java/android/net/wifi/WifiUsabilityStatsEntry.aidl
new file mode 100644
index 0000000..839af54
--- /dev/null
+++ b/wifi/java/android/net/wifi/WifiUsabilityStatsEntry.aidl
@@ -0,0 +1,19 @@
+/*
+ * Copyright (C) 2019 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.net.wifi;
+
+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/com/android/server/wifi/BaseWifiService.java b/wifi/java/com/android/server/wifi/BaseWifiService.java
index b3ac9f1..226c7c48 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,15 @@
     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();
+    }
 }
diff --git a/wifi/tests/src/android/net/wifi/WifiManagerTest.java b/wifi/tests/src/android/net/wifi/WifiManagerTest.java
index 4fbef5a..5c2f626 100644
--- a/wifi/tests/src/android/net/wifi/WifiManagerTest.java
+++ b/wifi/tests/src/android/net/wifi/WifiManagerTest.java
@@ -61,6 +61,7 @@
 import android.net.wifi.WifiManager.NetworkRequestUserSelectionCallback;
 import android.net.wifi.WifiManager.SoftApCallback;
 import android.net.wifi.WifiManager.TrafficStateCallback;
+import android.net.wifi.WifiManager.WifiUsabilityStatsListener;
 import android.os.Handler;
 import android.os.IBinder;
 import android.os.Message;
@@ -80,6 +81,7 @@
 import java.util.HashMap;
 import java.util.List;
 import java.util.Map;
+import java.util.concurrent.Executor;
 
 /**
  * Unit tests for {@link android.net.wifi.WifiManager}.
@@ -103,7 +105,9 @@
     @Mock SoftApCallback mSoftApCallback;
     @Mock TrafficStateCallback mTrafficStateCallback;
     @Mock NetworkRequestMatchCallback mNetworkRequestMatchCallback;
+    @Mock WifiUsabilityStatsListener mWifiUsabilityStatsListener;
 
+    private Executor mExecutor;
     private Handler mHandler;
     private TestLooper mLooper;
     private WifiManager mWifiManager;
@@ -1342,4 +1346,40 @@
         assertArrayEquals(TEST_MAC_ADDRESSES, mWifiManager.getFactoryMacAddresses());
         verify(mWifiService).getFactoryMacAddresses();
     }
+
+    /**
+     * Verify the call to addWifiUsabilityStatsListener goes to WifiServiceImpl.
+     */
+    @Test
+    public void addWifiUsabilityStatsListeneroesToWifiServiceImpl() throws Exception {
+        mExecutor = new SynchronousExecutor();
+        mWifiManager.addWifiUsabilityStatsListener(mExecutor, mWifiUsabilityStatsListener);
+        verify(mWifiService).addWifiUsabilityStatsListener(any(IBinder.class),
+                any(IWifiUsabilityStatsListener.Stub.class), anyInt());
+    }
+
+    /**
+     * Verify the call to removeWifiUsabilityStatsListener goes to WifiServiceImpl.
+     */
+    @Test
+    public void removeWifiUsabilityListenerGoesToWifiServiceImpl() throws Exception {
+        ArgumentCaptor<Integer> listenerIdentifier = ArgumentCaptor.forClass(Integer.class);
+        mExecutor = new SynchronousExecutor();
+        mWifiManager.addWifiUsabilityStatsListener(mExecutor, mWifiUsabilityStatsListener);
+        verify(mWifiService).addWifiUsabilityStatsListener(any(IBinder.class),
+                any(IWifiUsabilityStatsListener.Stub.class), listenerIdentifier.capture());
+
+        mWifiManager.removeWifiUsabilityStatsListener(mWifiUsabilityStatsListener);
+        verify(mWifiService).removeWifiUsabilityStatsListener(
+                eq((int) listenerIdentifier.getValue()));
+    }
+
+    /**
+     * Defined for testing purpose.
+     */
+    class SynchronousExecutor implements Executor {
+        public void execute(Runnable r) {
+            r.run();
+        }
+    }
 }
diff --git a/wifi/tests/src/android/net/wifi/WifiUsabilityStatsEntryTest.java b/wifi/tests/src/android/net/wifi/WifiUsabilityStatsEntryTest.java
new file mode 100644
index 0000000..a947b55
--- /dev/null
+++ b/wifi/tests/src/android/net/wifi/WifiUsabilityStatsEntryTest.java
@@ -0,0 +1,104 @@
+/*
+ * Copyright (C) 2019 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.net.wifi;
+
+import static org.junit.Assert.assertEquals;
+import static org.mockito.Mockito.validateMockitoUsage;
+
+import android.os.Parcel;
+
+import androidx.test.filters.SmallTest;
+
+import org.junit.After;
+import org.junit.Before;
+import org.junit.Test;
+import org.mockito.MockitoAnnotations;
+
+
+/**
+ * Unit tests for {@link android.net.wifi.WifiUsabilityStatsEntry}.
+ */
+@SmallTest
+public class WifiUsabilityStatsEntryTest {
+
+    /**
+     * Setup before tests.
+     */
+    @Before
+    public void setUp() throws Exception {
+        MockitoAnnotations.initMocks(this);
+    }
+
+    /**
+     * Clean up after tests.
+     */
+    @After
+    public void cleanup() {
+        validateMockitoUsage();
+    }
+
+    /**
+     * Verify parcel read/write for Wifi usability stats result.
+     */
+    @Test
+    public void verifyStatsResultWriteAndThenRead() throws Exception {
+        WifiUsabilityStatsEntry writeResult = createResult();
+        WifiUsabilityStatsEntry readResult = parcelWriteRead(writeResult);
+        assertWifiUsabilityStatsEntryEquals(writeResult, readResult);
+    }
+
+    /**
+     * Write the provided {@link WifiUsabilityStatsEntry} to a parcel and deserialize it.
+     */
+    private static WifiUsabilityStatsEntry parcelWriteRead(
+            WifiUsabilityStatsEntry writeResult) throws Exception {
+        Parcel parcel = Parcel.obtain();
+        writeResult.writeToParcel(parcel, 0);
+        parcel.setDataPosition(0);    // Rewind data position back to the beginning for read.
+        return WifiUsabilityStatsEntry.CREATOR.createFromParcel(parcel);
+    }
+
+    private static WifiUsabilityStatsEntry createResult() {
+        return new WifiUsabilityStatsEntry(
+                0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18
+        );
+    }
+
+    private static void assertWifiUsabilityStatsEntryEquals(
+            WifiUsabilityStatsEntry expected,
+            WifiUsabilityStatsEntry actual) {
+        assertEquals(expected.timeStampMs, actual.timeStampMs);
+        assertEquals(expected.rssi, actual.rssi);
+        assertEquals(expected.linkSpeedMbps, actual.linkSpeedMbps);
+        assertEquals(expected.totalTxSuccess, actual.totalTxSuccess);
+        assertEquals(expected.totalTxRetries, actual.totalTxRetries);
+        assertEquals(expected.totalTxBad, actual.totalTxBad);
+        assertEquals(expected.totalRxSuccess, actual.totalRxSuccess);
+        assertEquals(expected.totalRadioOnTimeMs, actual.totalRadioOnTimeMs);
+        assertEquals(expected.totalRadioTxTimeMs, actual.totalRadioTxTimeMs);
+        assertEquals(expected.totalRadioRxTimeMs, actual.totalRadioRxTimeMs);
+        assertEquals(expected.totalScanTimeMs, actual.totalScanTimeMs);
+        assertEquals(expected.totalNanScanTimeMs, actual.totalNanScanTimeMs);
+        assertEquals(expected.totalBackgroundScanTimeMs, actual.totalBackgroundScanTimeMs);
+        assertEquals(expected.totalRoamScanTimeMs, actual.totalRoamScanTimeMs);
+        assertEquals(expected.totalPnoScanTimeMs, actual.totalPnoScanTimeMs);
+        assertEquals(expected.totalHotspot2ScanTimeMs, actual.totalHotspot2ScanTimeMs);
+        assertEquals(expected.totalCcaBusyFreqTimeMs, actual.totalCcaBusyFreqTimeMs);
+        assertEquals(expected.totalRadioOnFreqTimeMs, actual.totalRadioOnFreqTimeMs);
+        assertEquals(expected.totalBeaconRx, actual.totalBeaconRx);
+    }
+}