Merge "Fix clip calculation for disabled clip bounds" into main
diff --git a/AconfigFlags.bp b/AconfigFlags.bp
index 44f3d70..52200bf 100644
--- a/AconfigFlags.bp
+++ b/AconfigFlags.bp
@@ -180,6 +180,18 @@
     srcs: ["core/java/android/nfc/*.aconfig"],
 }
 
+cc_aconfig_library {
+    name: "android_nfc_flags_aconfig_c_lib",
+    vendor_available: true,
+    aconfig_declarations: "android.nfc.flags-aconfig",
+    apex_available: [
+        "//apex_available:platform",
+        "com.android.nfcservices",
+        "nfc_nci.st21nfc.default",
+    ],
+    defaults: ["framework-minus-apex-aconfig-java-defaults"],
+}
+
 java_aconfig_library {
     name: "android.nfc.flags-aconfig-java",
     aconfig_declarations: "android.nfc.flags-aconfig",
diff --git a/core/api/current.txt b/core/api/current.txt
index 3f4a34b..d80b5cb 100644
--- a/core/api/current.txt
+++ b/core/api/current.txt
@@ -14699,31 +14699,31 @@
   }
 
   @FlaggedApi("android.database.sqlite.sqlite_apis_35") public final class SQLiteRawStatement implements java.io.Closeable {
-    method public void bindBlob(int, @NonNull byte[]) throws android.database.sqlite.SQLiteException;
-    method public void bindBlob(int, @NonNull byte[], int, int) throws android.database.sqlite.SQLiteException;
-    method public void bindDouble(int, double) throws android.database.sqlite.SQLiteException;
-    method public void bindInt(int, int) throws android.database.sqlite.SQLiteException;
-    method public void bindLong(int, long) throws android.database.sqlite.SQLiteException;
-    method public void bindNull(int) throws android.database.sqlite.SQLiteException;
-    method public void bindText(int, @NonNull String) throws android.database.sqlite.SQLiteException;
+    method public void bindBlob(int, @NonNull byte[]);
+    method public void bindBlob(int, @NonNull byte[], int, int);
+    method public void bindDouble(int, double);
+    method public void bindInt(int, int);
+    method public void bindLong(int, long);
+    method public void bindNull(int);
+    method public void bindText(int, @NonNull String);
     method public void clearBindings();
     method public void close();
-    method @Nullable public byte[] getColumnBlob(int) throws android.database.sqlite.SQLiteException;
-    method public double getColumnDouble(int) throws android.database.sqlite.SQLiteException;
-    method public int getColumnInt(int) throws android.database.sqlite.SQLiteException;
-    method public int getColumnLength(int) throws android.database.sqlite.SQLiteException;
-    method public long getColumnLong(int) throws android.database.sqlite.SQLiteException;
-    method @NonNull public String getColumnName(int) throws android.database.sqlite.SQLiteException;
-    method @NonNull public String getColumnText(int) throws android.database.sqlite.SQLiteException;
-    method public int getColumnType(int) throws android.database.sqlite.SQLiteException;
+    method @Nullable public byte[] getColumnBlob(int);
+    method public double getColumnDouble(int);
+    method public int getColumnInt(int);
+    method public int getColumnLength(int);
+    method public long getColumnLong(int);
+    method @NonNull public String getColumnName(int);
+    method @NonNull public String getColumnText(int);
+    method public int getColumnType(int);
     method public int getParameterCount();
     method public int getParameterIndex(@NonNull String);
     method @Nullable public String getParameterName(int);
     method public int getResultColumnCount();
     method public boolean isOpen();
-    method public int readColumnBlob(int, @NonNull byte[], int, int, int) throws android.database.sqlite.SQLiteException;
+    method public int readColumnBlob(int, @NonNull byte[], int, int, int);
     method public void reset();
-    method public boolean step() throws android.database.sqlite.SQLiteException;
+    method public boolean step();
     field public static final int SQLITE_DATA_TYPE_BLOB = 4; // 0x4
     field public static final int SQLITE_DATA_TYPE_FLOAT = 2; // 0x2
     field public static final int SQLITE_DATA_TYPE_INTEGER = 1; // 0x1
@@ -28698,14 +28698,17 @@
   }
 
   public final class NfcAdapter {
+    method @FlaggedApi("android.nfc.nfc_observe_mode") public boolean allowTransaction();
     method public void disableForegroundDispatch(android.app.Activity);
     method public void disableReaderMode(android.app.Activity);
+    method @FlaggedApi("android.nfc.nfc_observe_mode") public boolean disallowTransaction();
     method public void enableForegroundDispatch(android.app.Activity, android.app.PendingIntent, android.content.IntentFilter[], String[][]);
     method public void enableReaderMode(android.app.Activity, android.nfc.NfcAdapter.ReaderCallback, int, android.os.Bundle);
     method public static android.nfc.NfcAdapter getDefaultAdapter(android.content.Context);
     method @Nullable public android.nfc.NfcAntennaInfo getNfcAntennaInfo();
     method public boolean ignore(android.nfc.Tag, int, android.nfc.NfcAdapter.OnTagRemovedListener, android.os.Handler);
     method public boolean isEnabled();
+    method @FlaggedApi("android.nfc.nfc_observe_mode") public boolean isObserveModeSupported();
     method @FlaggedApi("android.nfc.enable_nfc_reader_option") public boolean isReaderOptionEnabled();
     method @FlaggedApi("android.nfc.enable_nfc_reader_option") public boolean isReaderOptionSupported();
     method public boolean isSecureNfcEnabled();
@@ -28813,6 +28816,7 @@
     method public boolean removeAidsForService(android.content.ComponentName, String);
     method @NonNull @RequiresPermission(android.Manifest.permission.NFC) public boolean setOffHostForService(@NonNull android.content.ComponentName, @NonNull String);
     method public boolean setPreferredService(android.app.Activity, android.content.ComponentName);
+    method @FlaggedApi("android.nfc.nfc_observe_mode") public boolean setServiceObserveModeDefault(@NonNull android.content.ComponentName, boolean);
     method public boolean supportsAidPrefixRegistration();
     method @NonNull @RequiresPermission(android.Manifest.permission.NFC) public boolean unsetOffHostForService(@NonNull android.content.ComponentName);
     method public boolean unsetPreferredService(android.app.Activity);
@@ -28832,9 +28836,20 @@
     method public final android.os.IBinder onBind(android.content.Intent);
     method public abstract void onDeactivated(int);
     method public abstract byte[] processCommandApdu(byte[], android.os.Bundle);
+    method @FlaggedApi("android.nfc.nfc_read_polling_loop") public void processPollingFrames(@NonNull java.util.List<android.os.Bundle>);
     method public final void sendResponseApdu(byte[]);
     field public static final int DEACTIVATION_DESELECTED = 1; // 0x1
     field public static final int DEACTIVATION_LINK_LOSS = 0; // 0x0
+    field @FlaggedApi("android.nfc.nfc_read_polling_loop") public static final String POLLING_LOOP_DATA_KEY = "android.nfc.cardemulation.DATA";
+    field @FlaggedApi("android.nfc.nfc_read_polling_loop") public static final String POLLING_LOOP_GAIN_KEY = "android.nfc.cardemulation.GAIN";
+    field @FlaggedApi("android.nfc.nfc_read_polling_loop") public static final String POLLING_LOOP_TIMESTAMP_KEY = "android.nfc.cardemulation.TIMESTAMP";
+    field @FlaggedApi("android.nfc.nfc_read_polling_loop") public static final char POLLING_LOOP_TYPE_A = 65; // 0x0041 'A'
+    field @FlaggedApi("android.nfc.nfc_read_polling_loop") public static final char POLLING_LOOP_TYPE_B = 66; // 0x0042 'B'
+    field @FlaggedApi("android.nfc.nfc_read_polling_loop") public static final char POLLING_LOOP_TYPE_F = 70; // 0x0046 'F'
+    field @FlaggedApi("android.nfc.nfc_read_polling_loop") public static final String POLLING_LOOP_TYPE_KEY = "android.nfc.cardemulation.TYPE";
+    field @FlaggedApi("android.nfc.nfc_read_polling_loop") public static final char POLLING_LOOP_TYPE_OFF = 88; // 0x0058 'X'
+    field @FlaggedApi("android.nfc.nfc_read_polling_loop") public static final char POLLING_LOOP_TYPE_ON = 79; // 0x004f 'O'
+    field @FlaggedApi("android.nfc.nfc_read_polling_loop") public static final char POLLING_LOOP_TYPE_UNKNOWN = 85; // 0x0055 'U'
     field public static final String SERVICE_INTERFACE = "android.nfc.cardemulation.action.HOST_APDU_SERVICE";
     field public static final String SERVICE_META_DATA = "android.nfc.cardemulation.host_apdu_service";
   }
diff --git a/core/api/lint-baseline.txt b/core/api/lint-baseline.txt
index 1308b1f..449249e 100644
--- a/core/api/lint-baseline.txt
+++ b/core/api/lint-baseline.txt
@@ -1,40 +1,4 @@
 // Baseline format: 1.0
-BannedThrow: android.database.sqlite.SQLiteRawStatement#bindBlob(int, byte[]):
-    Methods must not throw unchecked exceptions
-BannedThrow: android.database.sqlite.SQLiteRawStatement#bindBlob(int, byte[], int, int):
-    Methods must not throw unchecked exceptions
-BannedThrow: android.database.sqlite.SQLiteRawStatement#bindDouble(int, double):
-    Methods must not throw unchecked exceptions
-BannedThrow: android.database.sqlite.SQLiteRawStatement#bindInt(int, int):
-    Methods must not throw unchecked exceptions
-BannedThrow: android.database.sqlite.SQLiteRawStatement#bindLong(int, long):
-    Methods must not throw unchecked exceptions
-BannedThrow: android.database.sqlite.SQLiteRawStatement#bindNull(int):
-    Methods must not throw unchecked exceptions
-BannedThrow: android.database.sqlite.SQLiteRawStatement#bindText(int, String):
-    Methods must not throw unchecked exceptions
-BannedThrow: android.database.sqlite.SQLiteRawStatement#getColumnBlob(int):
-    Methods must not throw unchecked exceptions
-BannedThrow: android.database.sqlite.SQLiteRawStatement#getColumnDouble(int):
-    Methods must not throw unchecked exceptions
-BannedThrow: android.database.sqlite.SQLiteRawStatement#getColumnInt(int):
-    Methods must not throw unchecked exceptions
-BannedThrow: android.database.sqlite.SQLiteRawStatement#getColumnLength(int):
-    Methods must not throw unchecked exceptions
-BannedThrow: android.database.sqlite.SQLiteRawStatement#getColumnLong(int):
-    Methods must not throw unchecked exceptions
-BannedThrow: android.database.sqlite.SQLiteRawStatement#getColumnName(int):
-    Methods must not throw unchecked exceptions
-BannedThrow: android.database.sqlite.SQLiteRawStatement#getColumnText(int):
-    Methods must not throw unchecked exceptions
-BannedThrow: android.database.sqlite.SQLiteRawStatement#getColumnType(int):
-    Methods must not throw unchecked exceptions
-BannedThrow: android.database.sqlite.SQLiteRawStatement#readColumnBlob(int, byte[], int, int, int):
-    Methods must not throw unchecked exceptions
-BannedThrow: android.database.sqlite.SQLiteRawStatement#step():
-    Methods must not throw unchecked exceptions
-
-
 BroadcastBehavior: android.app.AlarmManager#ACTION_NEXT_ALARM_CLOCK_CHANGED:
     Field 'ACTION_NEXT_ALARM_CLOCK_CHANGED' is missing @BroadcastBehavior
 BroadcastBehavior: android.app.AlarmManager#ACTION_SCHEDULE_EXACT_ALARM_PERMISSION_STATE_CHANGED:
diff --git a/core/java/android/database/sqlite/SQLiteRawStatement.java b/core/java/android/database/sqlite/SQLiteRawStatement.java
index 33f602b..c59d3ce 100644
--- a/core/java/android/database/sqlite/SQLiteRawStatement.java
+++ b/core/java/android/database/sqlite/SQLiteRawStatement.java
@@ -163,7 +163,7 @@
      * {@link IllegalStateException} if a transaction is not in progress. Clients should call
      * {@link SQLiteDatabase.createRawStatement} to create a new instance.
      */
-    SQLiteRawStatement(@NonNull SQLiteDatabase db, @NonNull String sql) throws SQLiteException {
+    SQLiteRawStatement(@NonNull SQLiteDatabase db, @NonNull String sql) {
         mThread = Thread.currentThread();
         mDatabase = db;
         mSession = mDatabase.getThreadSession();
@@ -245,7 +245,7 @@
      * @throws SQLiteDatabaseLockedException if the database is locked or busy.
      * @throws SQLiteException if a native error occurs.
      */
-    public boolean step() throws SQLiteException {
+    public boolean step() {
         throwIfInvalid();
         try {
             int err = nativeStep(mStatement, true);
@@ -392,7 +392,7 @@
      * @throws SQLiteBindOrColumnIndexOutOfRangeException if the parameter is out of range.
      * @throws SQLiteException if a native error occurs.
      */
-    public void bindBlob(int parameterIndex, @NonNull byte[] value) throws SQLiteException {
+    public void bindBlob(int parameterIndex, @NonNull byte[] value) {
         Objects.requireNonNull(value);
         throwIfInvalid();
         try {
@@ -418,8 +418,7 @@
      * @throws SQLiteBindOrColumnIndexOutOfRangeException if the parameter is out of range.
      * @throws SQLiteException if a native error occurs.
      */
-    public void bindBlob(int parameterIndex, @NonNull byte[] value, int offset, int length)
-            throws SQLiteException {
+    public void bindBlob(int parameterIndex, @NonNull byte[] value, int offset, int length) {
         Objects.requireNonNull(value);
         throwIfInvalid();
         throwIfInvalidBounds(value.length, offset, length);
@@ -442,7 +441,7 @@
      * @throws SQLiteBindOrColumnIndexOutOfRangeException if the parameter is out of range.
      * @throws SQLiteException if a native error occurs.
      */
-    public void bindDouble(int parameterIndex, double value) throws SQLiteException {
+    public void bindDouble(int parameterIndex, double value) {
         throwIfInvalid();
         try {
             nativeBindDouble(mStatement, parameterIndex, value);
@@ -462,7 +461,7 @@
      * @throws SQLiteBindOrColumnIndexOutOfRangeException if the parameter is out of range.
      * @throws SQLiteException if a native error occurs.
      */
-    public void bindInt(int parameterIndex, int value) throws SQLiteException {
+    public void bindInt(int parameterIndex, int value) {
         throwIfInvalid();
         try {
             nativeBindInt(mStatement, parameterIndex, value);
@@ -482,7 +481,7 @@
      * @throws SQLiteBindOrColumnIndexOutOfRangeException if the parameter is out of range.
      * @throws SQLiteException if a native error occurs.
      */
-    public void bindLong(int parameterIndex, long value) throws SQLiteException {
+    public void bindLong(int parameterIndex, long value) {
         throwIfInvalid();
         try {
             nativeBindLong(mStatement, parameterIndex, value);
@@ -502,7 +501,7 @@
      * @throws SQLiteBindOrColumnIndexOutOfRangeException if the parameter is out of range.
      * @throws SQLiteException if a native error occurs.
      */
-    public void bindNull(int parameterIndex) throws SQLiteException {
+    public void bindNull(int parameterIndex) {
         throwIfInvalid();
         try {
             nativeBindNull(mStatement, parameterIndex);
@@ -523,7 +522,7 @@
      * @throws SQLiteBindOrColumnIndexOutOfRangeException if the parameter is out of range.
      * @throws SQLiteException if a native error occurs.
      */
-    public void bindText(int parameterIndex, @NonNull String value) throws SQLiteException {
+    public void bindText(int parameterIndex, @NonNull String value) {
         Objects.requireNonNull(value);
         throwIfInvalid();
         try {
@@ -562,7 +561,7 @@
      * @throws SQLiteException if a native error occurs.
      */
     @SQLiteDataType
-    public int getColumnType(int columnIndex) throws SQLiteException {
+    public int getColumnType(int columnIndex) {
         throwIfInvalid();
         try {
             return nativeColumnType(mStatement, columnIndex);
@@ -584,7 +583,7 @@
      * @throws SQLiteOutOfMemoryException if the database cannot allocate memory for the name.
      */
     @NonNull
-    public String getColumnName(int columnIndex) throws SQLiteException {
+    public String getColumnName(int columnIndex) {
         throwIfInvalid();
         try {
             return nativeColumnName(mStatement, columnIndex);
@@ -609,7 +608,7 @@
      * @throws SQLiteBindOrColumnIndexOutOfRangeException if the column is out of range.
      * @throws SQLiteException if a native error occurs.
      */
-    public int getColumnLength(int columnIndex) throws SQLiteException {
+    public int getColumnLength(int columnIndex) {
         throwIfInvalid();
         try {
             return nativeColumnBytes(mStatement, columnIndex);
@@ -635,7 +634,7 @@
      * @throws SQLiteException if a native error occurs.
      */
     @Nullable
-    public byte[] getColumnBlob(int columnIndex) throws SQLiteException {
+    public byte[] getColumnBlob(int columnIndex) {
         throwIfInvalid();
         try {
             return nativeColumnBlob(mStatement, columnIndex);
@@ -668,8 +667,7 @@
      * @throws SQLiteException if a native error occurs.
      */
     public int readColumnBlob(int columnIndex, @NonNull byte[] buffer, int offset,
-            int length, int srcOffset)
-            throws SQLiteException {
+            int length, int srcOffset) {
         Objects.requireNonNull(buffer);
         throwIfInvalid();
         throwIfInvalidBounds(buffer.length, offset, length);
@@ -695,7 +693,7 @@
      * @throws SQLiteBindOrColumnIndexOutOfRangeException if the column is out of range.
      * @throws SQLiteException if a native error occurs.
      */
-    public double getColumnDouble(int columnIndex) throws SQLiteException {
+    public double getColumnDouble(int columnIndex) {
         throwIfInvalid();
         try {
             return nativeColumnDouble(mStatement, columnIndex);
@@ -719,7 +717,7 @@
      * @throws SQLiteBindOrColumnIndexOutOfRangeException if the column is out of range.
      * @throws SQLiteException if a native error occurs.
      */
-    public int getColumnInt(int columnIndex) throws SQLiteException {
+    public int getColumnInt(int columnIndex) {
         throwIfInvalid();
         try {
             return nativeColumnInt(mStatement, columnIndex);
@@ -743,7 +741,7 @@
      * @throws SQLiteBindOrColumnIndexOutOfRangeException if the column is out of range.
      * @throws SQLiteException if a native error occurs.
      */
-    public long getColumnLong(int columnIndex) throws SQLiteException {
+    public long getColumnLong(int columnIndex) {
         throwIfInvalid();
         try {
             return nativeColumnLong(mStatement, columnIndex);
@@ -768,7 +766,7 @@
      * @throws SQLiteException if a native error occurs.
      */
     @NonNull
-    public String getColumnText(int columnIndex) throws SQLiteException {
+    public String getColumnText(int columnIndex) {
         throwIfInvalid();
         try {
             return nativeColumnText(mStatement, columnIndex);
diff --git a/core/java/android/hardware/camera2/CaptureRequest.java b/core/java/android/hardware/camera2/CaptureRequest.java
index aca6d06..5d06978 100644
--- a/core/java/android/hardware/camera2/CaptureRequest.java
+++ b/core/java/android/hardware/camera2/CaptureRequest.java
@@ -3402,8 +3402,8 @@
             new Key<Long>("android.sensor.exposureTime", long.class);
 
     /**
-     * <p>Duration from start of frame exposure to
-     * start of next frame exposure.</p>
+     * <p>Duration from start of frame readout to
+     * start of next frame readout.</p>
      * <p>The maximum frame rate that can be supported by a camera subsystem is
      * a function of many factors:</p>
      * <ul>
@@ -3464,6 +3464,10 @@
      * <p>For more details about stalling, see {@link android.hardware.camera2.params.StreamConfigurationMap#getOutputStallDuration }.</p>
      * <p>This control is only effective if {@link CaptureRequest#CONTROL_AE_MODE android.control.aeMode} or {@link CaptureRequest#CONTROL_MODE android.control.mode} is set to
      * OFF; otherwise the auto-exposure algorithm will override this value.</p>
+     * <p><em>Note:</em> Prior to Android 13, this field was described as measuring the duration from
+     * start of frame exposure to start of next frame exposure, which doesn't reflect the
+     * definition from sensor manufacturer. A mobile sensor defines the frame duration as
+     * intervals between sensor readouts.</p>
      * <p><b>Units</b>: Nanoseconds</p>
      * <p><b>Range of valid values:</b><br>
      * See {@link CameraCharacteristics#SENSOR_INFO_MAX_FRAME_DURATION android.sensor.info.maxFrameDuration}, {@link android.hardware.camera2.params.StreamConfigurationMap }.
diff --git a/core/java/android/hardware/camera2/CaptureResult.java b/core/java/android/hardware/camera2/CaptureResult.java
index 1c66f82..0d204f3 100644
--- a/core/java/android/hardware/camera2/CaptureResult.java
+++ b/core/java/android/hardware/camera2/CaptureResult.java
@@ -4103,8 +4103,8 @@
             new Key<Long>("android.sensor.exposureTime", long.class);
 
     /**
-     * <p>Duration from start of frame exposure to
-     * start of next frame exposure.</p>
+     * <p>Duration from start of frame readout to
+     * start of next frame readout.</p>
      * <p>The maximum frame rate that can be supported by a camera subsystem is
      * a function of many factors:</p>
      * <ul>
@@ -4165,6 +4165,10 @@
      * <p>For more details about stalling, see {@link android.hardware.camera2.params.StreamConfigurationMap#getOutputStallDuration }.</p>
      * <p>This control is only effective if {@link CaptureRequest#CONTROL_AE_MODE android.control.aeMode} or {@link CaptureRequest#CONTROL_MODE android.control.mode} is set to
      * OFF; otherwise the auto-exposure algorithm will override this value.</p>
+     * <p><em>Note:</em> Prior to Android 13, this field was described as measuring the duration from
+     * start of frame exposure to start of next frame exposure, which doesn't reflect the
+     * definition from sensor manufacturer. A mobile sensor defines the frame duration as
+     * intervals between sensor readouts.</p>
      * <p><b>Units</b>: Nanoseconds</p>
      * <p><b>Range of valid values:</b><br>
      * See {@link CameraCharacteristics#SENSOR_INFO_MAX_FRAME_DURATION android.sensor.info.maxFrameDuration}, {@link android.hardware.camera2.params.StreamConfigurationMap }.
diff --git a/core/java/android/hardware/camera2/impl/FrameNumberTracker.java b/core/java/android/hardware/camera2/impl/FrameNumberTracker.java
index 8304796..cf496d2 100644
--- a/core/java/android/hardware/camera2/impl/FrameNumberTracker.java
+++ b/core/java/android/hardware/camera2/impl/FrameNumberTracker.java
@@ -90,6 +90,22 @@
                         break;
                     }
                 }
+
+                if (!removeError) {
+                    // Check for the case where we might have an error after a frame number gap
+                    // caused by other types of capture requests
+                    int otherType1 = (requestType + 1) % CaptureRequest.REQUEST_TYPE_COUNT;
+                    int otherType2 = (requestType + 2) % CaptureRequest.REQUEST_TYPE_COUNT;
+                    if (mPendingFrameNumbersWithOtherType[otherType1].isEmpty() &&
+                            mPendingFrameNumbersWithOtherType[otherType2].isEmpty()) {
+                        long errorGapNumber = Math.max(mCompletedFrameNumber[otherType1],
+                                mCompletedFrameNumber[otherType2]) + 1;
+                        if ((errorGapNumber > mCompletedFrameNumber[requestType] + 1) &&
+                                (errorGapNumber == errorFrameNumber)) {
+                            removeError = true;
+                        }
+                    }
+                }
             }
             if (removeError) {
                 mCompletedFrameNumber[requestType] = errorFrameNumber;
diff --git a/core/java/android/nfc/INfcAdapter.aidl b/core/java/android/nfc/INfcAdapter.aidl
index 0c95c2e..f6beec1 100644
--- a/core/java/android/nfc/INfcAdapter.aidl
+++ b/core/java/android/nfc/INfcAdapter.aidl
@@ -84,4 +84,6 @@
     boolean isReaderOptionSupported();
     @JavaPassthrough(annotation="@android.annotation.RequiresPermission(android.Manifest.permission.WRITE_SECURE_SETTINGS)")
     boolean enableReaderOption(boolean enable);
+    boolean isObserveModeSupported();
+    boolean setObserveMode(boolean enabled);
 }
diff --git a/core/java/android/nfc/INfcCardEmulation.aidl b/core/java/android/nfc/INfcCardEmulation.aidl
index c7b3b2c..191385a 100644
--- a/core/java/android/nfc/INfcCardEmulation.aidl
+++ b/core/java/android/nfc/INfcCardEmulation.aidl
@@ -30,6 +30,7 @@
     boolean isDefaultServiceForAid(int userHandle, in ComponentName service, String aid);
     boolean setDefaultServiceForCategory(int userHandle, in ComponentName service, String category);
     boolean setDefaultForNextTap(int userHandle, in ComponentName service);
+    boolean setServiceObserveModeDefault(int userId, in android.content.ComponentName service, boolean enable);
     boolean registerAidGroupForService(int userHandle, in ComponentName service, in AidGroup aidGroup);
     boolean setOffHostForService(int userHandle, in ComponentName service, in String offHostSecureElement);
     boolean unsetOffHostForService(int userHandle, in ComponentName service);
diff --git a/core/java/android/nfc/NfcAdapter.java b/core/java/android/nfc/NfcAdapter.java
index c897595..98a980f 100644
--- a/core/java/android/nfc/NfcAdapter.java
+++ b/core/java/android/nfc/NfcAdapter.java
@@ -1081,6 +1081,61 @@
         }
     }
 
+
+    /**
+     * Returns whether the device supports observer mode or not. When observe
+     * mode is enabled, the NFC hardware will listen for NFC readers, but not
+     * respond to them. When observe mode is disabled, the NFC hardware will
+     * resoond to the reader and proceed with the transaction.
+     * @return true if the mode is supported, false otherwise.
+     */
+    @FlaggedApi(Flags.FLAG_NFC_OBSERVE_MODE)
+    public boolean isObserveModeSupported() {
+        try {
+            return sService.isObserveModeSupported();
+        } catch (RemoteException e) {
+            attemptDeadServiceRecovery(e);
+            return false;
+        }
+    }
+
+   /**
+    * Disables observe mode to allow the transaction to proceed. See
+    * {@link #isObserveModeSupported()} for a description of observe mode and
+    * use {@link #disallowTransaction()} to enable observe mode and block
+    * transactions again.
+    *
+    * @return boolean indicating success or failure.
+    */
+    @FlaggedApi(Flags.FLAG_NFC_OBSERVE_MODE)
+    public boolean allowTransaction() {
+        try {
+            return sService.setObserveMode(false);
+        } catch (RemoteException e) {
+            attemptDeadServiceRecovery(e);
+            return false;
+        }
+    }
+
+    /**
+    * Signals that the transaction has completed and observe mode may be
+    * reenabled. See {@link #isObserveModeSupported()} for a description of
+    * observe mode and use {@link #allowTransaction()} to disable observe
+    * mode and allow transactions to proceed.
+    *
+    * @return boolean indicating success or failure.
+    */
+
+    @FlaggedApi(Flags.FLAG_NFC_OBSERVE_MODE)
+    public boolean disallowTransaction() {
+        try {
+            return sService.setObserveMode(true);
+        } catch (RemoteException e) {
+            attemptDeadServiceRecovery(e);
+            return false;
+        }
+    }
+
     /**
      * Resumes default polling for the current device state if polling is paused. Calling
      * this while polling is not paused is a no-op.
diff --git a/core/java/android/nfc/cardemulation/CardEmulation.java b/core/java/android/nfc/cardemulation/CardEmulation.java
index d048b59..58b6179 100644
--- a/core/java/android/nfc/cardemulation/CardEmulation.java
+++ b/core/java/android/nfc/cardemulation/CardEmulation.java
@@ -328,6 +328,24 @@
             return SELECTION_MODE_ASK_IF_CONFLICT;
         }
     }
+    /**
+     * Sets whether the system should default to observe mode or not when
+     * the service is in the foreground or the default payment service.
+     *
+     * @param service The component name of the service
+     * @param enable Whether the servic should default to observe mode or not
+     * @return whether the change was successful.
+     */
+    @FlaggedApi(Flags.FLAG_NFC_OBSERVE_MODE)
+    public boolean setServiceObserveModeDefault(@NonNull ComponentName service, boolean enable) {
+        try {
+            return sService.setServiceObserveModeDefault(mContext.getUser().getIdentifier(),
+                    service, enable);
+        } catch (RemoteException e) {
+            Log.e(TAG, "Failed to reach CardEmulationService.");
+        }
+        return  false;
+    }
 
     /**
      * Registers a list of AIDs for a specific category for the
diff --git a/core/java/android/nfc/cardemulation/HostApduService.java b/core/java/android/nfc/cardemulation/HostApduService.java
index 55d0e73..7cd2533 100644
--- a/core/java/android/nfc/cardemulation/HostApduService.java
+++ b/core/java/android/nfc/cardemulation/HostApduService.java
@@ -16,11 +16,14 @@
 
 package android.nfc.cardemulation;
 
+import android.annotation.FlaggedApi;
+import android.annotation.NonNull;
 import android.annotation.SdkConstant;
 import android.annotation.SdkConstant.SdkConstantType;
 import android.app.Service;
 import android.content.Intent;
 import android.content.pm.PackageManager;
+import android.nfc.NfcAdapter;
 import android.os.Bundle;
 import android.os.Handler;
 import android.os.IBinder;
@@ -29,6 +32,9 @@
 import android.os.RemoteException;
 import android.util.Log;
 
+import java.util.ArrayList;
+import java.util.List;
+
 /**
  * <p>HostApduService is a convenience {@link Service} class that can be
  * extended to emulate an NFC card inside an Android
@@ -230,9 +236,99 @@
     /**
      * @hide
      */
+    public static final int MSG_POLLING_LOOP = 4;
+
+    /**
+     * @hide
+     */
     public static final String KEY_DATA = "data";
 
     /**
+     * POLLING_LOOP_TYPE_KEY is the Bundle key for the type of
+     * polling loop frame in the Bundle passed to {@link #processPollingFrames(List)}
+     */
+    @FlaggedApi(android.nfc.Flags.FLAG_NFC_READ_POLLING_LOOP)
+    public static final String POLLING_LOOP_TYPE_KEY = "android.nfc.cardemulation.TYPE";
+
+    /**
+     * POLLING_LOOP_TYPE_A is the value associated with the key
+     * POLLING_LOOP_TYPE  in the Bundle passed to {@link #processPollingFrames(List)}
+     * when the polling loop is for NFC-A.
+     */
+    @FlaggedApi(android.nfc.Flags.FLAG_NFC_READ_POLLING_LOOP)
+    public static final char POLLING_LOOP_TYPE_A = 'A';
+
+    /**
+     * POLLING_LOOP_TYPE_B is the value associated with the key
+     * POLLING_LOOP_TYPE  in the Bundle passed to {@link #processPollingFrames(List)}
+     * when the polling loop is for NFC-B.
+     */
+    @FlaggedApi(android.nfc.Flags.FLAG_NFC_READ_POLLING_LOOP)
+    public static final char POLLING_LOOP_TYPE_B = 'B';
+
+    /**
+     * POLLING_LOOP_TYPE_F is the value associated with the key
+     * POLLING_LOOP_TYPE  in the Bundle passed to {@link #processPollingFrames(List)}
+     * when the polling loop is for NFC-F.
+     */
+    @FlaggedApi(android.nfc.Flags.FLAG_NFC_READ_POLLING_LOOP)
+    public static final char POLLING_LOOP_TYPE_F = 'F';
+
+    /**
+     * POLLING_LOOP_TYPE_ON is the value associated with the key
+     * POLLING_LOOP_TYPE  in the Bundle passed to {@link #processPollingFrames(List)}
+     * when the polling loop turns on.
+     */
+    @FlaggedApi(android.nfc.Flags.FLAG_NFC_READ_POLLING_LOOP)
+    public static final char POLLING_LOOP_TYPE_ON = 'O';
+
+    /**
+     * POLLING_LOOP_TYPE_OFF is the value associated with the key
+     * POLLING_LOOP_TYPE  in the Bundle passed to {@link #processPollingFrames(List)}
+     * when the polling loop turns off.
+     */
+    @FlaggedApi(android.nfc.Flags.FLAG_NFC_READ_POLLING_LOOP)
+    public static final char POLLING_LOOP_TYPE_OFF = 'X';
+
+    /**
+     * POLLING_LOOP_TYPE_UNKNOWN is the value associated with the key
+     * POLLING_LOOP_TYPE  in the Bundle passed to {@link #processPollingFrames(List)}
+     * when the polling loop frame isn't recognized.
+     */
+    @FlaggedApi(android.nfc.Flags.FLAG_NFC_READ_POLLING_LOOP)
+    public static final char POLLING_LOOP_TYPE_UNKNOWN = 'U';
+
+    /**
+     * POLLING_LOOP_DATA is the Bundle key for the raw data of captured from
+     * the polling loop frame in the Bundle passed to {@link #processPollingFrames(List)}
+     * when the frame type isn't recognized.
+     */
+    @FlaggedApi(android.nfc.Flags.FLAG_NFC_READ_POLLING_LOOP)
+    public static final String POLLING_LOOP_DATA_KEY = "android.nfc.cardemulation.DATA";
+
+    /**
+     * POLLING_LOOP_GAIN_KEY is the Bundle key for the field strength of
+     * the polling loop frame in the Bundle passed to {@link #processPollingFrames(List)}
+     * when the frame type isn't recognized.
+     */
+    @FlaggedApi(android.nfc.Flags.FLAG_NFC_READ_POLLING_LOOP)
+    public static final String POLLING_LOOP_GAIN_KEY = "android.nfc.cardemulation.GAIN";
+
+    /**
+     * POLLING_LOOP_TIMESTAMP_KEY is the Bundle key for the timestamp of
+     * the polling loop frame in the Bundle passed to {@link #processPollingFrames(List)}
+     * when the frame type isn't recognized.
+     */
+    @FlaggedApi(android.nfc.Flags.FLAG_NFC_READ_POLLING_LOOP)
+    public static final String POLLING_LOOP_TIMESTAMP_KEY = "android.nfc.cardemulation.TIMESTAMP";
+
+    /**
+     * @hide
+     */
+    public static final String POLLING_LOOP_FRAMES_BUNDLE_KEY =
+            "android.nfc.cardemulation.POLLING_FRAMES";
+
+    /**
      * Messenger interface to NfcService for sending responses.
      * Only accessed on main thread by the message handler.
      *
@@ -255,6 +351,7 @@
 
                 byte[] apdu = dataBundle.getByteArray(KEY_DATA);
                 if (apdu != null) {
+                        HostApduService has = HostApduService.this;
                     byte[] responseApdu = processCommandApdu(apdu, null);
                     if (responseApdu != null) {
                         if (mNfcService == null) {
@@ -306,6 +403,12 @@
                     Log.e(TAG, "RemoteException calling into NfcService.");
                 }
                 break;
+                case MSG_POLLING_LOOP:
+                    ArrayList<Bundle> frames =
+                            msg.getData().getParcelableArrayList(POLLING_LOOP_FRAMES_BUNDLE_KEY,
+                            Bundle.class);
+                    processPollingFrames(frames);
+                    break;
             default:
                 super.handleMessage(msg);
             }
@@ -366,6 +469,21 @@
         }
     }
 
+    /**
+     * This method is called when a polling frame has been received from a
+     * remote device. If the device is in observe mode, the service should
+     * call {@link NfcAdapter#allowTransaction()} once it is ready to proceed
+     * with the transaction. If the device is not in observe mode, the service
+     * can use this polling frame information to determine how to proceed if it
+     * subsequently has {@link #processCommandApdu(byte[], Bundle)} called. The
+     * service must override this method inorder to receive polling frames,
+     * otherwise the base implementation drops the frame.
+     *
+     * @param frame A description of the polling frame.
+     */
+    @FlaggedApi(android.nfc.Flags.FLAG_NFC_READ_POLLING_LOOP)
+    public void processPollingFrames(@NonNull List<Bundle> frame) {
+    }
 
     /**
      * <p>This method will be called when a command APDU has been received
diff --git a/core/java/android/nfc/flags.aconfig b/core/java/android/nfc/flags.aconfig
index cd50ace..17e0427 100644
--- a/core/java/android/nfc/flags.aconfig
+++ b/core/java/android/nfc/flags.aconfig
@@ -20,3 +20,31 @@
     description: "Flag for NFC user restriction"
     bug: "291187960"
 }
+
+flag {
+    name: "nfc_observe_mode"
+    namespace: "nfc"
+    description: "Enable NFC Observe Mode"
+    bug: "294217286"
+}
+
+flag {
+    name: "nfc_read_polling_loop"
+    namespace: "nfc"
+    description: "Enable NFC Polling Loop Notifications"
+    bug: "294217286"
+}
+
+flag {
+    name: "nfc_observe_mode_st_shim"
+    namespace: "nfc"
+    description: "Enable NFC Observe Mode ST shim"
+    bug: "294217286"
+}
+
+flag {
+    name: "nfc_read_polling_loop_st_shim"
+    namespace: "nfc"
+    description: "Enable NFC Polling Loop Notifications ST shim"
+    bug: "294217286"
+}
diff --git a/core/java/android/os/Binder.java b/core/java/android/os/Binder.java
index 9956220..0ccc485 100644
--- a/core/java/android/os/Binder.java
+++ b/core/java/android/os/Binder.java
@@ -30,9 +30,11 @@
 import com.android.internal.os.BinderCallHeavyHitterWatcher.BinderCallHeavyHitterListener;
 import com.android.internal.os.BinderInternal;
 import com.android.internal.os.BinderInternal.CallSession;
+import com.android.internal.os.SomeArgs;
 import com.android.internal.util.FastPrintWriter;
 import com.android.internal.util.FunctionalUtils.ThrowingRunnable;
 import com.android.internal.util.FunctionalUtils.ThrowingSupplier;
+import com.android.internal.util.Preconditions;
 
 import dalvik.annotation.optimization.CriticalNative;
 
@@ -46,6 +48,7 @@
 import java.io.PrintWriter;
 import java.lang.reflect.Modifier;
 import java.util.concurrent.atomic.AtomicReferenceArray;
+import java.util.function.Supplier;
 
 /**
  * Base class for a remotable object, the core part of a lightweight
@@ -289,6 +292,33 @@
         sWarnOnBlockingOnCurrentThread.set(sWarnOnBlocking);
     }
 
+    private static ThreadLocal<SomeArgs> sIdentity$ravenwood;
+
+    @android.ravenwood.annotation.RavenwoodKeepWholeClass
+    private static class IdentitySupplier implements Supplier<SomeArgs> {
+        @Override
+        public SomeArgs get() {
+            final SomeArgs args = SomeArgs.obtain();
+            // Match IPCThreadState behavior
+            args.arg1 = Boolean.FALSE;
+            args.argi1 = android.os.Process.myUid();
+            args.argi2 = android.os.Process.myPid();
+            return args;
+        }
+    }
+
+    /** @hide */
+    @android.ravenwood.annotation.RavenwoodKeep
+    public static void init$ravenwood() {
+        sIdentity$ravenwood = ThreadLocal.withInitial(new IdentitySupplier());
+    }
+
+    /** @hide */
+    @android.ravenwood.annotation.RavenwoodKeep
+    public static void reset$ravenwood() {
+        sIdentity$ravenwood = null;
+    }
+
     /**
      * Raw native pointer to JavaBBinderHolder object. Owned by this Java object. Not null.
      */
@@ -312,8 +342,14 @@
      * Warning: oneway transactions do not receive PID.
      */
     @CriticalNative
+    @android.ravenwood.annotation.RavenwoodReplace
     public static final native int getCallingPid();
 
+    /** @hide */
+    public static final int getCallingPid$ravenwood() {
+        return Preconditions.requireNonNullViaRavenwoodRule(sIdentity$ravenwood).get().argi2;
+    }
+
     /**
      * Return the Linux UID assigned to the process that sent you the
      * current transaction that is being processed. This UID can be used with
@@ -322,8 +358,14 @@
      * incoming transaction, then its own UID is returned.
      */
     @CriticalNative
+    @android.ravenwood.annotation.RavenwoodReplace
     public static final native int getCallingUid();
 
+    /** @hide */
+    public static final int getCallingUid$ravenwood() {
+        return Preconditions.requireNonNullViaRavenwoodRule(sIdentity$ravenwood).get().argi1;
+    }
+
     /**
      * Returns {@code true} if the current thread is currently executing an
      * incoming transaction.
@@ -331,6 +373,7 @@
      * @hide
      */
     @CriticalNative
+    @android.ravenwood.annotation.RavenwoodReplace
     public static final native boolean isDirectlyHandlingTransactionNative();
 
     /** @hide */
@@ -344,6 +387,7 @@
     /**
      * @hide
      */
+    @android.ravenwood.annotation.RavenwoodKeep
     public static final boolean isDirectlyHandlingTransaction() {
         return sIsHandlingBinderTransaction || isDirectlyHandlingTransactionNative();
     }
@@ -363,8 +407,15 @@
     * @hide
     */
     @CriticalNative
+    @android.ravenwood.annotation.RavenwoodReplace
     private static native boolean hasExplicitIdentity();
 
+    /** @hide */
+    private static boolean hasExplicitIdentity$ravenwood() {
+        return Preconditions.requireNonNullViaRavenwoodRule(sIdentity$ravenwood).get().arg1
+                == Boolean.TRUE;
+    }
+
     /**
      * Return the Linux UID assigned to the process that sent the transaction
      * currently being processed.
@@ -373,6 +424,7 @@
      * executing an incoming transaction and the calling identity has not been
      * explicitly set with {@link #clearCallingIdentity()}
      */
+    @android.ravenwood.annotation.RavenwoodKeep
     public static final int getCallingUidOrThrow() {
         if (!isDirectlyHandlingTransaction() && !hasExplicitIdentity()) {
             throw new IllegalStateException(
@@ -434,8 +486,26 @@
      * @see #restoreCallingIdentity(long)
      */
     @CriticalNative
+    @android.ravenwood.annotation.RavenwoodReplace
     public static final native long clearCallingIdentity();
 
+    /** @hide */
+    public static final long clearCallingIdentity$ravenwood() {
+        final SomeArgs args = Preconditions.requireNonNullViaRavenwoodRule(
+                sIdentity$ravenwood).get();
+        long res = ((long) args.argi1 << 32) | args.argi2;
+        if (args.arg1 == Boolean.TRUE) {
+            res |= (0x1 << 30);
+        } else {
+            res &= ~(0x1 << 30);
+        }
+        // Match IPCThreadState behavior
+        args.arg1 = Boolean.TRUE;
+        args.argi1 = android.os.Process.myUid();
+        args.argi2 = android.os.Process.myPid();
+        return res;
+    }
+
     /**
      * Restore the identity of the incoming IPC on the current thread
      * back to a previously identity that was returned by {@link
@@ -447,8 +517,18 @@
      * @see #clearCallingIdentity
      */
     @CriticalNative
+    @android.ravenwood.annotation.RavenwoodReplace
     public static final native void restoreCallingIdentity(long token);
 
+    /** @hide */
+    public static final void restoreCallingIdentity$ravenwood(long token) {
+        final SomeArgs args = Preconditions.requireNonNullViaRavenwoodRule(
+                sIdentity$ravenwood).get();
+        args.arg1 = ((token & (0x1 << 30)) != 0) ? Boolean.TRUE : Boolean.FALSE;
+        args.argi1 = (int) (token >> 32);
+        args.argi2 = (int) (token & ~(0x1 << 30));
+    }
+
     /**
      * Convenience method for running the provided action enclosed in
      * {@link #clearCallingIdentity}/{@link #restoreCallingIdentity}.
@@ -644,8 +724,14 @@
      * in order to prevent the process from holding on to objects longer than
      * it needs to.
      */
+    @android.ravenwood.annotation.RavenwoodReplace
     public static final native void flushPendingCommands();
 
+    /** @hide */
+    public static final void flushPendingCommands$ravenwood() {
+        // Ravenwood doesn't support IPC; ignored
+    }
+
     /**
      * Add the calling thread to the IPC thread pool. This function does
      * not return until the current process is exiting.
@@ -703,6 +789,7 @@
      * <p>If you're creating a Binder token (a Binder object without an attached interface),
      * you should use {@link #Binder(String)} instead.
      */
+    @android.ravenwood.annotation.RavenwoodKeep
     public Binder() {
         this(null);
     }
@@ -719,6 +806,7 @@
      * Instead of creating multiple tokens with the same descriptor, consider adding a suffix to
      * help identify them.
      */
+    @android.ravenwood.annotation.RavenwoodKeep
     public Binder(@Nullable String descriptor) {
         mObject = getNativeBBinderHolder();
         if (mObject != 0L) {
@@ -742,6 +830,7 @@
      * will be implemented for you to return the given owner IInterface when
      * the corresponding descriptor is requested.
      */
+    @android.ravenwood.annotation.RavenwoodKeep
     public void attachInterface(@Nullable IInterface owner, @Nullable String descriptor) {
         mOwner = owner;
         mDescriptor = descriptor;
@@ -750,6 +839,7 @@
     /**
      * Default implementation returns an empty interface name.
      */
+    @android.ravenwood.annotation.RavenwoodKeep
     public @Nullable String getInterfaceDescriptor() {
         return mDescriptor;
     }
@@ -758,6 +848,7 @@
      * Default implementation always returns true -- if you got here,
      * the object is alive.
      */
+    @android.ravenwood.annotation.RavenwoodKeep
     public boolean pingBinder() {
         return true;
     }
@@ -768,6 +859,7 @@
      * Note that if you're calling on a local binder, this always returns true
      * because your process is alive if you're calling it.
      */
+    @android.ravenwood.annotation.RavenwoodKeep
     public boolean isBinderAlive() {
         return true;
     }
@@ -777,6 +869,7 @@
      * to return the associated {@link IInterface} if it matches the requested
      * descriptor.
      */
+    @android.ravenwood.annotation.RavenwoodKeep
     public @Nullable IInterface queryLocalInterface(@NonNull String descriptor) {
         if (mDescriptor != null && mDescriptor.equals(descriptor)) {
             return mOwner;
@@ -1250,12 +1343,14 @@
     /**
      * Local implementation is a no-op.
      */
+    @android.ravenwood.annotation.RavenwoodKeep
     public void linkToDeath(@NonNull DeathRecipient recipient, int flags) {
     }
 
     /**
      * Local implementation is a no-op.
      */
+    @android.ravenwood.annotation.RavenwoodKeep
     public boolean unlinkToDeath(@NonNull DeathRecipient recipient, int flags) {
         return true;
     }
@@ -1283,6 +1378,7 @@
         }
     }
 
+    @android.ravenwood.annotation.RavenwoodReplace
     private static native long getNativeBBinderHolder();
 
     private static long getNativeBBinderHolder$ravenwood() {
diff --git a/core/java/android/os/IBinder.java b/core/java/android/os/IBinder.java
index 90e4b17..91c2965 100644
--- a/core/java/android/os/IBinder.java
+++ b/core/java/android/os/IBinder.java
@@ -194,6 +194,7 @@
      * Limit that should be placed on IPC sizes, in bytes, to keep them safely under the transaction
      * buffer limit.
      */
+    @android.ravenwood.annotation.RavenwoodKeep
     static int getSuggestedMaxIpcSizeBytes() {
         return MAX_IPC_SIZE;
     }
diff --git a/core/java/android/os/IVibratorManagerService.aidl b/core/java/android/os/IVibratorManagerService.aidl
index f30dd20..0f27569 100644
--- a/core/java/android/os/IVibratorManagerService.aidl
+++ b/core/java/android/os/IVibratorManagerService.aidl
@@ -33,13 +33,13 @@
     boolean unregisterVibratorStateListener(int vibratorId, in IVibratorStateListener listener);
     boolean setAlwaysOnEffect(int uid, String opPkg, int alwaysOnId,
             in CombinedVibration vibration, in VibrationAttributes attributes);
-    void vibrate(int uid, int displayId, String opPkg, in CombinedVibration vibration,
+    void vibrate(int uid, int deviceId, String opPkg, in CombinedVibration vibration,
             in VibrationAttributes attributes, String reason, IBinder token);
     void cancelVibrate(int usageFilter, IBinder token);
 
     // Async oneway APIs.
     // There is no order guarantee with respect to the two-way APIs above like
     // vibrate/isVibrating/cancel.
-    oneway void performHapticFeedback(int uid, int displayId, String opPkg, int constant,
+    oneway void performHapticFeedback(int uid, int deviceId, String opPkg, int constant,
             boolean always, String reason, IBinder token);
 }
diff --git a/core/java/android/os/Process.java b/core/java/android/os/Process.java
index 677143a..daec1721 100644
--- a/core/java/android/os/Process.java
+++ b/core/java/android/os/Process.java
@@ -34,6 +34,9 @@
 import android.util.Pair;
 import android.webkit.WebViewZygote;
 
+import com.android.internal.os.SomeArgs;
+import com.android.internal.util.Preconditions;
+
 import dalvik.system.VMRuntime;
 
 import libcore.io.IoUtils;
@@ -833,14 +836,37 @@
         return VMRuntime.getRuntime().is64Bit();
     }
 
+    private static SomeArgs sIdentity$ravenwood;
+
+    /** @hide */
+    @android.ravenwood.annotation.RavenwoodKeep
+    public static void init$ravenwood(int uid, int pid) {
+        final SomeArgs args = SomeArgs.obtain();
+        args.argi1 = uid;
+        args.argi2 = pid;
+        sIdentity$ravenwood = args;
+    }
+
+    /** @hide */
+    @android.ravenwood.annotation.RavenwoodKeep
+    public static void reset$ravenwood() {
+        sIdentity$ravenwood = null;
+    }
+
     /**
      * Returns the identifier of this process, which can be used with
      * {@link #killProcess} and {@link #sendSignal}.
      */
+    @android.ravenwood.annotation.RavenwoodReplace
     public static final int myPid() {
         return Os.getpid();
     }
 
+    /** @hide */
+    public static final int myPid$ravenwood() {
+        return Preconditions.requireNonNullViaRavenwoodRule(sIdentity$ravenwood).argi2;
+    }
+
     /**
      * Returns the identifier of this process' parent.
      * @hide
@@ -864,10 +890,16 @@
      * app-specific sandbox.  It is different from {@link #myUserHandle} in that
      * a uid identifies a specific app sandbox in a specific user.
      */
+    @android.ravenwood.annotation.RavenwoodReplace
     public static final int myUid() {
         return Os.getuid();
     }
 
+    /** @hide */
+    public static final int myUid$ravenwood() {
+        return Preconditions.requireNonNullViaRavenwoodRule(sIdentity$ravenwood).argi1;
+    }
+
     /**
      * Returns this process's user handle.  This is the
      * user the process is running under.  It is distinct from
diff --git a/core/java/android/os/SystemClock.java b/core/java/android/os/SystemClock.java
index 831ca86..49a0bd3 100644
--- a/core/java/android/os/SystemClock.java
+++ b/core/java/android/os/SystemClock.java
@@ -24,6 +24,7 @@
 import android.content.Context;
 import android.location.ILocationManager;
 import android.location.LocationTime;
+import android.text.format.DateUtils;
 import android.util.Slog;
 
 import dalvik.annotation.optimization.CriticalNative;
@@ -125,6 +126,7 @@
      *
      * @param ms to sleep before returning, in milliseconds of uptime.
      */
+    @android.ravenwood.annotation.RavenwoodKeep
     public static void sleep(long ms)
     {
         long start = uptimeMillis();
@@ -186,8 +188,16 @@
      * @return milliseconds of non-sleep uptime since boot.
      */
     @CriticalNative
+    @android.ravenwood.annotation.RavenwoodReplace
     native public static long uptimeMillis();
 
+    /** @hide */
+    public static long uptimeMillis$ravenwood() {
+        // Ravenwood booted in Jan 2023, and has been in deep sleep for one week
+        return System.currentTimeMillis() - (1672556400L * 1_000)
+                - (DateUtils.WEEK_IN_MILLIS * 1_000);
+    }
+
     /**
      * Returns nanoseconds since boot, not counting time spent in deep sleep.
      *
@@ -195,8 +205,16 @@
      * @hide
      */
     @CriticalNative
+    @android.ravenwood.annotation.RavenwoodReplace
     public static native long uptimeNanos();
 
+    /** @hide */
+    public static long uptimeNanos$ravenwood() {
+        // Ravenwood booted in Jan 2023, and has been in deep sleep for one week
+        return System.nanoTime() - (1672556400L * 1_000_000_000)
+                - (DateUtils.WEEK_IN_MILLIS * 1_000_000_000);
+    }
+
     /**
      * Return {@link Clock} that starts at system boot, not counting time spent
      * in deep sleep.
@@ -218,8 +236,15 @@
      * @return elapsed milliseconds since boot.
      */
     @CriticalNative
+    @android.ravenwood.annotation.RavenwoodReplace
     native public static long elapsedRealtime();
 
+    /** @hide */
+    public static long elapsedRealtime$ravenwood() {
+        // Ravenwood booted in Jan 2023, and has been in deep sleep for one week
+        return System.currentTimeMillis() - (1672556400L * 1_000);
+    }
+
     /**
      * Return {@link Clock} that starts at system boot, including time spent in
      * sleep.
@@ -241,8 +266,15 @@
      * @return elapsed nanoseconds since boot.
      */
     @CriticalNative
+    @android.ravenwood.annotation.RavenwoodReplace
     public static native long elapsedRealtimeNanos();
 
+    /** @hide */
+    public static long elapsedRealtimeNanos$ravenwood() {
+        // Ravenwood booted in Jan 2023, and has been in deep sleep for one week
+        return System.nanoTime() - (1672556400L * 1_000_000_000);
+    }
+
     /**
      * Returns milliseconds running in the current thread.
      *
@@ -271,8 +303,15 @@
      */
     @UnsupportedAppUsage
     @CriticalNative
+    @android.ravenwood.annotation.RavenwoodReplace
     public static native long currentTimeMicro();
 
+    /** @hide */
+    public static long currentTimeMicro$ravenwood() {
+        // Ravenwood booted in Jan 2023, and has been in deep sleep for one week
+        return System.nanoTime() / 1000L;
+    }
+
     /**
      * Returns milliseconds since January 1, 1970 00:00:00.0 UTC, synchronized
      * using a remote network source outside the device.
diff --git a/core/java/android/os/SystemVibratorManager.java b/core/java/android/os/SystemVibratorManager.java
index ee90834..bc85412e 100644
--- a/core/java/android/os/SystemVibratorManager.java
+++ b/core/java/android/os/SystemVibratorManager.java
@@ -137,8 +137,8 @@
             return;
         }
         try {
-            mService.vibrate(uid, mContext.getAssociatedDisplayId(), opPkg, effect, attributes,
-                    reason, mToken);
+            mService.vibrate(uid, mContext.getDeviceId(), opPkg, effect, attributes, reason,
+                    mToken);
         } catch (RemoteException e) {
             Log.w(TAG, "Failed to vibrate.", e);
         }
@@ -152,8 +152,8 @@
         }
         try {
             mService.performHapticFeedback(
-                    Process.myUid(), mContext.getAssociatedDisplayId(), mPackageName, constant,
-                    always, reason, mToken);
+                    Process.myUid(), mContext.getDeviceId(), mPackageName, constant, always, reason,
+                    mToken);
         } catch (RemoteException e) {
             Log.w(TAG, "Failed to perform haptic feedback.", e);
         }
diff --git a/core/java/android/window/flags/windowing_sdk.aconfig b/core/java/android/window/flags/windowing_sdk.aconfig
index 0ad6c99..b600b22 100644
--- a/core/java/android/window/flags/windowing_sdk.aconfig
+++ b/core/java/android/window/flags/windowing_sdk.aconfig
@@ -35,4 +35,11 @@
     name: "fullscreen_dim_flag"
     description: "Whether to allow showing fullscreen dim on ActivityEmbedding split"
     bug: "253533308"
+}
+
+flag {
+    namespace: "windowing_sdk"
+    name: "activity_embedding_interactive_divider_flag"
+    description: "Whether the interactive divider feature is enabled"
+    bug: "293654166"
 }
\ No newline at end of file
diff --git a/core/java/com/android/internal/util/ArrayUtils.java b/core/java/com/android/internal/util/ArrayUtils.java
index 686e1fc..9d0be4b 100644
--- a/core/java/com/android/internal/util/ArrayUtils.java
+++ b/core/java/com/android/internal/util/ArrayUtils.java
@@ -40,6 +40,7 @@
 /**
  * Static utility methods for arrays that aren't already included in {@link java.util.Arrays}.
  */
+@android.ravenwood.annotation.RavenwoodKeepWholeClass
 public class ArrayUtils {
     private static final int CACHE_SIZE = 73;
     private static Object[] sCache = new Object[CACHE_SIZE];
@@ -48,35 +49,43 @@
 
     private ArrayUtils() { /* cannot be instantiated */ }
 
+    @android.ravenwood.annotation.RavenwoodReplace
     public static byte[] newUnpaddedByteArray(int minLen) {
         return (byte[])VMRuntime.getRuntime().newUnpaddedArray(byte.class, minLen);
     }
 
+    @android.ravenwood.annotation.RavenwoodReplace
     public static char[] newUnpaddedCharArray(int minLen) {
         return (char[])VMRuntime.getRuntime().newUnpaddedArray(char.class, minLen);
     }
 
+    @android.ravenwood.annotation.RavenwoodReplace
     @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553)
     public static int[] newUnpaddedIntArray(int minLen) {
         return (int[])VMRuntime.getRuntime().newUnpaddedArray(int.class, minLen);
     }
 
+    @android.ravenwood.annotation.RavenwoodReplace
     public static boolean[] newUnpaddedBooleanArray(int minLen) {
         return (boolean[])VMRuntime.getRuntime().newUnpaddedArray(boolean.class, minLen);
     }
 
+    @android.ravenwood.annotation.RavenwoodReplace
     public static long[] newUnpaddedLongArray(int minLen) {
         return (long[])VMRuntime.getRuntime().newUnpaddedArray(long.class, minLen);
     }
 
+    @android.ravenwood.annotation.RavenwoodReplace
     public static float[] newUnpaddedFloatArray(int minLen) {
         return (float[])VMRuntime.getRuntime().newUnpaddedArray(float.class, minLen);
     }
 
+    @android.ravenwood.annotation.RavenwoodReplace
     public static Object[] newUnpaddedObjectArray(int minLen) {
         return (Object[])VMRuntime.getRuntime().newUnpaddedArray(Object.class, minLen);
     }
 
+    @android.ravenwood.annotation.RavenwoodReplace
     @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553)
     @SuppressWarnings("unchecked")
     public static <T> T[] newUnpaddedArray(Class<T> clazz, int minLen) {
diff --git a/core/res/res/values/attrs.xml b/core/res/res/values/attrs.xml
index 3496994..698c5ba 100644
--- a/core/res/res/values/attrs.xml
+++ b/core/res/res/values/attrs.xml
@@ -4303,6 +4303,9 @@
         <!-- Whether the device must be screen on before routing data to this service.
              The default is true.-->
         <attr name="requireDeviceScreenOn" format="boolean"/>
+        <!-- Whether the device should default to observe mode when this service is
+             default or in the foreground. -->
+        <attr name="defaultToObserveMode" format="boolean"/>
     </declare-styleable>
 
     <!-- Use <code>offhost-apdu-service</code> as the root tag of the XML resource that
@@ -4327,6 +4330,9 @@
         <!-- Whether the device must be screen on before routing data to this service.
              The default is false.-->
         <attr name="requireDeviceScreenOn"/>
+        <!-- Whether the device should default to observe mode when this service is
+             default or in the foreground. -->
+        <attr name="defaultToObserveMode"/>
     </declare-styleable>
 
     <!-- Specify one or more <code>aid-group</code> elements inside a
diff --git a/media/java/android/media/tv/ad/ITvAdManager.aidl b/media/java/android/media/tv/ad/ITvAdManager.aidl
new file mode 100644
index 0000000..92cc923
--- /dev/null
+++ b/media/java/android/media/tv/ad/ITvAdManager.aidl
@@ -0,0 +1,25 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.media.tv.ad;
+
+/**
+ * Interface to the TV AD service.
+ * @hide
+ */
+interface ITvAdManager {
+    void startAdService(in IBinder sessionToken, int userId);
+}
diff --git a/media/java/android/media/tv/ad/ITvAdSession.aidl b/media/java/android/media/tv/ad/ITvAdSession.aidl
new file mode 100644
index 0000000..b834f1b9
--- /dev/null
+++ b/media/java/android/media/tv/ad/ITvAdSession.aidl
@@ -0,0 +1,25 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.media.tv.ad;
+
+/**
+ * Sub-interface of ITvAdService which is created per session and has its own context.
+ * @hide
+ */
+oneway interface ITvAdSession {
+    void startAdService();
+}
diff --git a/media/java/android/media/tv/ad/TvAdManager.java b/media/java/android/media/tv/ad/TvAdManager.java
new file mode 100644
index 0000000..aa5a290
--- /dev/null
+++ b/media/java/android/media/tv/ad/TvAdManager.java
@@ -0,0 +1,65 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.media.tv.ad;
+
+import android.os.IBinder;
+import android.os.RemoteException;
+import android.util.Log;
+
+/**
+ * Central system API to the overall client-side TV AD architecture, which arbitrates interaction
+ * between applications and AD services.
+ * @hide
+ */
+public class TvAdManager {
+    private static final String TAG = "TvAdManager";
+
+    private final ITvAdManager mService;
+    private final int mUserId;
+
+    public TvAdManager(ITvAdManager service, int userId) {
+        mService = service;
+        mUserId = userId;
+    }
+
+    /**
+     * The Session provides the per-session functionality of AD service.
+     */
+    public static final class Session {
+        private final IBinder mToken;
+        private final ITvAdManager mService;
+        private final int mUserId;
+
+        private Session(IBinder token, ITvAdManager service, int userId) {
+            mToken = token;
+            mService = service;
+            mUserId = userId;
+        }
+
+        void startAdService() {
+            if (mToken == null) {
+                Log.w(TAG, "The session has been already released");
+                return;
+            }
+            try {
+                mService.startAdService(mToken, mUserId);
+            } catch (RemoteException e) {
+                throw e.rethrowFromSystemServer();
+            }
+        }
+    }
+}
diff --git a/media/java/android/media/tv/ad/TvAdService.java b/media/java/android/media/tv/ad/TvAdService.java
new file mode 100644
index 0000000..61101f0
--- /dev/null
+++ b/media/java/android/media/tv/ad/TvAdService.java
@@ -0,0 +1,60 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.media.tv.ad;
+
+import android.app.Service;
+import android.view.KeyEvent;
+
+/**
+ * The TvAdService class represents a TV client-side advertisement service.
+ * @hide
+ */
+public abstract class TvAdService extends Service {
+    private static final boolean DEBUG = false;
+    private static final String TAG = "TvAdService";
+
+    /**
+     * Base class for derived classes to implement to provide a TV AD session.
+     */
+    public abstract static class Session implements KeyEvent.Callback {
+        /**
+         * Starts TvAdService session.
+         */
+        public void onStartAdService() {
+        }
+
+        void startAdService() {
+            onStartAdService();
+        }
+    }
+
+    /**
+     * Implements the internal ITvAdService interface.
+     */
+    public static class ITvAdSessionWrapper extends ITvAdSession.Stub {
+        private final Session mSessionImpl;
+
+        public ITvAdSessionWrapper(Session mSessionImpl) {
+            this.mSessionImpl = mSessionImpl;
+        }
+
+        @Override
+        public void startAdService() {
+            mSessionImpl.startAdService();
+        }
+    }
+}
diff --git a/media/java/android/media/tv/ad/TvAdView.java b/media/java/android/media/tv/ad/TvAdView.java
new file mode 100644
index 0000000..1a3771a
--- /dev/null
+++ b/media/java/android/media/tv/ad/TvAdView.java
@@ -0,0 +1,57 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.media.tv.ad;
+
+import android.content.Context;
+import android.util.Log;
+import android.view.ViewGroup;
+
+/**
+ * Displays contents of TV AD services.
+ * @hide
+ */
+public class TvAdView extends ViewGroup {
+    private static final String TAG = "TvAdView";
+    private static final boolean DEBUG = false;
+
+    // TODO: create session
+    private TvAdManager.Session mSession;
+
+    public TvAdView(Context context) {
+        super(context, /* attrs = */null, /* defStyleAttr = */0);
+    }
+
+    @Override
+    protected void onLayout(boolean changed, int l, int t, int r, int b) {
+        if (DEBUG) {
+            Log.d(TAG,
+                    "onLayout (left=" + l + ", top=" + t + ", right=" + r + ", bottom=" + b + ",)");
+        }
+    }
+
+    /**
+     * Starts the AD service.
+     */
+    public void startAdService() {
+        if (DEBUG) {
+            Log.d(TAG, "start");
+        }
+        if (mSession != null) {
+            mSession.startAdService();
+        }
+    }
+}
diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/bouncer/ui/composable/BouncerScene.kt b/packages/SystemUI/compose/features/src/com/android/systemui/bouncer/ui/composable/BouncerScene.kt
index 3928767..7eb7dac 100644
--- a/packages/SystemUI/compose/features/src/com/android/systemui/bouncer/ui/composable/BouncerScene.kt
+++ b/packages/SystemUI/compose/features/src/com/android/systemui/bouncer/ui/composable/BouncerScene.kt
@@ -208,12 +208,8 @@
     val isSplitAroundTheFoldRequired by viewModel.isFoldSplitRequired.collectAsState()
     val isSplitAroundTheFold =
         foldPosture == FoldPosture.Tabletop && !outputOnly && isSplitAroundTheFoldRequired
-    val currentSceneKey by
-        remember(isSplitAroundTheFold) {
-            mutableStateOf(
-                if (isSplitAroundTheFold) SceneKeys.SplitSceneKey else SceneKeys.ContiguousSceneKey
-            )
-        }
+    val currentSceneKey =
+        if (isSplitAroundTheFold) SceneKeys.SplitSceneKey else SceneKeys.ContiguousSceneKey
 
     SceneTransitionLayout(
         currentScene = currentSceneKey,
@@ -405,8 +401,7 @@
             if (visibility == UserInputAreaVisibility.INPUT_ONLY) {
                 PatternBouncer(
                     viewModel = nonNullViewModel,
-                    modifier =
-                        Modifier.aspectRatio(1f, matchHeightConstraintsFirst = false).then(modifier)
+                    modifier = modifier.aspectRatio(1f, matchHeightConstraintsFirst = false)
                 )
             }
         else -> Unit
diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/communal/ui/compose/CommunalHub.kt b/packages/SystemUI/compose/features/src/com/android/systemui/communal/ui/compose/CommunalHub.kt
index 1429782..0569431 100644
--- a/packages/SystemUI/compose/features/src/com/android/systemui/communal/ui/compose/CommunalHub.kt
+++ b/packages/SystemUI/compose/features/src/com/android/systemui/communal/ui/compose/CommunalHub.kt
@@ -25,7 +25,6 @@
 import androidx.compose.foundation.layout.fillMaxHeight
 import androidx.compose.foundation.layout.fillMaxSize
 import androidx.compose.foundation.layout.height
-import androidx.compose.foundation.layout.padding
 import androidx.compose.foundation.layout.width
 import androidx.compose.foundation.lazy.grid.GridCells
 import androidx.compose.foundation.lazy.grid.GridItemSpan
@@ -164,11 +163,7 @@
 @Composable
 private fun Umo(viewModel: CommunalViewModel, modifier: Modifier = Modifier) {
     AndroidView(
-        modifier =
-            modifier
-                .width(Dimensions.CardWidth)
-                .height(Dimensions.CardHeightThird)
-                .padding(Dimensions.Spacing),
+        modifier = modifier,
         factory = {
             viewModel.mediaHost.expansion = MediaHostState.EXPANDED
             viewModel.mediaHost.showsOnlyActiveMedia = false
diff --git a/packages/SystemUI/res/drawable/ic_ksh_key_meta.xml b/packages/SystemUI/res/drawable/ic_ksh_key_meta.xml
index be8fe8c..ff1146e 100644
--- a/packages/SystemUI/res/drawable/ic_ksh_key_meta.xml
+++ b/packages/SystemUI/res/drawable/ic_ksh_key_meta.xml
@@ -19,9 +19,14 @@
         android:height="24dp"
         android:viewportWidth="24"
         android:viewportHeight="24">
-    <path android:fillColor="@color/ksh_key_item_color"
-            android:pathData="M15.5 14h-.79l-.28-.27C15.41 12.59 16 11.11 16 9.5 16 5.91 13.09 3 9.5 3S3 5.91
-3 9.5 5.91 16 9.5 16c1.61 0 3.09-.59 4.23-1.57l.27 .28 v.79l5 4.99L20.49
-19l-4.99-5zm-6 0C7.01 14 5 11.99 5 9.5S7.01 5 9.5 5 14 7.01 14 9.5 11.99 14 9.5
-14z" />
+    <path android:pathData="M5.5,5.5m-4,0a4,4 0,1 1,8 0a4,4 0,1 1,-8 0"
+            android:fillColor="@color/ksh_key_item_color" />
+    <path android:pathData="M5.5,16.5m-4,0a4,4 0,1 1,8 0a4,4 0,1 1,-8 0"
+            android:fillColor="@color/ksh_key_item_color" />
+    <path android:pathData="M16.5,5.5m-4,0a4,4 0,1 1,8 0a4,4 0,1 1,-8 0"
+            android:fillColor="@color/ksh_key_item_color" />
+    <path android:pathData="M18.5,16.5C18.5,15.4 17.6,14.5 16.5,14.5C15.4,14.5 14.5,15.4 14.5,16.5C14.5,17.6 15.4,18.5 16.5,18.5C17.6,18.5 18.5,17.6 18.5,16.5ZM12.5,16.5C12.5,14.29 14.29,12.5 16.5,12.5C18.71,12.5 20.5,14.29 20.5,16.5C20.5,17.241 20.299,17.934 19.948,18.529L23,21.59L21.59,23L18.529,19.948C17.934,20.299 17.241,20.5 16.5,20.5C14.29,20.5 12.5,18.71 12.5,16.5Z"
+            android:fillColor="@color/ksh_key_item_color"
+            android:fillType="evenOdd" />
 </vector>
+
diff --git a/packages/SystemUI/src/com/android/systemui/communal/domain/interactor/CommunalInteractor.kt b/packages/SystemUI/src/com/android/systemui/communal/domain/interactor/CommunalInteractor.kt
index 508f52c..771dfbc 100644
--- a/packages/SystemUI/src/com/android/systemui/communal/domain/interactor/CommunalInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/communal/domain/interactor/CommunalInteractor.kt
@@ -141,6 +141,7 @@
     private val umoContent: Flow<List<CommunalContentModel.Umo>> =
         mediaRepository.mediaPlaying.flatMapLatest { mediaPlaying ->
             if (mediaPlaying) {
+                // TODO(b/310254801): support HALF and FULL layouts
                 flowOf(listOf(CommunalContentModel.Umo(CommunalContentSize.THIRD)))
             } else {
                 flowOf(emptyList())
diff --git a/packages/SystemUI/src/com/android/systemui/flags/Flags.kt b/packages/SystemUI/src/com/android/systemui/flags/Flags.kt
index ced96f1..cd3ecb3 100644
--- a/packages/SystemUI/src/com/android/systemui/flags/Flags.kt
+++ b/packages/SystemUI/src/com/android/systemui/flags/Flags.kt
@@ -316,11 +316,6 @@
     val SMARTSPACE_SHARED_ELEMENT_TRANSITION_ENABLED =
         releasedFlag("smartspace_shared_element_transition_enabled")
 
-    // TODO(b/258517050): Clean up after the feature is launched.
-    @JvmField
-    val SMARTSPACE_DATE_WEATHER_DECOUPLED =
-        sysPropBooleanFlag("persist.sysui.ss.dw_decoupled", default = true)
-
     // TODO(b/270223352): Tracking Bug
     @JvmField
     val HIDE_SMARTSPACE_ON_DREAM_OVERLAY = releasedFlag("hide_smartspace_on_dream_overlay")
diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/base/viewmodel/QSTileViewModelFactory.kt b/packages/SystemUI/src/com/android/systemui/qs/tiles/base/viewmodel/QSTileViewModelFactory.kt
index 736f7cf..ae554d9 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/tiles/base/viewmodel/QSTileViewModelFactory.kt
+++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/base/viewmodel/QSTileViewModelFactory.kt
@@ -26,6 +26,7 @@
 import com.android.systemui.qs.tiles.base.interactor.QSTileUserActionInteractor
 import com.android.systemui.qs.tiles.base.logging.QSTileLogger
 import com.android.systemui.qs.tiles.impl.di.QSTileComponent
+import com.android.systemui.qs.tiles.viewmodel.QSTileConfig
 import com.android.systemui.qs.tiles.viewmodel.QSTileConfigProvider
 import com.android.systemui.qs.tiles.viewmodel.QSTileState
 import com.android.systemui.user.data.repository.UserRepository
@@ -60,12 +61,17 @@
     ) : QSTileViewModelFactory<T> {
 
         /**
-         * Creates [QSTileViewModelImpl] based on the interactors obtained from [component].
-         * Reference of that [component] is then stored along the view model.
+         * Creates [QSTileViewModelImpl] based on the interactors obtained from [QSTileComponent].
+         * Reference of that [QSTileComponent] is then stored along the view model.
          */
-        fun create(tileSpec: TileSpec, component: QSTileComponent<T>): QSTileViewModelImpl<T> =
-            QSTileViewModelImpl(
-                qsTileConfigProvider.getConfig(tileSpec.spec),
+        fun create(
+            tileSpec: TileSpec,
+            componentFactory: (config: QSTileConfig) -> QSTileComponent<T>
+        ): QSTileViewModelImpl<T> {
+            val config = qsTileConfigProvider.getConfig(tileSpec.spec)
+            val component = componentFactory(config)
+            return QSTileViewModelImpl(
+                config,
                 component::userActionInteractor,
                 component::dataInteractor,
                 component::dataToStateMapper,
@@ -77,6 +83,7 @@
                 systemClock,
                 backgroundDispatcher,
             )
+        }
     }
 
     /**
diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/base/viewmodel/QSTileViewModelImpl.kt b/packages/SystemUI/src/com/android/systemui/qs/tiles/base/viewmodel/QSTileViewModelImpl.kt
index 12a083e..5e19439 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/tiles/base/viewmodel/QSTileViewModelImpl.kt
+++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/base/viewmodel/QSTileViewModelImpl.kt
@@ -116,7 +116,7 @@
             )
 
     override fun forceUpdate() {
-        forceUpdates.tryEmit(Unit)
+        tileScope.launch { forceUpdates.emit(Unit) }
     }
 
     override fun onUserChanged(user: UserHandle) {
diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/di/NewQSTileFactory.kt b/packages/SystemUI/src/com/android/systemui/qs/tiles/di/NewQSTileFactory.kt
index 7d7af64..27007bb 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/tiles/di/NewQSTileFactory.kt
+++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/di/NewQSTileFactory.kt
@@ -19,6 +19,11 @@
 import com.android.systemui.dagger.SysUISingleton
 import com.android.systemui.plugins.qs.QSFactory
 import com.android.systemui.plugins.qs.QSTile
+import com.android.systemui.qs.pipeline.shared.TileSpec
+import com.android.systemui.qs.tiles.base.viewmodel.QSTileViewModelFactory
+import com.android.systemui.qs.tiles.impl.custom.di.CustomTileComponent
+import com.android.systemui.qs.tiles.impl.custom.di.QSTileConfigModule
+import com.android.systemui.qs.tiles.impl.custom.domain.entity.CustomTileDataModel
 import com.android.systemui.qs.tiles.viewmodel.QSTileConfigProvider
 import com.android.systemui.qs.tiles.viewmodel.QSTileViewModel
 import com.android.systemui.qs.tiles.viewmodel.QSTileViewModelAdapter
@@ -34,18 +39,31 @@
     private val adapterFactory: QSTileViewModelAdapter.Factory,
     private val tileMap:
         Map<String, @JvmSuppressWildcards Provider<@JvmSuppressWildcards QSTileViewModel>>,
+    private val customTileComponentBuilder: CustomTileComponent.Builder,
+    private val customTileViewModelFactory: QSTileViewModelFactory.Component<CustomTileDataModel>,
 ) : QSFactory {
 
     init {
         for (viewModelTileSpec in tileMap.keys) {
-            // throws an exception when there is no config for a tileSpec of an injected viewModel
-            qsTileConfigProvider.getConfig(viewModelTileSpec)
+            require(qsTileConfigProvider.hasConfig(viewModelTileSpec)) {
+                "No config for $viewModelTileSpec"
+            }
         }
     }
 
-    override fun createTile(tileSpec: String): QSTile? =
-        tileMap[tileSpec]?.let {
-            val tile = it.get()
-            adapterFactory.create(tile)
+    override fun createTile(tileSpec: String): QSTile? {
+        val viewModel: QSTileViewModel =
+            when (val spec = TileSpec.create(tileSpec)) {
+                is TileSpec.CustomTileSpec -> createCustomTileViewModel(spec)
+                is TileSpec.PlatformTileSpec -> tileMap[tileSpec]?.get()
+                is TileSpec.Invalid -> null
+            }
+                ?: return null
+        return adapterFactory.create(viewModel)
+    }
+
+    private fun createCustomTileViewModel(spec: TileSpec.CustomTileSpec): QSTileViewModel =
+        customTileViewModelFactory.create(spec) { config ->
+            customTileComponentBuilder.qsTileConfigModule(QSTileConfigModule(config)).build()
         }
 }
diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/custom/CustomTileInteractor.kt b/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/custom/CustomTileInteractor.kt
index 761274e..14bf25d 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/custom/CustomTileInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/custom/CustomTileInteractor.kt
@@ -19,17 +19,18 @@
 import android.os.UserHandle
 import com.android.systemui.qs.tiles.base.interactor.DataUpdateTrigger
 import com.android.systemui.qs.tiles.base.interactor.QSTileDataInteractor
+import com.android.systemui.qs.tiles.impl.custom.domain.entity.CustomTileDataModel
 import com.android.systemui.qs.tiles.impl.di.QSTileScope
 import javax.inject.Inject
 import kotlinx.coroutines.flow.Flow
 
 @QSTileScope
-class CustomTileInteractor @Inject constructor() : QSTileDataInteractor<CustomTileData> {
+class CustomTileInteractor @Inject constructor() : QSTileDataInteractor<CustomTileDataModel> {
 
     override fun tileData(
         user: UserHandle,
         triggers: Flow<DataUpdateTrigger>
-    ): Flow<CustomTileData> {
+    ): Flow<CustomTileDataModel> {
         TODO("Not yet implemented")
     }
 
diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/custom/CustomTileMapper.kt b/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/custom/CustomTileMapper.kt
index f7bec02..e23a5c2 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/custom/CustomTileMapper.kt
+++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/custom/CustomTileMapper.kt
@@ -17,15 +17,16 @@
 package com.android.systemui.qs.tiles.impl.custom
 
 import com.android.systemui.qs.tiles.base.interactor.QSTileDataToStateMapper
+import com.android.systemui.qs.tiles.impl.custom.domain.entity.CustomTileDataModel
 import com.android.systemui.qs.tiles.impl.di.QSTileScope
 import com.android.systemui.qs.tiles.viewmodel.QSTileConfig
 import com.android.systemui.qs.tiles.viewmodel.QSTileState
 import javax.inject.Inject
 
 @QSTileScope
-class CustomTileMapper @Inject constructor() : QSTileDataToStateMapper<CustomTileData> {
+class CustomTileMapper @Inject constructor() : QSTileDataToStateMapper<CustomTileDataModel> {
 
-    override fun map(config: QSTileConfig, data: CustomTileData): QSTileState {
+    override fun map(config: QSTileConfig, data: CustomTileDataModel): QSTileState {
         TODO("Not yet implemented")
     }
 }
diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/custom/CustomTileUserActionInteractor.kt b/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/custom/CustomTileUserActionInteractor.kt
index 6c1c1a3..f34704b 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/custom/CustomTileUserActionInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/custom/CustomTileUserActionInteractor.kt
@@ -18,14 +18,15 @@
 
 import com.android.systemui.qs.tiles.base.interactor.QSTileInput
 import com.android.systemui.qs.tiles.base.interactor.QSTileUserActionInteractor
+import com.android.systemui.qs.tiles.impl.custom.domain.entity.CustomTileDataModel
 import com.android.systemui.qs.tiles.impl.di.QSTileScope
 import javax.inject.Inject
 
 @QSTileScope
 class CustomTileUserActionInteractor @Inject constructor() :
-    QSTileUserActionInteractor<CustomTileData> {
+    QSTileUserActionInteractor<CustomTileDataModel> {
 
-    override suspend fun handleInput(input: QSTileInput<CustomTileData>) {
+    override suspend fun handleInput(input: QSTileInput<CustomTileDataModel>) {
         TODO("Not yet implemented")
     }
 }
diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/custom/di/CustomTileComponent.kt b/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/custom/di/CustomTileComponent.kt
index 01df906..88bc8fa 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/custom/di/CustomTileComponent.kt
+++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/custom/di/CustomTileComponent.kt
@@ -16,13 +16,14 @@
 
 package com.android.systemui.qs.tiles.impl.custom.di
 
+import com.android.systemui.qs.tiles.impl.custom.domain.entity.CustomTileDataModel
 import com.android.systemui.qs.tiles.impl.di.QSTileComponent
 import com.android.systemui.qs.tiles.impl.di.QSTileScope
 import dagger.Subcomponent
 
 @QSTileScope
 @Subcomponent(modules = [QSTileConfigModule::class, CustomTileModule::class])
-interface CustomTileComponent : QSTileComponent<Any> {
+interface CustomTileComponent : QSTileComponent<CustomTileDataModel> {
 
     @Subcomponent.Builder
     interface Builder {
diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/custom/di/CustomTileModule.kt b/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/custom/di/CustomTileModule.kt
index 482bf9b..83767aa 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/custom/di/CustomTileModule.kt
+++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/custom/di/CustomTileModule.kt
@@ -19,13 +19,13 @@
 import com.android.systemui.qs.tiles.base.interactor.QSTileDataInteractor
 import com.android.systemui.qs.tiles.base.interactor.QSTileDataToStateMapper
 import com.android.systemui.qs.tiles.base.interactor.QSTileUserActionInteractor
-import com.android.systemui.qs.tiles.impl.custom.CustomTileData
 import com.android.systemui.qs.tiles.impl.custom.CustomTileInteractor
 import com.android.systemui.qs.tiles.impl.custom.CustomTileMapper
 import com.android.systemui.qs.tiles.impl.custom.CustomTileUserActionInteractor
 import com.android.systemui.qs.tiles.impl.custom.data.repository.CustomTileDefaultsRepository
 import com.android.systemui.qs.tiles.impl.custom.data.repository.CustomTileDefaultsRepositoryImpl
 import com.android.systemui.qs.tiles.impl.custom.di.bound.CustomTileBoundComponent
+import com.android.systemui.qs.tiles.impl.custom.domain.entity.CustomTileDataModel
 import dagger.Binds
 import dagger.Module
 
@@ -36,15 +36,15 @@
     @Binds
     fun bindDataInteractor(
         dataInteractor: CustomTileInteractor
-    ): QSTileDataInteractor<CustomTileData>
+    ): QSTileDataInteractor<CustomTileDataModel>
 
     @Binds
     fun bindUserActionInteractor(
         userActionInteractor: CustomTileUserActionInteractor
-    ): QSTileUserActionInteractor<CustomTileData>
+    ): QSTileUserActionInteractor<CustomTileDataModel>
 
     @Binds
-    fun bindMapper(customTileMapper: CustomTileMapper): QSTileDataToStateMapper<CustomTileData>
+    fun bindMapper(customTileMapper: CustomTileMapper): QSTileDataToStateMapper<CustomTileDataModel>
 
     @Binds
     fun bindCustomTileDefaultsRepository(
diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/custom/CustomTileData.kt b/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/custom/domain/entity/CustomTileDataModel.kt
similarity index 91%
rename from packages/SystemUI/src/com/android/systemui/qs/tiles/impl/custom/CustomTileData.kt
rename to packages/SystemUI/src/com/android/systemui/qs/tiles/impl/custom/domain/entity/CustomTileDataModel.kt
index bb5a229..f095c01 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/custom/CustomTileData.kt
+++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/custom/domain/entity/CustomTileDataModel.kt
@@ -14,7 +14,7 @@
  * limitations under the License.
  */
 
-package com.android.systemui.qs.tiles.impl.custom
+package com.android.systemui.qs.tiles.impl.custom.domain.entity
 
 import android.content.ComponentName
 import android.graphics.drawable.Icon
@@ -22,12 +22,11 @@
 import android.service.quicksettings.Tile
 import com.android.systemui.qs.tiles.impl.custom.di.bound.CustomTileBoundComponent
 
-data class CustomTileData(
+data class CustomTileDataModel(
     val user: UserHandle,
     val componentName: ComponentName,
     val tile: Tile,
     val callingAppUid: Int,
-    val isActive: Boolean,
     val hasPendingBind: Boolean,
     val shouldShowChevron: Boolean,
     val defaultTileLabel: CharSequence?,
diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/viewmodel/QSTileConfigProvider.kt b/packages/SystemUI/src/com/android/systemui/qs/tiles/viewmodel/QSTileConfigProvider.kt
index 3f3b94e..0609e79 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/tiles/viewmodel/QSTileConfigProvider.kt
+++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/viewmodel/QSTileConfigProvider.kt
@@ -18,20 +18,31 @@
 
 import com.android.internal.util.Preconditions
 import com.android.systemui.dagger.SysUISingleton
+import com.android.systemui.qs.QsEventLogger
+import com.android.systemui.qs.pipeline.shared.TileSpec
 import javax.inject.Inject
 
 interface QSTileConfigProvider {
 
     /**
-     * Returns a [QSTileConfig] for a [tileSpec] or throws [IllegalArgumentException] if there is no
-     * config for such [tileSpec].
+     * Returns a [QSTileConfig] for a [tileSpec]:
+     * - injected config for [TileSpec.PlatformTileSpec] or throws [IllegalArgumentException] if
+     *   there is none
+     * - new config for [TileSpec.CustomTileSpec].
+     * - throws [IllegalArgumentException] for [TileSpec.Invalid]
      */
     fun getConfig(tileSpec: String): QSTileConfig
+
+    fun hasConfig(tileSpec: String): Boolean
 }
 
 @SysUISingleton
-class QSTileConfigProviderImpl @Inject constructor(private val configs: Map<String, QSTileConfig>) :
-    QSTileConfigProvider {
+class QSTileConfigProviderImpl
+@Inject
+constructor(
+    private val configs: Map<String, QSTileConfig>,
+    private val qsEventLogger: QsEventLogger,
+) : QSTileConfigProvider {
 
     init {
         for (entry in configs.entries) {
@@ -44,6 +55,26 @@
         }
     }
 
+    override fun hasConfig(tileSpec: String): Boolean =
+        when (TileSpec.create(tileSpec)) {
+            is TileSpec.PlatformTileSpec -> configs.containsKey(tileSpec)
+            is TileSpec.CustomTileSpec -> true
+            is TileSpec.Invalid -> false
+        }
+
     override fun getConfig(tileSpec: String): QSTileConfig =
-        configs[tileSpec] ?: throw IllegalArgumentException("There is no config for spec=$tileSpec")
+        when (val spec = TileSpec.create(tileSpec)) {
+            is TileSpec.PlatformTileSpec -> {
+                configs[tileSpec]
+                    ?: throw IllegalArgumentException("There is no config for spec=$tileSpec")
+            }
+            is TileSpec.CustomTileSpec ->
+                QSTileConfig(
+                    spec,
+                    QSTileUIConfig.Empty,
+                    qsEventLogger.getNewInstanceId(),
+                )
+            is TileSpec.Invalid ->
+                throw IllegalArgumentException("TileSpec.Invalid doesn't support configs")
+        }
 }
diff --git a/packages/SystemUI/src/com/android/systemui/smartspace/config/BcSmartspaceConfigProvider.kt b/packages/SystemUI/src/com/android/systemui/smartspace/config/BcSmartspaceConfigProvider.kt
index ab0d6e3..922560f 100644
--- a/packages/SystemUI/src/com/android/systemui/smartspace/config/BcSmartspaceConfigProvider.kt
+++ b/packages/SystemUI/src/com/android/systemui/smartspace/config/BcSmartspaceConfigProvider.kt
@@ -17,11 +17,10 @@
 package com.android.systemui.smartspace.config
 
 import com.android.systemui.flags.FeatureFlags
-import com.android.systemui.flags.Flags
 import com.android.systemui.plugins.BcSmartspaceConfigPlugin
 
 class BcSmartspaceConfigProvider(private val featureFlags: FeatureFlags) :
     BcSmartspaceConfigPlugin {
     override val isDefaultDateWeatherDisabled: Boolean
-        get() = featureFlags.isEnabled(Flags.SMARTSPACE_DATE_WEATHER_DECOUPLED)
+        get() = true
 }
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/lockscreen/LockscreenSmartspaceController.kt b/packages/SystemUI/src/com/android/systemui/statusbar/lockscreen/LockscreenSmartspaceController.kt
index 2cd5560..ef87406 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/lockscreen/LockscreenSmartspaceController.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/lockscreen/LockscreenSmartspaceController.kt
@@ -269,8 +269,7 @@
     fun isDateWeatherDecoupled(): Boolean {
         execution.assertIsMainThread()
 
-        return featureFlags.isEnabled(Flags.SMARTSPACE_DATE_WEATHER_DECOUPLED) &&
-                datePlugin != null && weatherPlugin != null
+        return datePlugin != null && weatherPlugin != null
     }
 
     fun isWeatherEnabled(): Boolean {
@@ -501,8 +500,8 @@
     }
 
     private fun filterSmartspaceTarget(t: SmartspaceTarget): Boolean {
-        if (isDateWeatherDecoupled()) {
-            return t.featureType != SmartspaceTarget.FEATURE_WEATHER
+        if (isDateWeatherDecoupled() && t.featureType == SmartspaceTarget.FEATURE_WEATHER) {
+            return false
         }
         if (!showNotifications) {
             return t.featureType == SmartspaceTarget.FEATURE_WEATHER
diff --git a/packages/SystemUI/src/com/android/systemui/volume/VolumeDialogImpl.java b/packages/SystemUI/src/com/android/systemui/volume/VolumeDialogImpl.java
index 84d2b37..404621d 100644
--- a/packages/SystemUI/src/com/android/systemui/volume/VolumeDialogImpl.java
+++ b/packages/SystemUI/src/com/android/systemui/volume/VolumeDialogImpl.java
@@ -34,7 +34,6 @@
 
 import static com.android.internal.jank.InteractionJankMonitor.CUJ_VOLUME_CONTROL;
 import static com.android.internal.jank.InteractionJankMonitor.Configuration.Builder;
-import static com.android.systemui.flags.Flags.ONE_WAY_HAPTICS_API_MIGRATION;
 import static com.android.systemui.volume.Events.DISMISS_REASON_POSTURE_CHANGED;
 import static com.android.systemui.volume.Events.DISMISS_REASON_SETTINGS_CLICKED;
 
@@ -83,7 +82,6 @@
 import android.util.SparseBooleanArray;
 import android.view.ContextThemeWrapper;
 import android.view.Gravity;
-import android.view.HapticFeedbackConstants;
 import android.view.MotionEvent;
 import android.view.View;
 import android.view.View.AccessibilityDelegate;
@@ -120,7 +118,6 @@
 import com.android.systemui.Dumpable;
 import com.android.systemui.Prefs;
 import com.android.systemui.dump.DumpManager;
-import com.android.systemui.flags.FeatureFlags;
 import com.android.systemui.media.dialog.MediaOutputDialogFactory;
 import com.android.systemui.plugins.ActivityStarter;
 import com.android.systemui.plugins.VolumeDialog;
@@ -304,7 +301,6 @@
     private final DevicePostureController mDevicePostureController;
     private @DevicePostureController.DevicePostureInt int mDevicePosture;
     private int mOrientation;
-    private final FeatureFlags mFeatureFlags;
     private final Lazy<SecureSettings> mSecureSettings;
     private int mDialogTimeoutMillis;
 
@@ -323,9 +319,7 @@
             DevicePostureController devicePostureController,
             Looper looper,
             DumpManager dumpManager,
-            FeatureFlags featureFlags,
             Lazy<SecureSettings> secureSettings) {
-        mFeatureFlags = featureFlags;
         mContext =
                 new ContextThemeWrapper(context, R.style.volume_dialog_theme);
         mHandler = new H(looper);
@@ -1373,14 +1367,12 @@
 
     private void provideTouchFeedbackH(int newRingerMode) {
         VibrationEffect effect = null;
-        int hapticConstant = HapticFeedbackConstants.NO_HAPTICS;
         switch (newRingerMode) {
             case RINGER_MODE_NORMAL:
                 mController.scheduleTouchFeedback();
                 break;
             case RINGER_MODE_SILENT:
                 effect = VibrationEffect.get(VibrationEffect.EFFECT_CLICK);
-                hapticConstant = HapticFeedbackConstants.TOGGLE_OFF;
                 break;
             case RINGER_MODE_VIBRATE:
                 // Feedback handled by onStateChange, for feedback both when user toggles
@@ -1388,11 +1380,8 @@
                 break;
             default:
                 effect = VibrationEffect.get(VibrationEffect.EFFECT_DOUBLE_CLICK);
-                hapticConstant = HapticFeedbackConstants.TOGGLE_ON;
         }
-        if (mFeatureFlags.isEnabled(ONE_WAY_HAPTICS_API_MIGRATION)) {
-            mDialogView.performHapticFeedback(hapticConstant);
-        } else if (effect != null) {
+        if (effect != null) {
             mController.vibrate(effect);
         }
     }
@@ -1820,22 +1809,7 @@
                 && mState.ringerModeInternal != -1
                 && mState.ringerModeInternal != state.ringerModeInternal
                 && state.ringerModeInternal == AudioManager.RINGER_MODE_VIBRATE) {
-
-            if (mFeatureFlags.isEnabled(ONE_WAY_HAPTICS_API_MIGRATION)) {
-                if (mShowing) {
-                    // The dialog view is responsible for triggering haptics in the oneway API
-                    mDialogView.performHapticFeedback(HapticFeedbackConstants.TOGGLE_ON);
-                }
-                /*
-                TODO(b/290642122): If the dialog is not showing, we have the case where haptics is
-                enabled by dragging the volume slider of Settings to a value of 0. This must be
-                handled by view Slices in Settings whilst using the performHapticFeedback API.
-                 */
-
-            } else {
-                // Old behavior only active if the oneway API is not used.
-                mController.vibrate(VibrationEffect.get(VibrationEffect.EFFECT_DOUBLE_CLICK));
-            }
+            mController.vibrate(VibrationEffect.get(VibrationEffect.EFFECT_DOUBLE_CLICK));
         }
         mState = state;
         mDynamic.clear();
diff --git a/packages/SystemUI/src/com/android/systemui/volume/dagger/VolumeModule.java b/packages/SystemUI/src/com/android/systemui/volume/dagger/VolumeModule.java
index e3b3c21..53217d4 100644
--- a/packages/SystemUI/src/com/android/systemui/volume/dagger/VolumeModule.java
+++ b/packages/SystemUI/src/com/android/systemui/volume/dagger/VolumeModule.java
@@ -22,7 +22,6 @@
 
 import com.android.internal.jank.InteractionJankMonitor;
 import com.android.systemui.dump.DumpManager;
-import com.android.systemui.flags.FeatureFlags;
 import com.android.systemui.media.dialog.MediaOutputDialogFactory;
 import com.android.systemui.plugins.ActivityStarter;
 import com.android.systemui.plugins.VolumeDialog;
@@ -65,7 +64,6 @@
             CsdWarningDialog.Factory csdFactory,
             DevicePostureController devicePostureController,
             DumpManager dumpManager,
-            FeatureFlags featureFlags,
             Lazy<SecureSettings> secureSettings) {
         VolumeDialogImpl impl = new VolumeDialogImpl(
                 context,
@@ -82,7 +80,6 @@
                 devicePostureController,
                 Looper.getMainLooper(),
                 dumpManager,
-                featureFlags,
                 secureSettings);
         impl.setStreamImportant(AudioManager.STREAM_SYSTEM, false);
         impl.setAutomute(true);
diff --git a/packages/SystemUI/tests/src/com/android/systemui/communal/domain/interactor/CommunalInteractorTest.kt b/packages/SystemUI/tests/src/com/android/systemui/communal/domain/interactor/CommunalInteractorTest.kt
index a6c4f19..af4bf36 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/communal/domain/interactor/CommunalInteractorTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/communal/domain/interactor/CommunalInteractorTest.kt
@@ -240,7 +240,7 @@
         }
 
     @Test
-    fun contentOrdering() =
+    fun ordering_smartspaceBeforeUmoBeforeWidgets() =
         testScope.runTest {
             tutorialRepository.setTutorialSettingState(HUB_MODE_TUTORIAL_COMPLETED)
 
diff --git a/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/viewmodel/QSTileConfigProviderTest.kt b/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/viewmodel/QSTileConfigProviderTest.kt
index 682b2d0..5eca8ca 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/viewmodel/QSTileConfigProviderTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/viewmodel/QSTileConfigProviderTest.kt
@@ -19,7 +19,9 @@
 import androidx.test.ext.junit.runners.AndroidJUnit4
 import androidx.test.filters.SmallTest
 import com.android.systemui.SysuiTestCase
+import com.android.systemui.qs.QsEventLogger
 import com.android.systemui.qs.pipeline.shared.TileSpec
+import com.android.systemui.util.mockito.mock
 import com.google.common.truth.Truth.assertThat
 import org.junit.Test
 import org.junit.runner.RunWith
@@ -29,8 +31,8 @@
 class QSTileConfigProviderTest : SysuiTestCase() {
 
     private val underTest =
-        QSTileConfigProviderImpl(
-            mapOf(VALID_SPEC.spec to QSTileConfigTestBuilder.build { tileSpec = VALID_SPEC })
+        createQSTileConfigProviderImpl(
+            mapOf(VALID_SPEC.spec to QSTileConfigTestBuilder.build { tileSpec = VALID_SPEC }),
         )
 
     @Test
@@ -43,13 +45,31 @@
         underTest.getConfig(INVALID_SPEC.spec)
     }
 
+    @Test
+    fun hasConfigReturnsTrueForValidSpec() {
+        assertThat(underTest.hasConfig(VALID_SPEC.spec)).isTrue()
+    }
+
+    @Test
+    fun hasConfigReturnsFalseForInvalidSpec() {
+        assertThat(underTest.hasConfig(INVALID_SPEC.spec)).isFalse()
+    }
+
     @Test(expected = IllegalArgumentException::class)
     fun validatesSpecUponCreation() {
-        QSTileConfigProviderImpl(
+        createQSTileConfigProviderImpl(
             mapOf(VALID_SPEC.spec to QSTileConfigTestBuilder.build { tileSpec = INVALID_SPEC })
         )
     }
 
+    private fun createQSTileConfigProviderImpl(
+        configs: Map<String, QSTileConfig>
+    ): QSTileConfigProviderImpl =
+        QSTileConfigProviderImpl(
+            configs,
+            mock<QsEventLogger>(),
+        )
+
     private companion object {
 
         val VALID_SPEC = TileSpec.create("valid_tile_spec")
diff --git a/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/viewmodel/QSTileViewModelInterfaceComplianceTest.kt b/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/viewmodel/QSTileViewModelInterfaceComplianceTest.kt
deleted file mode 100644
index d3b7daa..0000000
--- a/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/viewmodel/QSTileViewModelInterfaceComplianceTest.kt
+++ /dev/null
@@ -1,120 +0,0 @@
-/*
- * Copyright (C) 2023 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you 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.qs.tiles.viewmodel
-
-import android.os.UserHandle
-import android.testing.TestableLooper
-import androidx.test.ext.junit.runners.AndroidJUnit4
-import androidx.test.filters.MediumTest
-import com.android.systemui.SysuiTestCase
-import com.android.systemui.classifier.FalsingManagerFake
-import com.android.systemui.common.shared.model.ContentDescription
-import com.android.systemui.common.shared.model.Icon
-import com.android.systemui.qs.tiles.base.analytics.QSTileAnalytics
-import com.android.systemui.qs.tiles.base.interactor.FakeDisabledByPolicyInteractor
-import com.android.systemui.qs.tiles.base.interactor.FakeQSTileDataInteractor
-import com.android.systemui.qs.tiles.base.interactor.FakeQSTileUserActionInteractor
-import com.android.systemui.qs.tiles.base.interactor.QSTileDataToStateMapper
-import com.android.systemui.qs.tiles.base.logging.QSTileLogger
-import com.android.systemui.qs.tiles.base.viewmodel.QSTileViewModelImpl
-import com.android.systemui.user.data.repository.FakeUserRepository
-import com.android.systemui.util.time.FakeSystemClock
-import com.google.common.truth.Truth.assertThat
-import kotlinx.coroutines.flow.launchIn
-import kotlinx.coroutines.test.StandardTestDispatcher
-import kotlinx.coroutines.test.TestScope
-import kotlinx.coroutines.test.runCurrent
-import kotlinx.coroutines.test.runTest
-import org.junit.Before
-import org.junit.Test
-import org.junit.runner.RunWith
-import org.mockito.Mock
-import org.mockito.MockitoAnnotations
-
-// TODO(b/299909368): Add more tests
-@MediumTest
-@RunWith(AndroidJUnit4::class)
-@TestableLooper.RunWithLooper(setAsMainLooper = true)
-class QSTileViewModelInterfaceComplianceTest : SysuiTestCase() {
-
-    @Mock private lateinit var qsTileLogger: QSTileLogger
-    @Mock private lateinit var qsTileAnalytics: QSTileAnalytics
-
-    private val fakeUserRepository = FakeUserRepository()
-    private val fakeQSTileDataInteractor = FakeQSTileDataInteractor<Any>()
-    private val fakeQSTileUserActionInteractor = FakeQSTileUserActionInteractor<Any>()
-    private val fakeDisabledByPolicyInteractor = FakeDisabledByPolicyInteractor()
-    private val fakeFalsingManager = FalsingManagerFake()
-
-    private val testCoroutineDispatcher = StandardTestDispatcher()
-    private val testScope = TestScope(testCoroutineDispatcher)
-
-    private lateinit var underTest: QSTileViewModel
-
-    @Before
-    fun setup() {
-        MockitoAnnotations.initMocks(this)
-        underTest = createViewModel(testScope)
-    }
-
-    @Test
-    fun testDoesntListenStateUntilCreated() =
-        testScope.runTest {
-            assertThat(fakeQSTileDataInteractor.dataRequests).isEmpty()
-
-            assertThat(fakeQSTileDataInteractor.dataRequests).isEmpty()
-
-            underTest.state.launchIn(backgroundScope)
-            runCurrent()
-
-            assertThat(fakeQSTileDataInteractor.dataRequests).isNotEmpty()
-            assertThat(fakeQSTileDataInteractor.dataRequests.first())
-                .isEqualTo(FakeQSTileDataInteractor.DataRequest(UserHandle.of(0)))
-        }
-
-    private fun createViewModel(
-        scope: TestScope,
-        config: QSTileConfig = TEST_QS_TILE_CONFIG,
-    ): QSTileViewModel =
-        QSTileViewModelImpl(
-            config,
-            { fakeQSTileUserActionInteractor },
-            { fakeQSTileDataInteractor },
-            {
-                object : QSTileDataToStateMapper<Any> {
-                    override fun map(config: QSTileConfig, data: Any): QSTileState =
-                        QSTileState.build(
-                            { Icon.Resource(0, ContentDescription.Resource(0)) },
-                            ""
-                        ) {}
-                }
-            },
-            fakeDisabledByPolicyInteractor,
-            fakeUserRepository,
-            fakeFalsingManager,
-            qsTileAnalytics,
-            qsTileLogger,
-            FakeSystemClock(),
-            testCoroutineDispatcher,
-            scope.backgroundScope,
-        )
-
-    private companion object {
-
-        val TEST_QS_TILE_CONFIG = QSTileConfigTestBuilder.build {}
-    }
-}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/viewmodel/QSTileViewModelTest.kt b/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/viewmodel/QSTileViewModelTest.kt
new file mode 100644
index 0000000..3a0ebdb
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/viewmodel/QSTileViewModelTest.kt
@@ -0,0 +1,202 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you 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.qs.tiles.viewmodel
+
+import android.os.UserHandle
+import androidx.test.ext.junit.runners.AndroidJUnit4
+import androidx.test.filters.MediumTest
+import com.android.systemui.SysuiTestCase
+import com.android.systemui.classifier.FalsingManagerFake
+import com.android.systemui.common.shared.model.ContentDescription
+import com.android.systemui.common.shared.model.Icon
+import com.android.systemui.coroutines.collectValues
+import com.android.systemui.qs.tiles.base.analytics.QSTileAnalytics
+import com.android.systemui.qs.tiles.base.interactor.DataUpdateTrigger
+import com.android.systemui.qs.tiles.base.interactor.FakeDisabledByPolicyInteractor
+import com.android.systemui.qs.tiles.base.interactor.FakeQSTileDataInteractor
+import com.android.systemui.qs.tiles.base.interactor.FakeQSTileUserActionInteractor
+import com.android.systemui.qs.tiles.base.interactor.QSTileDataToStateMapper
+import com.android.systemui.qs.tiles.base.logging.QSTileLogger
+import com.android.systemui.qs.tiles.base.viewmodel.QSTileViewModelImpl
+import com.android.systemui.user.data.repository.FakeUserRepository
+import com.android.systemui.util.mockito.eq
+import com.android.systemui.util.time.FakeSystemClock
+import com.google.common.truth.Truth.assertThat
+import kotlinx.coroutines.ExperimentalCoroutinesApi
+import kotlinx.coroutines.flow.launchIn
+import kotlinx.coroutines.test.StandardTestDispatcher
+import kotlinx.coroutines.test.TestScope
+import kotlinx.coroutines.test.runCurrent
+import kotlinx.coroutines.test.runTest
+import org.junit.Before
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.mockito.Mock
+import org.mockito.Mockito.verify
+import org.mockito.MockitoAnnotations
+
+@MediumTest
+@RunWith(AndroidJUnit4::class)
+@OptIn(ExperimentalCoroutinesApi::class)
+class QSTileViewModelTest : SysuiTestCase() {
+
+    @Mock private lateinit var qsTileLogger: QSTileLogger
+    @Mock private lateinit var qsTileAnalytics: QSTileAnalytics
+
+    private val tileConfig =
+        QSTileConfigTestBuilder.build { policy = QSTilePolicy.Restricted("test_restriction") }
+
+    private val userRepository = FakeUserRepository()
+    private val tileDataInteractor = FakeQSTileDataInteractor<String>()
+    private val tileUserActionInteractor = FakeQSTileUserActionInteractor<String>()
+    private val disabledByPolicyInteractor = FakeDisabledByPolicyInteractor()
+    private val falsingManager = FalsingManagerFake()
+
+    private val testCoroutineDispatcher = StandardTestDispatcher()
+    private val testScope = TestScope(testCoroutineDispatcher)
+
+    private lateinit var underTest: QSTileViewModel
+
+    @Before
+    fun setup() {
+        MockitoAnnotations.initMocks(this)
+        underTest = createViewModel(testScope)
+    }
+
+    @Test
+    fun stateReceivedForTheData() =
+        testScope.runTest {
+            val testTileData = "test_tile_data"
+            val states = collectValues(underTest.state)
+            runCurrent()
+
+            tileDataInteractor.emitData(testTileData)
+            runCurrent()
+
+            assertThat(states()).isNotEmpty()
+            assertThat(states().first().label).isEqualTo(testTileData)
+            verify(qsTileLogger).logInitialRequest(eq(tileConfig.tileSpec))
+        }
+
+    @Test
+    fun doesntListenDataIfStateIsntListened() =
+        testScope.runTest {
+            assertThat(tileDataInteractor.dataSubscriptionCount.value).isEqualTo(0)
+
+            underTest.state.launchIn(backgroundScope)
+            runCurrent()
+
+            assertThat(tileDataInteractor.dataSubscriptionCount.value).isEqualTo(1)
+        }
+
+    @Test
+    fun doesntListenAvailabilityIfAvailabilityIsntListened() =
+        testScope.runTest {
+            assertThat(tileDataInteractor.availabilitySubscriptionCount.value).isEqualTo(0)
+
+            underTest.isAvailable.launchIn(backgroundScope)
+            runCurrent()
+
+            assertThat(tileDataInteractor.availabilitySubscriptionCount.value).isEqualTo(1)
+        }
+
+    @Test
+    fun doesntListedDataAfterDestroy() =
+        testScope.runTest {
+            underTest.state.launchIn(backgroundScope)
+            underTest.isAvailable.launchIn(backgroundScope)
+            runCurrent()
+
+            underTest.destroy()
+            runCurrent()
+
+            assertThat(tileDataInteractor.dataSubscriptionCount.value).isEqualTo(0)
+            assertThat(tileDataInteractor.availabilitySubscriptionCount.value).isEqualTo(0)
+        }
+
+    @Test
+    fun forceUpdateTriggersData() =
+        testScope.runTest {
+            underTest.state.launchIn(backgroundScope)
+            runCurrent()
+
+            underTest.forceUpdate()
+            runCurrent()
+
+            assertThat(tileDataInteractor.triggers.last())
+                .isInstanceOf(DataUpdateTrigger.ForceUpdate::class.java)
+            verify(qsTileLogger).logForceUpdate(eq(tileConfig.tileSpec))
+        }
+
+    @Test
+    fun userChangeUpdatesData() =
+        testScope.runTest {
+            underTest.state.launchIn(backgroundScope)
+            runCurrent()
+
+            underTest.onUserChanged(USER)
+            runCurrent()
+
+            assertThat(tileDataInteractor.dataRequests.last())
+                .isEqualTo(FakeQSTileDataInteractor.DataRequest(USER))
+        }
+
+    @Test
+    fun userChangeUpdatesAvailability() =
+        testScope.runTest {
+            underTest.isAvailable.launchIn(backgroundScope)
+            runCurrent()
+
+            underTest.onUserChanged(USER)
+            runCurrent()
+
+            assertThat(tileDataInteractor.availabilityRequests.last())
+                .isEqualTo(FakeQSTileDataInteractor.AvailabilityRequest(USER))
+        }
+
+    private fun createViewModel(
+        scope: TestScope,
+        config: QSTileConfig = tileConfig,
+    ): QSTileViewModel =
+        QSTileViewModelImpl(
+            config,
+            { tileUserActionInteractor },
+            { tileDataInteractor },
+            {
+                object : QSTileDataToStateMapper<String> {
+                    override fun map(config: QSTileConfig, data: String): QSTileState =
+                        QSTileState.build(
+                            { Icon.Resource(0, ContentDescription.Resource(0)) },
+                            data
+                        ) {}
+                }
+            },
+            disabledByPolicyInteractor,
+            userRepository,
+            falsingManager,
+            qsTileAnalytics,
+            qsTileLogger,
+            FakeSystemClock(),
+            testCoroutineDispatcher,
+            scope.backgroundScope,
+        )
+
+    private companion object {
+
+        val USER = UserHandle.of(1)!!
+    }
+}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/viewmodel/QSTileViewModelUserInputTest.kt b/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/viewmodel/QSTileViewModelUserInputTest.kt
new file mode 100644
index 0000000..ea8acc7
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/viewmodel/QSTileViewModelUserInputTest.kt
@@ -0,0 +1,194 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you 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.qs.tiles.viewmodel
+
+import androidx.test.filters.MediumTest
+import com.android.settingslib.RestrictedLockUtils
+import com.android.systemui.SysuiTestCase
+import com.android.systemui.classifier.FalsingManagerFake
+import com.android.systemui.common.shared.model.ContentDescription
+import com.android.systemui.common.shared.model.Icon
+import com.android.systemui.qs.tiles.base.analytics.QSTileAnalytics
+import com.android.systemui.qs.tiles.base.interactor.DataUpdateTrigger
+import com.android.systemui.qs.tiles.base.interactor.DisabledByPolicyInteractor
+import com.android.systemui.qs.tiles.base.interactor.FakeDisabledByPolicyInteractor
+import com.android.systemui.qs.tiles.base.interactor.FakeQSTileDataInteractor
+import com.android.systemui.qs.tiles.base.interactor.FakeQSTileUserActionInteractor
+import com.android.systemui.qs.tiles.base.interactor.QSTileDataToStateMapper
+import com.android.systemui.qs.tiles.base.logging.QSTileLogger
+import com.android.systemui.qs.tiles.base.viewmodel.QSTileViewModelImpl
+import com.android.systemui.user.data.repository.FakeUserRepository
+import com.android.systemui.util.mockito.any
+import com.android.systemui.util.mockito.eq
+import com.android.systemui.util.time.FakeSystemClock
+import com.google.common.truth.Truth.assertThat
+import kotlinx.coroutines.ExperimentalCoroutinesApi
+import kotlinx.coroutines.flow.launchIn
+import kotlinx.coroutines.test.StandardTestDispatcher
+import kotlinx.coroutines.test.TestScope
+import kotlinx.coroutines.test.runCurrent
+import kotlinx.coroutines.test.runTest
+import org.junit.Before
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.junit.runners.Parameterized
+import org.junit.runners.Parameterized.Parameter
+import org.mockito.Mock
+import org.mockito.Mockito.never
+import org.mockito.Mockito.verify
+import org.mockito.MockitoAnnotations
+
+/** Tests all possible [QSTileUserAction]s. If you need */
+@MediumTest
+@RunWith(Parameterized::class)
+@OptIn(ExperimentalCoroutinesApi::class)
+class QSTileViewModelUserInputTest : SysuiTestCase() {
+
+    @Mock private lateinit var qsTileLogger: QSTileLogger
+    @Mock private lateinit var qsTileAnalytics: QSTileAnalytics
+
+    @Parameter lateinit var userAction: QSTileUserAction
+
+    private val tileConfig =
+        QSTileConfigTestBuilder.build { policy = QSTilePolicy.Restricted("test_restriction") }
+
+    private val userRepository = FakeUserRepository()
+    private val tileDataInteractor = FakeQSTileDataInteractor<String>()
+    private val tileUserActionInteractor = FakeQSTileUserActionInteractor<String>()
+    private val disabledByPolicyInteractor = FakeDisabledByPolicyInteractor()
+    private val falsingManager = FalsingManagerFake()
+
+    private val testCoroutineDispatcher = StandardTestDispatcher()
+    private val testScope = TestScope(testCoroutineDispatcher)
+
+    private lateinit var underTest: QSTileViewModel
+
+    @Before
+    fun setup() {
+        MockitoAnnotations.initMocks(this)
+        underTest = createViewModel(testScope)
+    }
+
+    @Test
+    fun userInputTriggersData() =
+        testScope.runTest {
+            tileDataInteractor.emitData("initial_data")
+            underTest.state.launchIn(backgroundScope)
+            runCurrent()
+
+            underTest.onActionPerformed(userAction)
+            runCurrent()
+
+            assertThat(tileDataInteractor.triggers.last())
+                .isInstanceOf(DataUpdateTrigger.UserInput::class.java)
+            verify(qsTileLogger)
+                .logUserAction(eq(userAction), eq(tileConfig.tileSpec), eq(true), eq(true))
+            verify(qsTileLogger)
+                .logUserActionPipeline(
+                    eq(tileConfig.tileSpec),
+                    eq(userAction),
+                    any(),
+                    eq("initial_data")
+                )
+            verify(qsTileAnalytics).trackUserAction(eq(tileConfig), eq(userAction))
+        }
+
+    @Test
+    fun disabledByPolicyUserInputIsSkipped() =
+        testScope.runTest {
+            underTest.state.launchIn(backgroundScope)
+            disabledByPolicyInteractor.policyResult =
+                DisabledByPolicyInteractor.PolicyResult.TileDisabled(
+                    RestrictedLockUtils.EnforcedAdmin()
+                )
+            runCurrent()
+
+            underTest.onActionPerformed(userAction)
+            runCurrent()
+
+            assertThat(tileDataInteractor.triggers.last())
+                .isNotInstanceOf(DataUpdateTrigger.UserInput::class.java)
+            verify(qsTileLogger)
+                .logUserActionRejectedByPolicy(eq(userAction), eq(tileConfig.tileSpec))
+            verify(qsTileAnalytics, never()).trackUserAction(any(), any())
+        }
+
+    @Test
+    fun falsedUserInputIsSkipped() =
+        testScope.runTest {
+            underTest.state.launchIn(backgroundScope)
+            falsingManager.setFalseLongTap(true)
+            falsingManager.setFalseTap(true)
+            runCurrent()
+
+            underTest.onActionPerformed(userAction)
+            runCurrent()
+
+            assertThat(tileDataInteractor.triggers.last())
+                .isNotInstanceOf(DataUpdateTrigger.UserInput::class.java)
+            verify(qsTileLogger)
+                .logUserActionRejectedByFalsing(eq(userAction), eq(tileConfig.tileSpec))
+            verify(qsTileAnalytics, never()).trackUserAction(any(), any())
+        }
+
+    @Test
+    fun userInputIsThrottled() =
+        testScope.runTest {
+            val inputCount = 100
+            underTest.state.launchIn(backgroundScope)
+
+            repeat(inputCount) { underTest.onActionPerformed(userAction) }
+            runCurrent()
+
+            assertThat(tileDataInteractor.triggers.size).isLessThan(inputCount)
+        }
+
+    private fun createViewModel(scope: TestScope): QSTileViewModel =
+        QSTileViewModelImpl(
+            tileConfig,
+            { tileUserActionInteractor },
+            { tileDataInteractor },
+            {
+                object : QSTileDataToStateMapper<String> {
+                    override fun map(config: QSTileConfig, data: String): QSTileState =
+                        QSTileState.build(
+                            { Icon.Resource(0, ContentDescription.Resource(0)) },
+                            data
+                        ) {}
+                }
+            },
+            disabledByPolicyInteractor,
+            userRepository,
+            falsingManager,
+            qsTileAnalytics,
+            qsTileLogger,
+            FakeSystemClock(),
+            testCoroutineDispatcher,
+            scope.backgroundScope,
+        )
+
+    companion object {
+
+        @JvmStatic
+        @Parameterized.Parameters
+        fun data(): Iterable<QSTileUserAction> =
+            listOf(
+                QSTileUserAction.Click(null),
+                QSTileUserAction.LongClick(null),
+            )
+    }
+}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/smartspace/BcSmartspaceConfigProviderTest.kt b/packages/SystemUI/tests/src/com/android/systemui/smartspace/BcSmartspaceConfigProviderTest.kt
index b8fe2f9..cb83e7c 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/smartspace/BcSmartspaceConfigProviderTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/smartspace/BcSmartspaceConfigProviderTest.kt
@@ -20,10 +20,7 @@
 import androidx.test.filters.SmallTest
 import com.android.systemui.SysuiTestCase
 import com.android.systemui.flags.FeatureFlags
-import com.android.systemui.flags.Flags
 import com.android.systemui.smartspace.config.BcSmartspaceConfigProvider
-import com.android.systemui.util.mockito.whenever
-import junit.framework.Assert.assertFalse
 import junit.framework.Assert.assertTrue
 import org.junit.Before
 import org.junit.Test
@@ -45,16 +42,7 @@
     }
 
     @Test
-    fun isDefaultDateWeatherDisabled_flagIsTrue_returnsTrue() {
-        whenever(featureFlags.isEnabled(Flags.SMARTSPACE_DATE_WEATHER_DECOUPLED)).thenReturn(true)
-
+    fun isDefaultDateWeatherDisabled_returnsTrue() {
         assertTrue(configProvider.isDefaultDateWeatherDisabled)
     }
-
-    @Test
-    fun isDefaultDateWeatherDisabled_flagIsFalse_returnsFalse() {
-        whenever(featureFlags.isEnabled(Flags.SMARTSPACE_DATE_WEATHER_DECOUPLED)).thenReturn(false)
-
-        assertFalse(configProvider.isDefaultDateWeatherDisabled)
-    }
 }
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/lockscreen/LockscreenSmartspaceControllerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/lockscreen/LockscreenSmartspaceControllerTest.kt
index 9036f22..8440e00 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/lockscreen/LockscreenSmartspaceControllerTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/lockscreen/LockscreenSmartspaceControllerTest.kt
@@ -38,7 +38,6 @@
 import com.android.systemui.SysuiTestCase
 import com.android.systemui.dump.DumpManager
 import com.android.systemui.flags.FeatureFlags
-import com.android.systemui.flags.Flags
 import com.android.systemui.plugins.ActivityStarter
 import com.android.systemui.plugins.BcSmartspaceConfigPlugin
 import com.android.systemui.plugins.BcSmartspaceDataPlugin
@@ -205,10 +204,6 @@
     fun setUp() {
         MockitoAnnotations.initMocks(this)
 
-        // Todo(b/261760571): flip the flag value here when feature is launched, and update relevant
-        //  tests.
-        `when`(featureFlags.isEnabled(Flags.SMARTSPACE_DATE_WEATHER_DECOUPLED)).thenReturn(false)
-
         `when`(secureSettings.getUriFor(PRIVATE_LOCKSCREEN_SETTING))
                 .thenReturn(fakePrivateLockscreenSettingUri)
         `when`(secureSettings.getUriFor(NOTIF_ON_LOCKSCREEN_SETTING))
@@ -260,17 +255,6 @@
         deviceProvisionedListener = deviceProvisionedCaptor.value
     }
 
-    @Test(expected = RuntimeException::class)
-    fun testBuildAndConnectWeatherView_throwsIfDecouplingDisabled() {
-        // GIVEN the feature flag is disabled
-        `when`(featureFlags.isEnabled(Flags.SMARTSPACE_DATE_WEATHER_DECOUPLED)).thenReturn(false)
-
-        // WHEN we try to build the view
-        controller.buildAndConnectWeatherView(fakeParent)
-
-        // THEN an exception is thrown
-    }
-
     @Test
     fun testBuildAndConnectView_connectsOnlyAfterDeviceIsProvisioned() {
         // GIVEN an unprovisioned device and an attempt to connect
@@ -332,6 +316,8 @@
         clearInvocations(plugin)
 
         // WHEN the session is closed
+        controller.stateChangeListener.onViewDetachedFromWindow(dateSmartspaceView as View)
+        controller.stateChangeListener.onViewDetachedFromWindow(weatherSmartspaceView as View)
         controller.stateChangeListener.onViewDetachedFromWindow(smartspaceView as View)
         controller.disconnect()
 
@@ -376,20 +362,6 @@
         configChangeListener.onThemeChanged()
 
         // We update the new text color to match the wallpaper color
-        verify(smartspaceView).setPrimaryTextColor(anyInt())
-    }
-
-    @Test
-    fun testThemeChange_ifDecouplingEnabled_updatesTextColor() {
-        `when`(featureFlags.isEnabled(Flags.SMARTSPACE_DATE_WEATHER_DECOUPLED)).thenReturn(true)
-
-        // GIVEN a connected smartspace session
-        connectSession()
-
-        // WHEN the theme changes
-        configChangeListener.onThemeChanged()
-
-        // We update the new text color to match the wallpaper color
         verify(dateSmartspaceView).setPrimaryTextColor(anyInt())
         verify(weatherSmartspaceView).setPrimaryTextColor(anyInt())
         verify(smartspaceView).setPrimaryTextColor(anyInt())
@@ -404,20 +376,6 @@
         statusBarStateListener.onDozeAmountChanged(0.1f, 0.7f)
 
         // We pass that along to the view
-        verify(smartspaceView).setDozeAmount(0.7f)
-    }
-
-    @Test
-    fun testDozeAmountChange_ifDecouplingEnabled_updatesViews() {
-        `when`(featureFlags.isEnabled(Flags.SMARTSPACE_DATE_WEATHER_DECOUPLED)).thenReturn(true)
-
-        // GIVEN a connected smartspace session
-        connectSession()
-
-        // WHEN the doze amount changes
-        statusBarStateListener.onDozeAmountChanged(0.1f, 0.7f)
-
-        // We pass that along to the view
         verify(dateSmartspaceView).setDozeAmount(0.7f)
         verify(weatherSmartspaceView).setDozeAmount(0.7f)
         verify(smartspaceView).setDozeAmount(0.7f)
@@ -472,7 +430,7 @@
     }
 
     @Test
-    fun testAllTargetsAreFilteredExceptWeatherWhenNotificationsAreDisabled() {
+    fun testAllTargetsAreFilteredInclWeatherWhenNotificationsAreDisabled() {
         // GIVEN the active user doesn't allow any notifications on lockscreen
         setShowNotifications(userHandlePrimary, false)
         connectSession()
@@ -488,7 +446,7 @@
         sessionListener.onTargetsAvailable(targets)
 
         // THEN all non-sensitive content is still shown
-        verify(plugin).onTargetsAvailable(eq(listOf(targets[3])))
+        verify(plugin).onTargetsAvailable(emptyList())
     }
 
     @Test
@@ -519,8 +477,7 @@
     }
 
     @Test
-    fun testSessionListener_ifDecouplingEnabled_weatherTargetIsFilteredOut() {
-        `when`(featureFlags.isEnabled(Flags.SMARTSPACE_DATE_WEATHER_DECOUPLED)).thenReturn(true)
+    fun testSessionListener_weatherTargetIsFilteredOut() {
         connectSession()
 
         // WHEN we receive a list of targets
@@ -670,8 +627,7 @@
     }
 
     @Test
-    fun testSessionListener_ifDecouplingEnabled_weatherDataUpdates() {
-        `when`(featureFlags.isEnabled(Flags.SMARTSPACE_DATE_WEATHER_DECOUPLED)).thenReturn(true)
+    fun testSessionListener_weatherDataUpdates() {
         connectSession()
 
         clock.setCurrentTimeMillis(SMARTSPACE_TIME_JUST_RIGHT)
@@ -699,33 +655,6 @@
     }
 
     @Test
-    fun testSessionListener_ifDecouplingDisabled_weatherDataUpdates() {
-        `when`(featureFlags.isEnabled(Flags.SMARTSPACE_DATE_WEATHER_DECOUPLED)).thenReturn(false)
-        connectSession()
-
-        clock.setCurrentTimeMillis(SMARTSPACE_TIME_JUST_RIGHT)
-        // WHEN we receive a list of targets
-        val targets = listOf(
-                makeWeatherTargetWithExtras(
-                        id = 1,
-                        userHandle = userHandlePrimary,
-                        description = "Sunny",
-                        state = WeatherData.WeatherStateIcon.SUNNY.id,
-                        temperature = "32",
-                        useCelsius = false),
-                makeTarget(2, userHandlePrimary, isSensitive = true)
-        )
-
-        sessionListener.onTargetsAvailable(targets)
-
-        verify(keyguardUpdateMonitor).sendWeatherData(argThat { w ->
-            w.description == "Sunny" &&
-                    w.state == WeatherData.WeatherStateIcon.SUNNY &&
-                    w.temperature == 32 && !w.useCelsius
-        })
-    }
-
-    @Test
     fun testSettingsAreReloaded() {
         // GIVEN a connected session where the privacy settings later flip to false
         connectSession()
@@ -781,6 +710,8 @@
         connectSession()
 
         // WHEN we are told to cleanup
+        controller.stateChangeListener.onViewDetachedFromWindow(dateSmartspaceView as View)
+        controller.stateChangeListener.onViewDetachedFromWindow(weatherSmartspaceView as View)
         controller.stateChangeListener.onViewDetachedFromWindow(smartspaceView as View)
         controller.disconnect()
 
@@ -816,16 +747,6 @@
     }
 
     @Test
-    fun testWeatherViewUsesSameSession() {
-        `when`(featureFlags.isEnabled(Flags.SMARTSPACE_DATE_WEATHER_DECOUPLED)).thenReturn(true)
-        // GIVEN a connected session
-        connectSession()
-
-        // No checks is needed here, since connectSession() already checks internally that
-        // createSmartspaceSession is invoked only once.
-    }
-
-    @Test
     fun testViewGetInitializedWithBypassEnabledState() {
         // GIVEN keyguard bypass is enabled.
         `when`(keyguardBypassController.bypassEnabled).thenReturn(true)
@@ -853,31 +774,29 @@
     }
 
     private fun connectSession() {
-        if (controller.isDateWeatherDecoupled()) {
-            val dateView = controller.buildAndConnectDateView(fakeParent)
-            dateSmartspaceView = dateView as SmartspaceView
-            fakeParent.addView(dateView)
-            controller.stateChangeListener.onViewAttachedToWindow(dateView)
+        val dateView = controller.buildAndConnectDateView(fakeParent)
+        dateSmartspaceView = dateView as SmartspaceView
+        fakeParent.addView(dateView)
+        controller.stateChangeListener.onViewAttachedToWindow(dateView)
 
-            verify(dateSmartspaceView).setUiSurface(
-                    BcSmartspaceDataPlugin.UI_SURFACE_LOCK_SCREEN_AOD)
-            verify(dateSmartspaceView).registerDataProvider(datePlugin)
+        verify(dateSmartspaceView).setUiSurface(
+                BcSmartspaceDataPlugin.UI_SURFACE_LOCK_SCREEN_AOD)
+        verify(dateSmartspaceView).registerDataProvider(datePlugin)
 
-            verify(dateSmartspaceView).setPrimaryTextColor(anyInt())
-            verify(dateSmartspaceView).setDozeAmount(0.5f)
+        verify(dateSmartspaceView).setPrimaryTextColor(anyInt())
+        verify(dateSmartspaceView).setDozeAmount(0.5f)
 
-            val weatherView = controller.buildAndConnectWeatherView(fakeParent)
-            weatherSmartspaceView = weatherView as SmartspaceView
-            fakeParent.addView(weatherView)
-            controller.stateChangeListener.onViewAttachedToWindow(weatherView)
+        val weatherView = controller.buildAndConnectWeatherView(fakeParent)
+        weatherSmartspaceView = weatherView as SmartspaceView
+        fakeParent.addView(weatherView)
+        controller.stateChangeListener.onViewAttachedToWindow(weatherView)
 
-            verify(weatherSmartspaceView).setUiSurface(
-                    BcSmartspaceDataPlugin.UI_SURFACE_LOCK_SCREEN_AOD)
-            verify(weatherSmartspaceView).registerDataProvider(weatherPlugin)
+        verify(weatherSmartspaceView).setUiSurface(
+                BcSmartspaceDataPlugin.UI_SURFACE_LOCK_SCREEN_AOD)
+        verify(weatherSmartspaceView).registerDataProvider(weatherPlugin)
 
-            verify(weatherSmartspaceView).setPrimaryTextColor(anyInt())
-            verify(weatherSmartspaceView).setDozeAmount(0.5f)
-        }
+        verify(weatherSmartspaceView).setPrimaryTextColor(anyInt())
+        verify(weatherSmartspaceView).setDozeAmount(0.5f)
 
         val view = controller.buildAndConnectView(fakeParent)
         smartspaceView = view as SmartspaceView
@@ -918,10 +837,8 @@
         verify(smartspaceView).setPrimaryTextColor(anyInt())
         verify(smartspaceView).setDozeAmount(0.5f)
 
-        if (controller.isDateWeatherDecoupled()) {
-            clearInvocations(dateSmartspaceView)
-            clearInvocations(weatherSmartspaceView)
-        }
+        clearInvocations(dateSmartspaceView)
+        clearInvocations(weatherSmartspaceView)
         clearInvocations(smartspaceView)
     }
 
diff --git a/packages/SystemUI/tests/src/com/android/systemui/volume/VolumeDialogImplTest.java b/packages/SystemUI/tests/src/com/android/systemui/volume/VolumeDialogImplTest.java
index 7456e00..8c823b2 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/volume/VolumeDialogImplTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/volume/VolumeDialogImplTest.java
@@ -20,7 +20,6 @@
 import static android.media.AudioManager.RINGER_MODE_SILENT;
 import static android.media.AudioManager.RINGER_MODE_VIBRATE;
 
-import static com.android.systemui.flags.Flags.ONE_WAY_HAPTICS_API_MIGRATION;
 import static com.android.systemui.volume.Events.DISMISS_REASON_UNKNOWN;
 import static com.android.systemui.volume.Events.SHOW_REASON_UNKNOWN;
 import static com.android.systemui.volume.VolumeDialogControllerImpl.STREAMS;
@@ -68,7 +67,6 @@
 import com.android.systemui.SysuiTestCase;
 import com.android.systemui.animation.AnimatorTestRule;
 import com.android.systemui.dump.DumpManager;
-import com.android.systemui.flags.FakeFeatureFlags;
 import com.android.systemui.media.dialog.MediaOutputDialogFactory;
 import com.android.systemui.plugins.ActivityStarter;
 import com.android.systemui.plugins.VolumeDialogController;
@@ -149,14 +147,13 @@
         }
     };
 
-    private FakeFeatureFlags mFeatureFlags;
     private int mLongestHideShowAnimationDuration = 250;
     private FakeSettings mSecureSettings;
 
     @Rule
     public final AnimatorTestRule mAnimatorTestRule = new AnimatorTestRule();
 
-    @Before
+   @Before
     public void setup() throws Exception {
         MockitoAnnotations.initMocks(this);
 
@@ -179,8 +176,6 @@
 
         mConfigurationController = new FakeConfigurationController();
 
-        mFeatureFlags = new FakeFeatureFlags();
-
         mSecureSettings = new FakeSettings();
 
         when(mLazySecureSettings.get()).thenReturn(mSecureSettings);
@@ -200,7 +195,6 @@
                 mPostureController,
                 mTestableLooper.getLooper(),
                 mDumpManager,
-                mFeatureFlags,
                 mLazySecureSettings);
         mDialog.init(0, null);
         State state = createShellState();
@@ -328,7 +322,6 @@
 
     @Test
     public void testVibrateOnRingerChangedToVibrate() {
-        mFeatureFlags.set(ONE_WAY_HAPTICS_API_MIGRATION, false);
         final State initialSilentState = new State();
         initialSilentState.ringerModeInternal = AudioManager.RINGER_MODE_SILENT;
 
@@ -349,30 +342,7 @@
     }
 
     @Test
-    public void testControllerDoesNotVibrateOnRingerChangedToVibrate_OnewayAPI_On() {
-        mFeatureFlags.set(ONE_WAY_HAPTICS_API_MIGRATION, true);
-        final State initialSilentState = new State();
-        initialSilentState.ringerModeInternal = AudioManager.RINGER_MODE_SILENT;
-
-        final State vibrateState = new State();
-        vibrateState.ringerModeInternal = AudioManager.RINGER_MODE_VIBRATE;
-
-        // change ringer to silent
-        mDialog.onStateChangedH(initialSilentState);
-
-        // expected: shouldn't call vibrate yet
-        verify(mVolumeDialogController, never()).vibrate(any());
-
-        // changed ringer to vibrate
-        mDialog.onStateChangedH(vibrateState);
-
-        // expected: vibrate method of controller is not used
-        verify(mVolumeDialogController, never()).vibrate(any());
-    }
-
-    @Test
     public void testNoVibrateOnRingerInitialization() {
-        mFeatureFlags.set(ONE_WAY_HAPTICS_API_MIGRATION, false);
         final State initialUnsetState = new State();
         initialUnsetState.ringerModeInternal = -1;
 
@@ -390,29 +360,9 @@
     }
 
     @Test
-    public void testControllerDoesNotVibrateOnRingerInitialization_OnewayAPI_On() {
-        mFeatureFlags.set(ONE_WAY_HAPTICS_API_MIGRATION, true);
-        final State initialUnsetState = new State();
-        initialUnsetState.ringerModeInternal = -1;
-
-        // ringer not initialized yet:
-        mDialog.onStateChangedH(initialUnsetState);
-
-        final State vibrateState = new State();
-        vibrateState.ringerModeInternal = AudioManager.RINGER_MODE_VIBRATE;
-
-        // changed ringer to vibrate
-        mDialog.onStateChangedH(vibrateState);
-
-        // shouldn't call vibrate on the controller either
-        verify(mVolumeDialogController, never()).vibrate(any());
-    }
-
-    @Test
     public void testSelectVibrateFromDrawer() {
         assumeHasDrawer();
 
-        mFeatureFlags.set(ONE_WAY_HAPTICS_API_MIGRATION, false);
         final State initialUnsetState = new State();
         initialUnsetState.ringerModeInternal = AudioManager.RINGER_MODE_NORMAL;
         mDialog.onStateChangedH(initialUnsetState);
@@ -426,27 +376,9 @@
     }
 
     @Test
-    public void testSelectVibrateFromDrawer_OnewayAPI_On() {
-        assumeHasDrawer();
-
-        mFeatureFlags.set(ONE_WAY_HAPTICS_API_MIGRATION, true);
-        final State initialUnsetState = new State();
-        initialUnsetState.ringerModeInternal = RINGER_MODE_NORMAL;
-        mDialog.onStateChangedH(initialUnsetState);
-
-        mActiveRinger.performClick();
-        mDrawerVibrate.performClick();
-
-        // Make sure we've actually changed the ringer mode.
-        verify(mVolumeDialogController, times(1)).setRingerMode(
-                AudioManager.RINGER_MODE_VIBRATE, false);
-    }
-
-    @Test
     public void testSelectMuteFromDrawer() {
         assumeHasDrawer();
 
-        mFeatureFlags.set(ONE_WAY_HAPTICS_API_MIGRATION, false);
         final State initialUnsetState = new State();
         initialUnsetState.ringerModeInternal = AudioManager.RINGER_MODE_NORMAL;
         mDialog.onStateChangedH(initialUnsetState);
@@ -460,27 +392,9 @@
     }
 
     @Test
-    public void testSelectMuteFromDrawer_OnewayAPI_On() {
-        assumeHasDrawer();
-
-        mFeatureFlags.set(ONE_WAY_HAPTICS_API_MIGRATION, true);
-        final State initialUnsetState = new State();
-        initialUnsetState.ringerModeInternal = RINGER_MODE_NORMAL;
-        mDialog.onStateChangedH(initialUnsetState);
-
-        mActiveRinger.performClick();
-        mDrawerMute.performClick();
-
-        // Make sure we've actually changed the ringer mode.
-        verify(mVolumeDialogController, times(1)).setRingerMode(
-                AudioManager.RINGER_MODE_SILENT, false);
-    }
-
-    @Test
     public void testSelectNormalFromDrawer() {
         assumeHasDrawer();
 
-        mFeatureFlags.set(ONE_WAY_HAPTICS_API_MIGRATION, false);
         final State initialUnsetState = new State();
         initialUnsetState.ringerModeInternal = AudioManager.RINGER_MODE_VIBRATE;
         mDialog.onStateChangedH(initialUnsetState);
@@ -493,23 +407,6 @@
                 AudioManager.RINGER_MODE_NORMAL, false);
     }
 
-    @Test
-    public void testSelectNormalFromDrawer_OnewayAPI_On() {
-        assumeHasDrawer();
-
-        mFeatureFlags.set(ONE_WAY_HAPTICS_API_MIGRATION, true);
-        final State initialUnsetState = new State();
-        initialUnsetState.ringerModeInternal = AudioManager.RINGER_MODE_VIBRATE;
-        mDialog.onStateChangedH(initialUnsetState);
-
-        mActiveRinger.performClick();
-        mDrawerNormal.performClick();
-
-        // Make sure we've actually changed the ringer mode.
-        verify(mVolumeDialogController, times(1)).setRingerMode(
-                RINGER_MODE_NORMAL, false);
-    }
-
     /**
      * Ideally we would look at the ringer ImageView and check its assigned drawable id, but that
      * API does not exist. So we do the next best thing; we check the cached icon id.
@@ -682,7 +579,6 @@
 
         State state = createShellState();
         state.ringerModeInternal = ringerMode;
-        mFeatureFlags.set(ONE_WAY_HAPTICS_API_MIGRATION, true);
         mDialog.onStateChangedH(state);
 
         mDialog.show(SHOW_REASON_UNKNOWN);
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/qs/tiles/base/interactor/FakeDisabledByPolicyInteractor.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/qs/tiles/base/interactor/FakeDisabledByPolicyInteractor.kt
index 1efa74b..62765d1 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/qs/tiles/base/interactor/FakeDisabledByPolicyInteractor.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/qs/tiles/base/interactor/FakeDisabledByPolicyInteractor.kt
@@ -20,7 +20,6 @@
 
 class FakeDisabledByPolicyInteractor : DisabledByPolicyInteractor {
 
-    var handleResult: Boolean = false
     var policyResult: DisabledByPolicyInteractor.PolicyResult =
         DisabledByPolicyInteractor.PolicyResult.TileEnabled
 
@@ -31,5 +30,9 @@
 
     override fun handlePolicyResult(
         policyResult: DisabledByPolicyInteractor.PolicyResult
-    ): Boolean = handleResult
+    ): Boolean =
+        when (policyResult) {
+            is DisabledByPolicyInteractor.PolicyResult.TileEnabled -> false
+            is DisabledByPolicyInteractor.PolicyResult.TileDisabled -> true
+        }
 }
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/qs/tiles/base/interactor/FakeQSTileDataInteractor.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/qs/tiles/base/interactor/FakeQSTileDataInteractor.kt
index 2b3330f..3fcf8a9 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/qs/tiles/base/interactor/FakeQSTileDataInteractor.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/qs/tiles/base/interactor/FakeQSTileDataInteractor.kt
@@ -17,16 +17,21 @@
 package com.android.systemui.qs.tiles.base.interactor
 
 import android.os.UserHandle
-import javax.annotation.CheckReturnValue
 import kotlinx.coroutines.flow.Flow
 import kotlinx.coroutines.flow.MutableSharedFlow
 import kotlinx.coroutines.flow.flatMapLatest
 
-class FakeQSTileDataInteractor<T>(
-    private val dataFlow: MutableSharedFlow<T> = MutableSharedFlow(replay = Int.MAX_VALUE),
-    private val availabilityFlow: MutableSharedFlow<Boolean> =
-        MutableSharedFlow(replay = Int.MAX_VALUE),
-) : QSTileDataInteractor<T> {
+class FakeQSTileDataInteractor<T> : QSTileDataInteractor<T> {
+
+    private val dataFlow: MutableSharedFlow<T> = MutableSharedFlow(replay = 1)
+    val dataSubscriptionCount
+        get() = dataFlow.subscriptionCount
+    private val availabilityFlow: MutableSharedFlow<Boolean> = MutableSharedFlow(replay = 1)
+    val availabilitySubscriptionCount
+        get() = availabilityFlow.subscriptionCount
+
+    private val mutableTriggers = mutableListOf<DataUpdateTrigger>()
+    val triggers: List<DataUpdateTrigger> = mutableTriggers
 
     private val mutableDataRequests = mutableListOf<DataRequest>()
     val dataRequests: List<DataRequest> = mutableDataRequests
@@ -34,14 +39,17 @@
     private val mutableAvailabilityRequests = mutableListOf<AvailabilityRequest>()
     val availabilityRequests: List<AvailabilityRequest> = mutableAvailabilityRequests
 
-    @CheckReturnValue fun emitData(data: T): Boolean = dataFlow.tryEmit(data)
+    suspend fun emitData(data: T): Unit = dataFlow.emit(data)
 
     fun tryEmitAvailability(isAvailable: Boolean): Boolean = availabilityFlow.tryEmit(isAvailable)
     suspend fun emitAvailability(isAvailable: Boolean) = availabilityFlow.emit(isAvailable)
 
     override fun tileData(user: UserHandle, triggers: Flow<DataUpdateTrigger>): Flow<T> {
         mutableDataRequests.add(DataRequest(user))
-        return triggers.flatMapLatest { dataFlow }
+        return triggers.flatMapLatest {
+            mutableTriggers.add(it)
+            dataFlow
+        }
     }
 
     override fun availability(user: UserHandle): Flow<Boolean> {
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/qs/tiles/viewmodel/FakeQSTileConfigProvider.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/qs/tiles/viewmodel/FakeQSTileConfigProvider.kt
index de72a7d..d231d63 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/qs/tiles/viewmodel/FakeQSTileConfigProvider.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/qs/tiles/viewmodel/FakeQSTileConfigProvider.kt
@@ -24,6 +24,8 @@
 
     override fun getConfig(tileSpec: String): QSTileConfig = configs.getValue(tileSpec)
 
+    override fun hasConfig(tileSpec: String): Boolean = configs.containsKey(tileSpec)
+
     fun putConfig(tileSpec: TileSpec, config: QSTileConfig) {
         configs[tileSpec.spec] = config
     }
diff --git a/ravenwood/Android.bp b/ravenwood/Android.bp
index ec12d21..fc4ed1d 100644
--- a/ravenwood/Android.bp
+++ b/ravenwood/Android.bp
@@ -28,9 +28,8 @@
     name: "ravenwood-junit",
     srcs: ["junit-src/**/*.java"],
     libs: [
+        "framework-minus-apex.ravenwood",
         "junit",
     ],
-    sdk_version: "core_current",
-    host_supported: true,
     visibility: ["//visibility:public"],
 }
diff --git a/ravenwood/README-ravenwood+mockito.md b/ravenwood/README-ravenwood+mockito.md
new file mode 100644
index 0000000..6adb6144
--- /dev/null
+++ b/ravenwood/README-ravenwood+mockito.md
@@ -0,0 +1,24 @@
+# Ravenwood and Mockito
+
+Last update: 2023-11-13
+
+- As of 2023-11-13, `external/mockito` is based on version 2.x.
+- Mockito didn't support static mocking before 3.4.0.
+  See: https://javadoc.io/doc/org.mockito/mockito-core/latest/org/mockito/Mockito.html#48
+
+- Latest Mockito is 5.*. According to https://github.com/mockito/mockito:
+  `Mockito 3 does not introduce any breaking API changes, but now requires Java 8 over Java 6 for Mockito 2. Mockito 4 removes deprecated API. Mockito 5 switches the default mockmaker to mockito-inline, and now requires Java 11.`
+
+- Mockito now supports Android natively.
+  See: https://javadoc.io/doc/org.mockito/mockito-core/latest/org/mockito/Mockito.html#0.1
+  - But it's unclear at this point to omakoto@ how the `mockito-android` module is built.
+
+- Potential plan:
+  - Ideal option:
+    - If we can update `external/mockito`, that'd be great, but it may not work because
+      Mockito has removed the deprecated APIs.
+  - Second option:
+    - Import the latest mockito as `external/mockito-new`, and require ravenwood
+      to use this one.
+    - The latest mockito needs be exposed to all of 1) device tests, 2) host tests, and 3) ravenwood tests.
+    - This probably will require the latest `bytebuddy` and `objenesis`.
\ No newline at end of file
diff --git a/ravenwood/annotations-src/android/ravenwood/annotations/RavenwoodClassLoadHook.java b/ravenwood/annotations-src/android/ravenwood/annotation/RavenwoodClassLoadHook.java
similarity index 96%
rename from ravenwood/annotations-src/android/ravenwood/annotations/RavenwoodClassLoadHook.java
rename to ravenwood/annotations-src/android/ravenwood/annotation/RavenwoodClassLoadHook.java
index 76964a7..7dc197e 100644
--- a/ravenwood/annotations-src/android/ravenwood/annotations/RavenwoodClassLoadHook.java
+++ b/ravenwood/annotations-src/android/ravenwood/annotation/RavenwoodClassLoadHook.java
@@ -13,7 +13,7 @@
  * See the License for the specific language governing permissions and
  * limitations under the License.
  */
-package android.ravenwood.annotations;
+package android.ravenwood.annotation;
 
 import static java.lang.annotation.ElementType.TYPE;
 
diff --git a/ravenwood/annotations-src/android/ravenwood/annotations/RavenwoodKeep.java b/ravenwood/annotations-src/android/ravenwood/annotation/RavenwoodKeep.java
similarity index 96%
rename from ravenwood/annotations-src/android/ravenwood/annotations/RavenwoodKeep.java
rename to ravenwood/annotations-src/android/ravenwood/annotation/RavenwoodKeep.java
index ddf65dc..1d31579 100644
--- a/ravenwood/annotations-src/android/ravenwood/annotations/RavenwoodKeep.java
+++ b/ravenwood/annotations-src/android/ravenwood/annotation/RavenwoodKeep.java
@@ -13,7 +13,7 @@
  * See the License for the specific language governing permissions and
  * limitations under the License.
  */
-package android.ravenwood.annotations;
+package android.ravenwood.annotation;
 
 import static java.lang.annotation.ElementType.CONSTRUCTOR;
 import static java.lang.annotation.ElementType.FIELD;
diff --git a/ravenwood/annotations-src/android/ravenwood/annotations/RavenwoodWholeClassKeep.java b/ravenwood/annotations-src/android/ravenwood/annotation/RavenwoodKeepWholeClass.java
similarity index 93%
rename from ravenwood/annotations-src/android/ravenwood/annotations/RavenwoodWholeClassKeep.java
rename to ravenwood/annotations-src/android/ravenwood/annotation/RavenwoodKeepWholeClass.java
index d7ef7f5..d2c77c1 100644
--- a/ravenwood/annotations-src/android/ravenwood/annotations/RavenwoodWholeClassKeep.java
+++ b/ravenwood/annotations-src/android/ravenwood/annotation/RavenwoodKeepWholeClass.java
@@ -13,7 +13,7 @@
  * See the License for the specific language governing permissions and
  * limitations under the License.
  */
-package android.ravenwood.annotations;
+package android.ravenwood.annotation;
 
 import static java.lang.annotation.ElementType.CONSTRUCTOR;
 import static java.lang.annotation.ElementType.FIELD;
@@ -35,5 +35,5 @@
  */
 @Target({TYPE, FIELD, METHOD, CONSTRUCTOR})
 @Retention(RetentionPolicy.CLASS)
-public @interface RavenwoodWholeClassKeep {
+public @interface RavenwoodKeepWholeClass {
 }
diff --git a/ravenwood/annotations-src/android/ravenwood/annotations/RavenwoodNativeSubstitutionClass.java b/ravenwood/annotations-src/android/ravenwood/annotation/RavenwoodNativeSubstitutionClass.java
similarity index 96%
rename from ravenwood/annotations-src/android/ravenwood/annotations/RavenwoodNativeSubstitutionClass.java
rename to ravenwood/annotations-src/android/ravenwood/annotation/RavenwoodNativeSubstitutionClass.java
index 8cdc1ff..4b9cf85 100644
--- a/ravenwood/annotations-src/android/ravenwood/annotations/RavenwoodNativeSubstitutionClass.java
+++ b/ravenwood/annotations-src/android/ravenwood/annotation/RavenwoodNativeSubstitutionClass.java
@@ -13,7 +13,7 @@
  * See the License for the specific language governing permissions and
  * limitations under the License.
  */
-package android.ravenwood.annotations;
+package android.ravenwood.annotation;
 
 import static java.lang.annotation.ElementType.TYPE;
 
diff --git a/ravenwood/annotations-src/android/ravenwood/annotations/RavenwoodRemove.java b/ravenwood/annotations-src/android/ravenwood/annotation/RavenwoodRemove.java
similarity index 96%
rename from ravenwood/annotations-src/android/ravenwood/annotations/RavenwoodRemove.java
rename to ravenwood/annotations-src/android/ravenwood/annotation/RavenwoodRemove.java
index 759c918..6727327 100644
--- a/ravenwood/annotations-src/android/ravenwood/annotations/RavenwoodRemove.java
+++ b/ravenwood/annotations-src/android/ravenwood/annotation/RavenwoodRemove.java
@@ -13,7 +13,7 @@
  * See the License for the specific language governing permissions and
  * limitations under the License.
  */
-package android.ravenwood.annotations;
+package android.ravenwood.annotation;
 
 import static java.lang.annotation.ElementType.CONSTRUCTOR;
 import static java.lang.annotation.ElementType.FIELD;
diff --git a/ravenwood/annotations-src/android/ravenwood/annotations/RavenwoodNativeSubstitutionClass.java b/ravenwood/annotations-src/android/ravenwood/annotation/RavenwoodReplace.java
similarity index 83%
copy from ravenwood/annotations-src/android/ravenwood/annotations/RavenwoodNativeSubstitutionClass.java
copy to ravenwood/annotations-src/android/ravenwood/annotation/RavenwoodReplace.java
index 8cdc1ff..a920f63 100644
--- a/ravenwood/annotations-src/android/ravenwood/annotations/RavenwoodNativeSubstitutionClass.java
+++ b/ravenwood/annotations-src/android/ravenwood/annotation/RavenwoodReplace.java
@@ -13,9 +13,9 @@
  * See the License for the specific language governing permissions and
  * limitations under the License.
  */
-package android.ravenwood.annotations;
+package android.ravenwood.annotation;
 
-import static java.lang.annotation.ElementType.TYPE;
+import static java.lang.annotation.ElementType.METHOD;
 
 import java.lang.annotation.Retention;
 import java.lang.annotation.RetentionPolicy;
@@ -29,8 +29,7 @@
  *
  * @hide
  */
-@Target({TYPE})
+@Target({METHOD})
 @Retention(RetentionPolicy.CLASS)
-public @interface RavenwoodNativeSubstitutionClass {
-    String value();
+public @interface RavenwoodReplace {
 }
diff --git a/ravenwood/annotations-src/android/ravenwood/annotations/RavenwoodThrow.java b/ravenwood/annotations-src/android/ravenwood/annotation/RavenwoodThrow.java
similarity index 96%
rename from ravenwood/annotations-src/android/ravenwood/annotations/RavenwoodThrow.java
rename to ravenwood/annotations-src/android/ravenwood/annotation/RavenwoodThrow.java
index de3dd04..a234a9b 100644
--- a/ravenwood/annotations-src/android/ravenwood/annotations/RavenwoodThrow.java
+++ b/ravenwood/annotations-src/android/ravenwood/annotation/RavenwoodThrow.java
@@ -13,7 +13,7 @@
  * See the License for the specific language governing permissions and
  * limitations under the License.
  */
-package android.ravenwood.annotations;
+package android.ravenwood.annotation;
 
 import static java.lang.annotation.ElementType.CONSTRUCTOR;
 import static java.lang.annotation.ElementType.METHOD;
diff --git a/ravenwood/annotations-src/android/ravenwood/annotations/RavenwoodSubstitute.java b/ravenwood/annotations-src/android/ravenwood/annotations/RavenwoodSubstitute.java
deleted file mode 100644
index 5a0a8f4..0000000
--- a/ravenwood/annotations-src/android/ravenwood/annotations/RavenwoodSubstitute.java
+++ /dev/null
@@ -1,38 +0,0 @@
-/*
- * Copyright (C) 2023 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you 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.ravenwood.annotations;
-
-import static java.lang.annotation.ElementType.METHOD;
-
-import java.lang.annotation.Retention;
-import java.lang.annotation.RetentionPolicy;
-import java.lang.annotation.Target;
-
-/**
- * THIS ANNOTATION IS EXPERIMENTAL. REACH OUT TO g/ravenwood BEFORE USING IT, OR YOU HAVE ANY
- * QUESTIONS ABOUT IT.
- *
- * TODO: Javadoc
- *
- * @hide
- */
-@Target({METHOD})
-@Retention(RetentionPolicy.CLASS)
-public @interface RavenwoodSubstitute {
-    // TODO We should add "_host" as default. We're not doing it yet, because extractign the default
-    // value with ASM doesn't seem trivial. (? not sure.)
-    String suffix();
-}
diff --git a/ravenwood/framework-minus-apex-ravenwood-policies.txt b/ravenwood/framework-minus-apex-ravenwood-policies.txt
index 48c0a2d..692d598 100644
--- a/ravenwood/framework-minus-apex-ravenwood-policies.txt
+++ b/ravenwood/framework-minus-apex-ravenwood-policies.txt
@@ -76,21 +76,13 @@
 class android.util.UtilConfig stubclass
 
 # Internals
-class com.android.internal.util.ArrayUtils stubclass
-    method newUnpaddedByteArray (I)[B @newUnpaddedByteArray$ravenwood
-    method newUnpaddedCharArray (I)[C @newUnpaddedCharArray$ravenwood
-    method newUnpaddedIntArray (I)[I @newUnpaddedIntArray$ravenwood
-    method newUnpaddedBooleanArray (I)[Z @newUnpaddedBooleanArray$ravenwood
-    method newUnpaddedLongArray (I)[J @newUnpaddedLongArray$ravenwood
-    method newUnpaddedFloatArray (I)[F @newUnpaddedFloatArray$ravenwood
-    method newUnpaddedObjectArray (I)[Ljava/lang/Object; @newUnpaddedObjectArray$ravenwood
-    method newUnpaddedArray (Ljava/lang/Class;I)[Ljava/lang/Object; @newUnpaddedArray$ravenwood
-
 class com.android.internal.util.GrowingArrayUtils stubclass
 class com.android.internal.util.LineBreakBufferedWriter stubclass
 class com.android.internal.util.Preconditions stubclass
 class com.android.internal.util.StringPool stubclass
 
+class com.android.internal.os.SomeArgs stubclass
+
 # Parcel
 class android.os.Parcel stubclass
     method writeException (Ljava/lang/Exception;)V @writeException$ravenwood
@@ -102,14 +94,16 @@
 class android.os.BadParcelableException stubclass
 class android.os.BadTypeParcelableException stubclass
 
-# Binder: just enough to construct, no further functionality
-class android.os.Binder stub
-    method <init> ()V stub
-    method <init> (Ljava/lang/String;)V stub
-    method isDirectlyHandlingTransaction ()Z stub
-    method isDirectlyHandlingTransactionNative ()Z @isDirectlyHandlingTransactionNative$ravenwood
-    method getNativeBBinderHolder ()J @getNativeBBinderHolder$ravenwood
+# Binder
+class android.os.DeadObjectException stubclass
+class android.os.DeadSystemException stubclass
+class android.os.RemoteException stubclass
+class android.os.TransactionTooLargeException stubclass
 
 # Containers
 class android.os.BaseBundle stubclass
 class android.os.Bundle stubclass
+
+# Misc
+class android.os.PatternMatcher stubclass
+class android.os.ParcelUuid stubclass
diff --git a/ravenwood/junit-src/android/platform/test/ravenwood/RavenwoodRule.java b/ravenwood/junit-src/android/platform/test/ravenwood/RavenwoodRule.java
index a6b3f66..bffd0cd 100644
--- a/ravenwood/junit-src/android/platform/test/ravenwood/RavenwoodRule.java
+++ b/ravenwood/junit-src/android/platform/test/ravenwood/RavenwoodRule.java
@@ -16,6 +16,7 @@
 
 package android.platform.test.ravenwood;
 
+import android.os.Process;
 import android.platform.test.annotations.IgnoreUnderRavenwood;
 
 import org.junit.Assume;
@@ -23,6 +24,8 @@
 import org.junit.runner.Description;
 import org.junit.runners.model.Statement;
 
+import java.util.concurrent.atomic.AtomicInteger;
+
 /**
  * THIS RULE IS EXPERIMENTAL. REACH OUT TO g/ravenwood BEFORE USING IT, OR YOU HAVE ANY
  * QUESTIONS ABOUT IT.
@@ -30,20 +33,84 @@
  * @hide
  */
 public class RavenwoodRule implements TestRule {
+    private static AtomicInteger sNextPid = new AtomicInteger(100);
+
+    /**
+     * Unless the test author requests differently, run as "nobody", and give each collection of
+     * tests its own unique PID.
+     */
+    private int mUid = android.os.Process.NOBODY_UID;
+    private int mPid = sNextPid.getAndIncrement();
+
+    public RavenwoodRule() {
+    }
+
+    public static class Builder {
+        private RavenwoodRule mRule = new RavenwoodRule();
+
+        public Builder() {
+        }
+
+        /**
+         * Configure the identity of this process to be the system UID for the duration of the
+         * test. Has no effect under non-Ravenwood environments.
+         */
+        public Builder setProcessSystem() {
+            mRule.mUid = android.os.Process.SYSTEM_UID;
+            return this;
+        }
+
+        /**
+         * Configure the identity of this process to be an app UID for the duration of the
+         * test. Has no effect under non-Ravenwood environments.
+         */
+        public Builder setProcessApp() {
+            mRule.mUid = android.os.Process.FIRST_APPLICATION_UID;
+            return this;
+        }
+
+        public RavenwoodRule build() {
+            return mRule;
+        }
+    }
+
+    /**
+     * Return if the current process is running under a Ravenwood test environment.
+     */
     public boolean isUnderRavenwood() {
         // TODO: give ourselves a better environment signal
         return System.getProperty("java.class.path").contains("ravenwood");
     }
 
+    private void init() {
+        android.os.Process.init$ravenwood(mUid, mPid);
+        android.os.Binder.init$ravenwood();
+    }
+
+    private void reset() {
+        android.os.Process.reset$ravenwood();
+        android.os.Binder.reset$ravenwood();
+    }
+
     @Override
     public Statement apply(Statement base, Description description) {
         return new Statement() {
             @Override
             public void evaluate() throws Throwable {
+                final boolean isUnderRavenwood = isUnderRavenwood();
                 if (description.getAnnotation(IgnoreUnderRavenwood.class) != null) {
-                    Assume.assumeFalse(isUnderRavenwood());
+                    Assume.assumeFalse(isUnderRavenwood);
                 }
-                base.evaluate();
+                if (isUnderRavenwood) {
+                    init();
+                }
+                try {
+                    base.evaluate();
+                } finally {
+                    if (isUnderRavenwood) {
+                        reset();
+                    }
+                }
             }
         };
     }
diff --git a/ravenwood/mockito/Android.bp b/ravenwood/mockito/Android.bp
new file mode 100644
index 0000000..4135022
--- /dev/null
+++ b/ravenwood/mockito/Android.bp
@@ -0,0 +1,72 @@
+package {
+    // See: http://go/android-license-faq
+    // A large-scale-change added 'default_applicable_licenses' to import
+    // all of the 'license_kinds' from "frameworks_base_license"
+    // to get the below license kinds:
+    //   SPDX-license-identifier-Apache-2.0
+    default_applicable_licenses: ["frameworks_base_license"],
+}
+
+// Ravenwood tests run on the hostside, so we need mockit of the host variant.
+// But we need to use it in modules of the android variant, so we "wash" the variant with it.
+java_host_for_device {
+    name: "mockito_ravenwood",
+    libs: [
+        "mockito",
+        "objenesis",
+    ],
+}
+
+android_ravenwood_test {
+    name: "RavenwoodMockitoTest",
+
+    srcs: [
+        "test/**/*.java",
+    ],
+    static_libs: [
+        "junit",
+        "truth",
+
+        "mockito_ravenwood",
+    ],
+    libs: [
+        "android.test.mock",
+        "android.test.base",
+        "android.test.runner",
+    ],
+    auto_gen_config: true,
+}
+
+android_test {
+    name: "RavenwoodMockitoTest_device",
+
+    srcs: [
+        "test/**/*.java",
+    ],
+    static_libs: [
+        "junit",
+        "truth",
+
+        "androidx.test.rules",
+
+        "ravenwood-junit",
+
+        "mockito-target-extended-minus-junit4",
+    ],
+    libs: [
+        "android.test.mock",
+        "android.test.base",
+        "android.test.runner",
+    ],
+    jni_libs: [
+        // Required by mockito
+        "libdexmakerjvmtiagent",
+        "libstaticjvmtiagent",
+    ],
+    test_suites: [
+        "device-tests",
+    ],
+    optimize: {
+        enabled: false,
+    },
+}
diff --git a/ravenwood/mockito/AndroidManifest.xml b/ravenwood/mockito/AndroidManifest.xml
new file mode 100644
index 0000000..15f0a29
--- /dev/null
+++ b/ravenwood/mockito/AndroidManifest.xml
@@ -0,0 +1,28 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- 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.
+-->
+
+<manifest xmlns:android="http://schemas.android.com/apk/res/android"
+        package="com.android.ravenwood.mockitotest">
+
+    <application android:debuggable="true" >
+        <uses-library android:name="android.test.runner" />
+    </application>
+
+    <instrumentation
+        android:name="androidx.test.runner.AndroidJUnitRunner"
+        android:targetPackage="com.android.ravenwood.mockitotest"
+        />
+</manifest>
diff --git a/ravenwood/mockito/AndroidTest.xml b/ravenwood/mockito/AndroidTest.xml
new file mode 100644
index 0000000..96bc275
--- /dev/null
+++ b/ravenwood/mockito/AndroidTest.xml
@@ -0,0 +1,31 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (C) 2023 The Android Open Source Project
+
+     Licensed under the Apache License, Version 2.0 (the "License");
+     you 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.
+-->
+<configuration description="Runs Frameworks Services Tests.">
+    <option name="test-suite-tag" value="apct" />
+    <option name="test-suite-tag" value="apct-instrumentation" />
+
+    <target_preparer class="com.android.tradefed.targetprep.suite.SuiteApkInstaller">
+        <option name="cleanup-apks" value="true" />
+        <option name="test-file-name" value="RavenwoodMockitoTest_device.apk" />
+    </target_preparer>
+
+    <option name="test-tag" value="FrameworksMockingServicesTests" />
+
+    <test class="com.android.tradefed.testtype.AndroidJUnitTest" >
+        <option name="package" value="com.android.ravenwood.mockitotest" />
+        <option name="runner" value="androidx.test.runner.AndroidJUnitRunner" />
+    </test>
+</configuration>
diff --git a/ravenwood/mockito/test/com/android/ravenwood/mockito/RavenwoodMockitoTest.java b/ravenwood/mockito/test/com/android/ravenwood/mockito/RavenwoodMockitoTest.java
new file mode 100644
index 0000000..36fa3dd
--- /dev/null
+++ b/ravenwood/mockito/test/com/android/ravenwood/mockito/RavenwoodMockitoTest.java
@@ -0,0 +1,106 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you 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.ravenwood.mockito;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.when;
+
+import android.content.Context;
+import android.content.Intent;
+import android.platform.test.annotations.IgnoreUnderRavenwood;
+import android.platform.test.ravenwood.RavenwoodRule;
+
+import org.junit.Rule;
+import org.junit.Test;
+
+public class RavenwoodMockitoTest {
+    @Rule public final RavenwoodRule mRavenwood = new RavenwoodRule();
+
+
+// Use this to mock static methods, which isn't supported by mockito 2.
+// Mockito supports static mocking since 3.4.0:
+// See: https://javadoc.io/doc/org.mockito/mockito-core/latest/org/mockito/Mockito.html#48
+
+//    private MockitoSession mMockingSession;
+//
+//    @Before
+//    public void setUp() {
+//        mMockingSession = mockitoSession()
+//                .strictness(Strictness.LENIENT)
+//                .mockStatic(RavenwoodMockitoTest.class)
+//                .startMocking();
+//    }
+//
+//    @After
+//    public void tearDown() {
+//        if (mMockingSession != null) {
+//            mMockingSession.finishMocking();
+//        }
+//    }
+
+    @Test
+    public void testMockJdkClass() {
+        Process object = mock(Process.class);
+
+        when(object.exitValue()).thenReturn(42);
+
+        assertThat(object.exitValue()).isEqualTo(42);
+    }
+
+    /*
+ - Intent can't be mocked because of the dependency to `org.xmlpull.v1.XmlPullParser`.
+   (The error says "Mockito can only mock non-private & non-final classes", but that's likely a
+   red-herring.)
+
+STACKTRACE:
+org.mockito.exceptions.base.MockitoException:
+Mockito cannot mock this class: class android.content.Intent.
+
+  :
+
+Underlying exception : java.lang.IllegalArgumentException: Could not create type
+    at com.android.ravenwood.mockito.RavenwoodMockitoTest.testMockAndroidClass1
+    at java.base/jdk.internal.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
+
+  :
+
+Caused by: java.lang.ClassNotFoundException: org.xmlpull.v1.XmlPullParser
+    at java.base/jdk.internal.loader.BuiltinClassLoader.loadClass(BuiltinClassLoader.java:641)
+    at java.base/jdk.internal.loader.ClassLoaders$AppClassLoader.loadClass(ClassLoaders.java:188)
+    at java.base/java.lang.ClassLoader.loadClass(ClassLoader.java:520)
+    ... 54 more
+     */
+    @Test
+    @IgnoreUnderRavenwood
+    public void testMockAndroidClass1() {
+        Intent object = mock(Intent.class);
+
+        when(object.getAction()).thenReturn("ACTION_RAVENWOOD");
+
+        assertThat(object.getAction()).isEqualTo("ACTION_RAVENWOOD");
+    }
+
+    @Test
+    public void testMockAndroidClass2() {
+        Context object = mock(Context.class);
+
+        when(object.getPackageName()).thenReturn("android");
+
+        assertThat(object.getPackageName()).isEqualTo("android");
+    }
+}
diff --git a/ravenwood/ravenwood-annotation-allowed-classes.txt b/ravenwood/ravenwood-annotation-allowed-classes.txt
index 0811f90..776a19a 100644
--- a/ravenwood/ravenwood-annotation-allowed-classes.txt
+++ b/ravenwood/ravenwood-annotation-allowed-classes.txt
@@ -1,2 +1,9 @@
 # Only classes listed here can use the Ravenwood annotations.
 
+com.android.internal.util.ArrayUtils
+
+android.os.Binder
+android.os.Binder$IdentitySupplier
+android.os.IBinder
+android.os.Process
+android.os.SystemClock
diff --git a/ravenwood/ravenwood-standard-options.txt b/ravenwood/ravenwood-standard-options.txt
index 6e1384f..4b07ef6 100644
--- a/ravenwood/ravenwood-standard-options.txt
+++ b/ravenwood/ravenwood-standard-options.txt
@@ -16,22 +16,22 @@
 # Standard annotations.
 # Note, each line is a single argument, so we need newlines after each `--xxx-annotation`.
 --keep-annotation
-    android.ravenwood.annotations.RavenwoodKeep
+    android.ravenwood.annotation.RavenwoodKeep
 
 --keep-class-annotation
-    android.ravenwood.annotations.RavenwoodWholeClassKeep
+    android.ravenwood.annotation.RavenwoodKeepWholeClass
 
 --throw-annotation
-    android.ravenwood.annotations.RavenwoodThrow
+    android.ravenwood.annotation.RavenwoodThrow
 
 --remove-annotation
-    android.ravenwood.annotations.RavenwoodRemove
+    android.ravenwood.annotation.RavenwoodRemove
 
 --substitute-annotation
-    android.ravenwood.annotations.RavenwoodSubstitute
+    android.ravenwood.annotation.RavenwoodReplace
 
 --native-substitute-annotation
-    android.ravenwood.annotations.RavenwoodNativeSubstitutionClass
+    android.ravenwood.annotation.RavenwoodNativeSubstitutionClass
 
 --class-load-hook-annotation
-    android.ravenwood.annotations.RavenwoodClassLoadHook
+    android.ravenwood.annotation.RavenwoodClassLoadHook
diff --git a/services/companion/java/com/android/server/companion/virtual/VirtualDeviceManagerService.java b/services/companion/java/com/android/server/companion/virtual/VirtualDeviceManagerService.java
index 92af68b..85b3c9a 100644
--- a/services/companion/java/com/android/server/companion/virtual/VirtualDeviceManagerService.java
+++ b/services/companion/java/com/android/server/companion/virtual/VirtualDeviceManagerService.java
@@ -441,10 +441,8 @@
                                 + " is not the owner of the supplied VirtualDevice");
             }
 
-            int displayId = virtualDeviceImpl.createVirtualDisplay(virtualDisplayConfig, callback,
-                    packageName);
-            mLocalService.onVirtualDisplayCreated(displayId);
-            return displayId;
+            return virtualDeviceImpl.createVirtualDisplay(
+                    virtualDisplayConfig, callback, packageName);
         }
 
         @Override // Binder call
@@ -625,9 +623,6 @@
 
     private final class LocalService extends VirtualDeviceManagerInternal {
         @GuardedBy("mVirtualDeviceManagerLock")
-        private final ArrayList<VirtualDisplayListener>
-                mVirtualDisplayListeners = new ArrayList<>();
-        @GuardedBy("mVirtualDeviceManagerLock")
         private final ArrayList<AppsOnVirtualDeviceListener>
                 mAppsOnVirtualDeviceListeners = new ArrayList<>();
         @GuardedBy("mVirtualDeviceManagerLock")
@@ -665,35 +660,15 @@
         }
 
         @Override
-        public void onVirtualDisplayCreated(int displayId) {
-            final VirtualDisplayListener[] listeners;
-            synchronized (mVirtualDeviceManagerLock) {
-                listeners = mVirtualDisplayListeners.toArray(new VirtualDisplayListener[0]);
-            }
-            mHandler.post(() -> {
-                for (VirtualDisplayListener listener : listeners) {
-                    listener.onVirtualDisplayCreated(displayId);
-                }
-            });
-        }
-
-        @Override
         public void onVirtualDisplayRemoved(IVirtualDevice virtualDevice, int displayId) {
-            final VirtualDisplayListener[] listeners;
             VirtualDeviceImpl virtualDeviceImpl;
             synchronized (mVirtualDeviceManagerLock) {
-                listeners = mVirtualDisplayListeners.toArray(new VirtualDisplayListener[0]);
                 virtualDeviceImpl = mVirtualDevices.get(
                         ((VirtualDeviceImpl) virtualDevice).getDeviceId());
             }
             if (virtualDeviceImpl != null) {
                 virtualDeviceImpl.onVirtualDisplayRemoved(displayId);
             }
-            mHandler.post(() -> {
-                for (VirtualDisplayListener listener : listeners) {
-                    listener.onVirtualDisplayRemoved(displayId);
-                }
-            });
         }
 
         @Override
@@ -799,22 +774,6 @@
         }
 
         @Override
-        public void registerVirtualDisplayListener(
-                @NonNull VirtualDisplayListener listener) {
-            synchronized (mVirtualDeviceManagerLock) {
-                mVirtualDisplayListeners.add(listener);
-            }
-        }
-
-        @Override
-        public void unregisterVirtualDisplayListener(
-                @NonNull VirtualDisplayListener listener) {
-            synchronized (mVirtualDeviceManagerLock) {
-                mVirtualDisplayListeners.remove(listener);
-            }
-        }
-
-        @Override
         public void registerAppsOnVirtualDeviceListener(
                 @NonNull AppsOnVirtualDeviceListener listener) {
             synchronized (mVirtualDeviceManagerLock) {
diff --git a/services/core/java/com/android/server/companion/virtual/VirtualDeviceManagerInternal.java b/services/core/java/com/android/server/companion/virtual/VirtualDeviceManagerInternal.java
index 283353dd..0d7f778 100644
--- a/services/core/java/com/android/server/companion/virtual/VirtualDeviceManagerInternal.java
+++ b/services/core/java/com/android/server/companion/virtual/VirtualDeviceManagerInternal.java
@@ -32,29 +32,12 @@
  */
 public abstract class VirtualDeviceManagerInternal {
 
-    /** Interface to listen to the creation and destruction of virtual displays. */
-    public interface VirtualDisplayListener {
-        /** Notifies that a virtual display was created. */
-        void onVirtualDisplayCreated(int displayId);
-
-        /** Notifies that a virtual display was removed. */
-        void onVirtualDisplayRemoved(int displayId);
-    }
-
     /** Interface to listen to the changes on the list of app UIDs running on any virtual device. */
     public interface AppsOnVirtualDeviceListener {
         /** Notifies that running apps on any virtual device has changed */
         void onAppsOnAnyVirtualDeviceChanged(Set<Integer> allRunningUids);
     }
 
-    /** Register a listener for the creation and destruction of virtual displays. */
-    public abstract void registerVirtualDisplayListener(
-            @NonNull VirtualDisplayListener listener);
-
-    /** Unregister a listener for the creation and destruction of virtual displays. */
-    public abstract void unregisterVirtualDisplayListener(
-            @NonNull VirtualDisplayListener listener);
-
     /** Register a listener for changes of running app UIDs on any virtual device. */
     public abstract void registerAppsOnVirtualDeviceListener(
             @NonNull AppsOnVirtualDeviceListener listener);
@@ -104,13 +87,6 @@
     public abstract @NonNull ArraySet<Integer> getDeviceIdsForUid(int uid);
 
     /**
-     * Notifies that a virtual display is created.
-     *
-     * @param displayId The display id of the created virtual display.
-     */
-    public abstract void onVirtualDisplayCreated(int displayId);
-
-    /**
      * Notifies that a virtual display is removed.
      *
      * @param virtualDevice The virtual device where the virtual display located.
diff --git a/services/core/java/com/android/server/notification/NotificationManagerService.java b/services/core/java/com/android/server/notification/NotificationManagerService.java
index 707e990..2ec3700 100755
--- a/services/core/java/com/android/server/notification/NotificationManagerService.java
+++ b/services/core/java/com/android/server/notification/NotificationManagerService.java
@@ -11449,17 +11449,16 @@
                             }
                         }
                     }
-                }
 
-                // clean up anything in the disallowed pkgs list
-                for (int i = 0; i < pkgList.length; i++) {
-                    String pkg = pkgList[i];
-                    for (int j = mRequestedNotificationListeners.size() - 1; j >= 0; j--) {
-                        NotificationListenerFilter nlf =
-                                mRequestedNotificationListeners.valueAt(j);
-
-                        VersionedPackage ai = new VersionedPackage(pkg, uidList[i]);
-                        nlf.removePackage(ai);
+                    // Clean up removed package from the disallowed packages list
+                    for (int i = 0; i < pkgList.length; i++) {
+                        String pkg = pkgList[i];
+                        for (int j = mRequestedNotificationListeners.size() - 1; j >= 0; j--) {
+                            NotificationListenerFilter nlf =
+                                    mRequestedNotificationListeners.valueAt(j);
+                            VersionedPackage ai = new VersionedPackage(pkg, uidList[i]);
+                            nlf.removePackage(ai);
+                        }
                     }
                 }
             }
diff --git a/services/core/java/com/android/server/powerstats/PowerStatsService.java b/services/core/java/com/android/server/powerstats/PowerStatsService.java
index 77290fd..9f0a975 100644
--- a/services/core/java/com/android/server/powerstats/PowerStatsService.java
+++ b/services/core/java/com/android/server/powerstats/PowerStatsService.java
@@ -603,40 +603,51 @@
     @NonNull
     private String getEnergyConsumerName(EnergyConsumer consumer,
             EnergyConsumer[] energyConsumers) {
-        if (consumer.type != EnergyConsumerType.OTHER) {
-            StringBuilder sb = new StringBuilder();
-            sb.append(energyConsumerTypeToString(consumer.type));
-            boolean hasOrdinal = consumer.ordinal != 0;
-            if (!hasOrdinal) {
-                // See if any other EnergyConsumer of the same type has an ordinal
-                for (EnergyConsumer aConsumer : energyConsumers) {
-                    if (aConsumer.type == consumer.type && aConsumer.ordinal != 0) {
-                        hasOrdinal = true;
-                        break;
-                    }
+        StringBuilder sb = new StringBuilder();
+        switch (consumer.type) {
+            case EnergyConsumerType.BLUETOOTH:
+                sb.append("BLUETOOTH");
+                break;
+            case EnergyConsumerType.CPU_CLUSTER:
+                sb.append("CPU");
+                break;
+            case EnergyConsumerType.DISPLAY:
+                sb.append("DISPLAY");
+                break;
+            case EnergyConsumerType.GNSS:
+                sb.append("GNSS");
+                break;
+            case EnergyConsumerType.MOBILE_RADIO:
+                sb.append("MOBILE_RADIO");
+                break;
+            case EnergyConsumerType.WIFI:
+                sb.append("WIFI");
+                break;
+            case EnergyConsumerType.CAMERA:
+                sb.append("CAMERA");
+                break;
+            default:
+                if (consumer.name != null && !consumer.name.isBlank()) {
+                    sb.append(consumer.name.toUpperCase(Locale.ENGLISH));
+                } else {
+                    sb.append("CONSUMER_").append(consumer.type);
+                }
+                break;
+        }
+        boolean hasOrdinal = consumer.ordinal != 0;
+        if (!hasOrdinal) {
+            // See if any other EnergyConsumer of the same type has an ordinal
+            for (EnergyConsumer aConsumer : energyConsumers) {
+                if (aConsumer.type == consumer.type && aConsumer.ordinal != 0) {
+                    hasOrdinal = true;
+                    break;
                 }
             }
-            if (hasOrdinal) {
-                sb.append('/').append(consumer.ordinal);
-            }
-            return sb.toString();
-        } else {
-            return consumer.name;
         }
-    }
-
-    private static String energyConsumerTypeToString(int type) {
-        switch(type) {
-            case EnergyConsumerType.BLUETOOTH: return "BLUETOOTH";
-            case EnergyConsumerType.CPU_CLUSTER: return "CPU";
-            case EnergyConsumerType.DISPLAY: return "DISPLAY";
-            case EnergyConsumerType.GNSS: return "GNSS";
-            case EnergyConsumerType.MOBILE_RADIO: return "MOBILE_RADIO";
-            case EnergyConsumerType.WIFI: return "WIFI";
-            case EnergyConsumerType.OTHER: return "";
-            default:
-                throw new IllegalStateException("Unrecognized EnergyConsumerType: " + type);
+        if (hasOrdinal) {
+            sb.append('/').append(consumer.ordinal);
         }
+        return sb.toString();
     }
 
     /**
diff --git a/services/core/java/com/android/server/vibrator/Vibration.java b/services/core/java/com/android/server/vibrator/Vibration.java
index fed6e7e..b2e808a 100644
--- a/services/core/java/com/android/server/vibrator/Vibration.java
+++ b/services/core/java/com/android/server/vibrator/Vibration.java
@@ -117,16 +117,16 @@
     static final class CallerInfo {
         public final VibrationAttributes attrs;
         public final int uid;
-        public final int displayId;
+        public final int deviceId;
         public final String opPkg;
         public final String reason;
 
-        CallerInfo(@NonNull VibrationAttributes attrs, int uid, int displayId,
-                String opPkg, String reason) {
+        CallerInfo(@NonNull VibrationAttributes attrs, int uid, int deviceId, String opPkg,
+                String reason) {
             Objects.requireNonNull(attrs);
             this.attrs = attrs;
             this.uid = uid;
-            this.displayId = displayId;
+            this.deviceId = deviceId;
             this.opPkg = opPkg;
             this.reason = reason;
         }
@@ -138,14 +138,14 @@
             CallerInfo that = (CallerInfo) o;
             return Objects.equals(attrs, that.attrs)
                     && uid == that.uid
-                    && displayId == that.displayId
+                    && deviceId == that.deviceId
                     && Objects.equals(opPkg, that.opPkg)
                     && Objects.equals(reason, that.reason);
         }
 
         @Override
         public int hashCode() {
-            return Objects.hash(attrs, uid, displayId, opPkg, reason);
+            return Objects.hash(attrs, uid, deviceId, opPkg, reason);
         }
 
         @Override
@@ -153,7 +153,7 @@
             return "CallerInfo{"
                     + " uid=" + uid
                     + ", opPkg=" + opPkg
-                    + ", displayId=" + displayId
+                    + ", deviceId=" + deviceId
                     + ", attrs=" + attrs
                     + ", reason=" + reason
                     + '}';
@@ -267,8 +267,8 @@
                     mStartTime == 0 ? "" : DEBUG_TIME_FORMAT.format(new Date(mStartTime)),
                     mEndTime == 0 ? "" : DEBUG_TIME_FORMAT.format(new Date(mEndTime)));
             String callerInfoStr = String.format(Locale.ROOT,
-                    " | %s (uid=%d, displayId=%d) | usage: %s (audio=%s) | flags: %s | reason: %s",
-                    mCallerInfo.opPkg, mCallerInfo.uid, mCallerInfo.displayId,
+                    " | %s (uid=%d, deviceId=%d) | usage: %s (audio=%s) | flags: %s | reason: %s",
+                    mCallerInfo.opPkg, mCallerInfo.uid, mCallerInfo.deviceId,
                     mCallerInfo.attrs.usageToString(),
                     AudioAttributes.usageToString(mCallerInfo.attrs.getAudioUsage()),
                     Long.toBinaryString(mCallerInfo.attrs.getFlags()),
diff --git a/services/core/java/com/android/server/vibrator/VibrationSettings.java b/services/core/java/com/android/server/vibrator/VibrationSettings.java
index 7f55836..839c207 100644
--- a/services/core/java/com/android/server/vibrator/VibrationSettings.java
+++ b/services/core/java/com/android/server/vibrator/VibrationSettings.java
@@ -61,7 +61,6 @@
 import android.util.SparseArray;
 import android.util.SparseIntArray;
 import android.util.proto.ProtoOutputStream;
-import android.view.Display;
 
 import com.android.internal.annotations.GuardedBy;
 import com.android.internal.annotations.VisibleForTesting;
@@ -166,7 +165,6 @@
     final MyUidObserver mUidObserver;
     @VisibleForTesting
     final SettingsBroadcastReceiver mSettingChangeReceiver;
-    final VirtualDeviceListener mVirtualDeviceListener;
 
     @GuardedBy("mLock")
     private final List<OnVibratorSettingsChanged> mListeners = new ArrayList<>();
@@ -180,6 +178,8 @@
     @GuardedBy("mLock")
     @Nullable
     private PowerManagerInternal mPowerManagerInternal;
+    @Nullable
+    private VirtualDeviceManagerInternal mVirtualDeviceManagerInternal;
 
     @GuardedBy("mLock")
     private boolean mVibrateInputDevices;
@@ -207,8 +207,6 @@
         mSettingObserver = new SettingsContentObserver(handler);
         mUidObserver = new MyUidObserver();
         mSettingChangeReceiver = new SettingsBroadcastReceiver();
-        mVirtualDeviceListener = new VirtualDeviceListener();
-
         mSystemUiPackage = LocalServices.getService(PackageManagerInternal.class)
                 .getSystemUiServiceComponent().getPackageName();
 
@@ -272,13 +270,6 @@
                     }
                 });
 
-        VirtualDeviceManagerInternal vdm = LocalServices.getService(
-                VirtualDeviceManagerInternal.class);
-        if (vdm != null) {
-            vdm.registerVirtualDisplayListener(mVirtualDeviceListener);
-            vdm.registerAppsOnVirtualDeviceListener(mVirtualDeviceListener);
-        }
-
         registerSettingsChangeReceiver(USER_SWITCHED_INTENT_FILTER);
         registerSettingsChangeReceiver(INTERNAL_RINGER_MODE_CHANGED_INTENT_FILTER);
 
@@ -414,8 +405,14 @@
                     && !BACKGROUND_PROCESS_USAGE_ALLOWLIST.contains(usage)) {
                 return Vibration.Status.IGNORED_BACKGROUND;
             }
-            if (mVirtualDeviceListener.isAppOrDisplayOnAnyVirtualDevice(callerInfo.uid,
-                    callerInfo.displayId)) {
+
+            if (callerInfo.deviceId != Context.DEVICE_ID_DEFAULT
+                    && callerInfo.deviceId != Context.DEVICE_ID_INVALID) {
+                return Vibration.Status.IGNORED_FROM_VIRTUAL_DEVICE;
+            }
+
+            if (callerInfo.deviceId == Context.DEVICE_ID_INVALID
+                    && isAppRunningOnAnyVirtualDevice(callerInfo.uid)) {
                 return Vibration.Status.IGNORED_FROM_VIRTUAL_DEVICE;
             }
 
@@ -794,6 +791,15 @@
         return out;
     }
 
+    private boolean isAppRunningOnAnyVirtualDevice(int uid) {
+        if (mVirtualDeviceManagerInternal == null) {
+            mVirtualDeviceManagerInternal =
+                    LocalServices.getService(VirtualDeviceManagerInternal.class);
+        }
+        return mVirtualDeviceManagerInternal != null
+                && mVirtualDeviceManagerInternal.isAppRunningOnAnyVirtualDevice(uid);
+    }
+
     /** Implementation of {@link ContentObserver} to be registered to a setting {@link Uri}. */
     @VisibleForTesting
     final class SettingsContentObserver extends ContentObserver {
@@ -853,73 +859,4 @@
             }
         }
     }
-
-    /**
-     * Implementation of Virtual Device listeners for the changes of virtual displays and of apps
-     * running on any virtual device.
-     */
-    final class VirtualDeviceListener implements
-            VirtualDeviceManagerInternal.VirtualDisplayListener,
-            VirtualDeviceManagerInternal.AppsOnVirtualDeviceListener {
-        @GuardedBy("mLock")
-        private final Set<Integer> mVirtualDisplays = new HashSet<>();
-        @GuardedBy("mLock")
-        private final Set<Integer> mAppsOnVirtualDevice = new HashSet<>();
-
-
-        @Override
-        public void onVirtualDisplayCreated(int displayId) {
-            synchronized (mLock) {
-                mVirtualDisplays.add(displayId);
-            }
-        }
-
-        @Override
-        public void onVirtualDisplayRemoved(int displayId) {
-            synchronized (mLock) {
-                mVirtualDisplays.remove(displayId);
-            }
-        }
-
-
-        @Override
-        public void onAppsOnAnyVirtualDeviceChanged(Set<Integer> allRunningUids) {
-            synchronized (mLock) {
-                mAppsOnVirtualDevice.clear();
-                mAppsOnVirtualDevice.addAll(allRunningUids);
-            }
-        }
-
-        /**
-         * @param uid:       uid of the calling app.
-         * @param displayId: the id of a Display.
-         * @return Returns true if:
-         * <ul>
-         *   <li> the displayId is valid, and it's owned by a virtual device.</li>
-         *   <li> the displayId is invalid, and the calling app (uid) is running on a virtual
-         *        device.</li>
-         * </ul>
-         */
-        public boolean isAppOrDisplayOnAnyVirtualDevice(int uid, int displayId) {
-            if (displayId == Display.DEFAULT_DISPLAY) {
-                // The default display is the primary physical display on the phone.
-                return false;
-            }
-
-            synchronized (mLock) {
-                if (displayId == Display.INVALID_DISPLAY) {
-                    // There is no Display object associated with the Context of calling
-                    // {@link SystemVibratorManager}, checking the calling UID instead.
-                    return mAppsOnVirtualDevice.contains(uid);
-                } else {
-                    // Other valid display IDs representing valid logical displays will be
-                    // checked
-                    // against the active virtual displays set built with the registered
-                    // {@link VirtualDisplayListener}.
-                    return mVirtualDisplays.contains(displayId);
-                }
-            }
-        }
-
-    }
 }
diff --git a/services/core/java/com/android/server/vibrator/VibratorManagerService.java b/services/core/java/com/android/server/vibrator/VibratorManagerService.java
index ace7777..cf33cc5 100644
--- a/services/core/java/com/android/server/vibrator/VibratorManagerService.java
+++ b/services/core/java/com/android/server/vibrator/VibratorManagerService.java
@@ -63,7 +63,6 @@
 import android.util.Slog;
 import android.util.SparseArray;
 import android.util.proto.ProtoOutputStream;
-import android.view.Display;
 
 import com.android.internal.annotations.GuardedBy;
 import com.android.internal.annotations.VisibleForTesting;
@@ -385,7 +384,7 @@
                     return false;
                 }
                 AlwaysOnVibration alwaysOnVibration = new AlwaysOnVibration(alwaysOnId,
-                        new Vibration.CallerInfo(attrs, uid, Display.DEFAULT_DISPLAY, opPkg,
+                        new Vibration.CallerInfo(attrs, uid, Context.DEVICE_ID_DEFAULT, opPkg,
                                 null), effects);
                 mAlwaysOnEffects.put(alwaysOnId, alwaysOnVibration);
                 updateAlwaysOnLocked(alwaysOnVibration);
@@ -397,16 +396,16 @@
     }
 
     @Override // Binder call
-    public void vibrate(int uid, int displayId, String opPkg, @NonNull CombinedVibration effect,
+    public void vibrate(int uid, int deviceId, String opPkg, @NonNull CombinedVibration effect,
             @Nullable VibrationAttributes attrs, String reason, IBinder token) {
-        vibrateWithPermissionCheck(uid, displayId, opPkg, effect, attrs, reason, token);
+        vibrateWithPermissionCheck(uid, deviceId, opPkg, effect, attrs, reason, token);
     }
 
     @Override // Binder call
     public void performHapticFeedback(
-            int uid, int displayId, String opPkg, int constant, boolean always, String reason,
+            int uid, int deviceId, String opPkg, int constant, boolean always, String reason,
             IBinder token) {
-        performHapticFeedbackInternal(uid, displayId, opPkg, constant, always, reason, token);
+        performHapticFeedbackInternal(uid, deviceId, opPkg, constant, always, reason, token);
     }
 
     /**
@@ -417,7 +416,7 @@
     @VisibleForTesting
     @Nullable
     HalVibration performHapticFeedbackInternal(
-            int uid, int displayId, String opPkg, int constant, boolean always, String reason,
+            int uid, int deviceId, String opPkg, int constant, boolean always, String reason,
             IBinder token) {
         HapticFeedbackVibrationProvider hapticVibrationProvider = getHapticVibrationProvider();
         if (hapticVibrationProvider == null) {
@@ -433,7 +432,7 @@
         VibrationAttributes attrs =
                 hapticVibrationProvider.getVibrationAttributesForHapticFeedback(
                         constant, /* bypassVibrationIntensitySetting= */ always);
-        return vibrateWithoutPermissionCheck(uid, displayId, opPkg, combinedVibration, attrs,
+        return vibrateWithoutPermissionCheck(uid, deviceId, opPkg, combinedVibration, attrs,
                 "performHapticFeedback: " + reason, token);
     }
 
@@ -444,7 +443,7 @@
      */
     @VisibleForTesting
     @Nullable
-    HalVibration vibrateWithPermissionCheck(int uid, int displayId, String opPkg,
+    HalVibration vibrateWithPermissionCheck(int uid, int deviceId, String opPkg,
             @NonNull CombinedVibration effect, @Nullable VibrationAttributes attrs,
             String reason, IBinder token) {
         Trace.traceBegin(Trace.TRACE_TAG_VIBRATOR, "vibrate, reason = " + reason);
@@ -452,24 +451,24 @@
             attrs = fixupVibrationAttributes(attrs, effect);
             mContext.enforceCallingOrSelfPermission(
                     android.Manifest.permission.VIBRATE, "vibrate");
-            return vibrateInternal(uid, displayId, opPkg, effect, attrs, reason, token);
+            return vibrateInternal(uid, deviceId, opPkg, effect, attrs, reason, token);
         } finally {
             Trace.traceEnd(Trace.TRACE_TAG_VIBRATOR);
         }
     }
 
-    HalVibration vibrateWithoutPermissionCheck(int uid, int displayId, String opPkg,
+    HalVibration vibrateWithoutPermissionCheck(int uid, int deviceId, String opPkg,
             @NonNull CombinedVibration effect, @NonNull VibrationAttributes attrs,
             String reason, IBinder token) {
         Trace.traceBegin(Trace.TRACE_TAG_VIBRATOR, "vibrate no perm check, reason = " + reason);
         try {
-            return vibrateInternal(uid, displayId, opPkg, effect, attrs, reason, token);
+            return vibrateInternal(uid, deviceId, opPkg, effect, attrs, reason, token);
         } finally {
             Trace.traceEnd(Trace.TRACE_TAG_VIBRATOR);
         }
     }
 
-    private HalVibration vibrateInternal(int uid, int displayId, String opPkg,
+    private HalVibration vibrateInternal(int uid, int deviceId, String opPkg,
             @NonNull CombinedVibration effect, @NonNull VibrationAttributes attrs,
             String reason, IBinder token) {
         if (token == null) {
@@ -482,7 +481,7 @@
         }
         // Create Vibration.Stats as close to the received request as possible, for tracking.
         HalVibration vib = new HalVibration(token, effect,
-                new Vibration.CallerInfo(attrs, uid, displayId, opPkg, reason));
+                new Vibration.CallerInfo(attrs, uid, deviceId, opPkg, reason));
         fillVibrationFallbacks(vib, effect);
 
         if (attrs.isFlagSet(VibrationAttributes.FLAG_INVALIDATE_SETTINGS_CACHE)) {
@@ -1558,10 +1557,9 @@
         private ExternalVibrationHolder(ExternalVibration externalVibration) {
             super(externalVibration.getToken(), new Vibration.CallerInfo(
                     externalVibration.getVibrationAttributes(), externalVibration.getUid(),
-                    // TODO(b/243604888): propagating displayID from IExternalVibration instead of
-                    //  using INVALID_DISPLAY for all external vibrations.
-                    Display.INVALID_DISPLAY,
-                    externalVibration.getPackage(), null));
+                    // TODO(b/249785241): Find a way to link ExternalVibration to a VirtualDevice
+                    // instead of using DEVICE_ID_INVALID here and relying on the UID checks.
+                    Context.DEVICE_ID_INVALID, externalVibration.getPackage(), null));
             this.externalVibration = externalVibration;
             this.scale = IExternalVibratorService.SCALE_NONE;
             mStatus = Vibration.Status.RUNNING;
@@ -1974,8 +1972,6 @@
             boolean alreadyUnderExternalControl = false;
             boolean waitForCompletion = false;
             synchronized (mLock) {
-                // TODO(b/243604888): propagating displayID from IExternalVibration instead of
-                // using INVALID_DISPLAY for all external vibrations.
                 Vibration.EndInfo vibrationEndInfo = shouldIgnoreVibrationLocked(
                         vibHolder.callerInfo);
 
@@ -2184,7 +2180,7 @@
             IBinder deathBinder = commonOptions.background ? VibratorManagerService.this
                     : mShellCallbacksToken;
             HalVibration vib = vibrateWithPermissionCheck(Binder.getCallingUid(),
-                    Display.DEFAULT_DISPLAY, SHELL_PACKAGE_NAME, combined, attrs,
+                    Context.DEVICE_ID_DEFAULT, SHELL_PACKAGE_NAME, combined, attrs,
                     commonOptions.description, deathBinder);
             maybeWaitOnVibration(vib, commonOptions);
         }
@@ -2241,7 +2237,7 @@
             IBinder deathBinder = commonOptions.background ? VibratorManagerService.this
                     : mShellCallbacksToken;
             HalVibration vib = performHapticFeedbackInternal(Binder.getCallingUid(),
-                    Display.DEFAULT_DISPLAY, SHELL_PACKAGE_NAME, constant,
+                    Context.DEVICE_ID_DEFAULT, SHELL_PACKAGE_NAME, constant,
                     /* always= */ commonOptions.force, /* reason= */ commonOptions.description,
                     deathBinder);
             maybeWaitOnVibration(vib, commonOptions);
diff --git a/services/proguard.flags b/services/proguard.flags
index 261bb7c..407505d 100644
--- a/services/proguard.flags
+++ b/services/proguard.flags
@@ -14,13 +14,20 @@
 }
 
 # APIs referenced by dependent JAR files and modules
--keep @interface android.annotation.SystemApi
+# TODO(b/300514883): Pull @SystemApi keep rules from system-api.pro.
+-keep interface android.annotation.SystemApi
 -keep @android.annotation.SystemApi class * {
   public protected *;
 }
 -keepclasseswithmembers class * {
   @android.annotation.SystemApi *;
 }
+# Also ensure nested classes are kept. This is overly conservative, but handles
+# cases where such classes aren't explicitly marked @SystemApi.
+-if @android.annotation.SystemApi class *
+-keep public class <1>$** {
+  public protected *;
+}
 
 # Derivatives of SystemService and other services created via reflection
 -keep,allowoptimization,allowaccessmodification class * extends com.android.server.SystemService {
diff --git a/services/tests/powerstatstests/src/com/android/server/powerstats/PowerStatsServiceTest.java b/services/tests/powerstatstests/src/com/android/server/powerstats/PowerStatsServiceTest.java
index df46054..1838fe8 100644
--- a/services/tests/powerstatstests/src/com/android/server/powerstats/PowerStatsServiceTest.java
+++ b/services/tests/powerstatstests/src/com/android/server/powerstats/PowerStatsServiceTest.java
@@ -1081,7 +1081,7 @@
         assertThat(result.powerMonitors).isNotNull();
         assertThat(Arrays.stream(result.powerMonitors).map(PowerMonitor::getName).toList())
                 .containsAtLeast(
-                        "energyconsumer0",
+                        "ENERGYCONSUMER0",
                         "BLUETOOTH/1",
                         "[channelname0]:channelsubsystem0",
                         "[channelname1]:channelsubsystem1");
@@ -1131,7 +1131,7 @@
         Map<String, PowerMonitor> map =
                 Arrays.stream(supportedPowerMonitorsResult.powerMonitors)
                         .collect(Collectors.toMap(PowerMonitor::getName, pm -> pm));
-        PowerMonitor consumer1 = map.get("energyconsumer0");
+        PowerMonitor consumer1 = map.get("ENERGYCONSUMER0");
         PowerMonitor consumer2 = map.get("BLUETOOTH/1");
         PowerMonitor measurement1 = map.get("[channelname0]:channelsubsystem0");
         PowerMonitor measurement2 = map.get("[channelname1]:channelsubsystem1");
@@ -1196,6 +1196,6 @@
         supportedPowerMonitorsResult = new GetSupportedPowerMonitorsResult();
         mService.getSupportedPowerMonitorsImpl(supportedPowerMonitorsResult);
         assertThat(Arrays.stream(supportedPowerMonitorsResult.powerMonitors)
-                .map(PowerMonitor::getName).toList()).contains("energyconsumer0");
+                .map(PowerMonitor::getName).toList()).contains("ENERGYCONSUMER0");
     }
 }
diff --git a/services/tests/servicestests/src/com/android/server/companion/virtual/VirtualDeviceManagerServiceTest.java b/services/tests/servicestests/src/com/android/server/companion/virtual/VirtualDeviceManagerServiceTest.java
index 2598a6b..cdff623 100644
--- a/services/tests/servicestests/src/com/android/server/companion/virtual/VirtualDeviceManagerServiceTest.java
+++ b/services/tests/servicestests/src/com/android/server/companion/virtual/VirtualDeviceManagerServiceTest.java
@@ -259,8 +259,6 @@
     @Mock
     private Consumer<ArraySet<Integer>> mRunningAppsChangedCallback;
     @Mock
-    private VirtualDeviceManagerInternal.VirtualDisplayListener mDisplayListener;
-    @Mock
     private VirtualDeviceManagerInternal.AppsOnVirtualDeviceListener mAppsOnVirtualDeviceListener;
     @Mock
     IPowerManager mIPowerManagerMock;
@@ -724,28 +722,6 @@
     }
 
     @Test
-    public void onVirtualDisplayCreatedLocked_listenersNotified() {
-        mLocalService.registerVirtualDisplayListener(mDisplayListener);
-
-        mLocalService.onVirtualDisplayCreated(DISPLAY_ID_1);
-        TestableLooper.get(this).processAllMessages();
-
-        verify(mDisplayListener).onVirtualDisplayCreated(DISPLAY_ID_1);
-    }
-
-    @Test
-    public void onVirtualDisplayRemovedLocked_listenersNotified() {
-        mLocalService.registerVirtualDisplayListener(mDisplayListener);
-
-        addVirtualDisplay(mDeviceImpl, DISPLAY_ID_1);
-
-        mLocalService.onVirtualDisplayRemoved(mDeviceImpl, DISPLAY_ID_1);
-        TestableLooper.get(this).processAllMessages();
-
-        verify(mDisplayListener).onVirtualDisplayRemoved(DISPLAY_ID_1);
-    }
-
-    @Test
     public void onAppsOnVirtualDeviceChanged_singleVirtualDevice_listenersNotified() {
         ArraySet<Integer> uids = new ArraySet<>(Arrays.asList(UID_1, UID_2));
         mLocalService.registerAppsOnVirtualDeviceListener(mAppsOnVirtualDeviceListener);
diff --git a/services/tests/uiservicestests/src/com/android/server/notification/NotificationListenersTest.java b/services/tests/uiservicestests/src/com/android/server/notification/NotificationListenersTest.java
index 4406d83..ea11395 100644
--- a/services/tests/uiservicestests/src/com/android/server/notification/NotificationListenersTest.java
+++ b/services/tests/uiservicestests/src/com/android/server/notification/NotificationListenersTest.java
@@ -426,7 +426,7 @@
     }
 
     @Test
-    public void testOnPackageChanged_removingDisallowedPackage() {
+    public void testOnPackageChanged_removingPackage_removeFromDisallowed() {
         NotificationListenerFilter nlf = new NotificationListenerFilter(7, new ArraySet<>());
         VersionedPackage a1 = new VersionedPackage("pkg1", 243);
         NotificationListenerFilter nlf2 =
@@ -440,6 +440,25 @@
 
         assertThat(mListeners.getNotificationListenerFilter(Pair.create(mCn1, 0))
                 .getDisallowedPackages()).isEmpty();
+        assertThat(mListeners.getNotificationListenerFilter(Pair.create(mCn2, 0))
+                .getDisallowedPackages()).isEmpty();
+    }
+
+    @Test
+    public void testOnPackageChanged_notRemovingPackage_staysInDisallowed() {
+        NotificationListenerFilter nlf = new NotificationListenerFilter(7, new ArraySet<>());
+        VersionedPackage a1 = new VersionedPackage("pkg1", 243);
+        NotificationListenerFilter nlf2 =
+                new NotificationListenerFilter(4, new ArraySet<>(new VersionedPackage[] {a1}));
+        mListeners.setNotificationListenerFilter(Pair.create(mCn1, 0), nlf);
+        mListeners.setNotificationListenerFilter(Pair.create(mCn2, 0), nlf2);
+
+        String[] pkgs = new String[] {"pkg1"};
+        int[] uids = new int[] {243};
+        mListeners.onPackagesChanged(false, pkgs, uids);
+
+        assertThat(mListeners.getNotificationListenerFilter(Pair.create(mCn2, 0))
+                .getDisallowedPackages()).contains(a1);
     }
 
     @Test
diff --git a/services/tests/vibrator/src/com/android/server/vibrator/VibrationSettingsTest.java b/services/tests/vibrator/src/com/android/server/vibrator/VibrationSettingsTest.java
index 7a2bb5a..f080341 100644
--- a/services/tests/vibrator/src/com/android/server/vibrator/VibrationSettingsTest.java
+++ b/services/tests/vibrator/src/com/android/server/vibrator/VibrationSettingsTest.java
@@ -75,8 +75,6 @@
 import android.platform.test.flag.junit.CheckFlagsRule;
 import android.platform.test.flag.junit.DeviceFlagsValueProvider;
 import android.provider.Settings;
-import android.util.ArraySet;
-import android.view.Display;
 
 import androidx.test.InstrumentationRegistry;
 
@@ -103,7 +101,7 @@
     public final CheckFlagsRule mCheckFlagsRule = DeviceFlagsValueProvider.createCheckFlagsRule();
 
     private static final int UID = 1;
-    private static final int VIRTUAL_DISPLAY_ID = 1;
+    private static final int VIRTUAL_DEVICE_ID = 1;
     private static final String SYSUI_PACKAGE_NAME = "sysui";
     private static final PowerSaveState NORMAL_POWER_STATE = new PowerSaveState.Builder().build();
     private static final PowerSaveState LOW_POWER_STATE = new PowerSaveState.Builder()
@@ -137,9 +135,6 @@
     private VibrationSettings mVibrationSettings;
     private PowerManagerInternal.LowPowerModeListener mRegisteredPowerModeListener;
     private BroadcastReceiver mRegisteredBatteryBroadcastReceiver;
-    private VirtualDeviceManagerInternal.VirtualDisplayListener mRegisteredVirtualDisplayListener;
-    private VirtualDeviceManagerInternal.AppsOnVirtualDeviceListener
-            mRegisteredAppsOnVirtualDeviceListener;
 
     @Before
     public void setUp() throws Exception {
@@ -155,14 +150,6 @@
         }).when(mPowerManagerInternalMock).registerLowPowerModeObserver(any());
         when(mPackageManagerInternalMock.getSystemUiServiceComponent())
                 .thenReturn(new ComponentName(SYSUI_PACKAGE_NAME, ""));
-        doAnswer(invocation -> {
-            mRegisteredVirtualDisplayListener = invocation.getArgument(0);
-            return null;
-        }).when(mVirtualDeviceManagerInternalMock).registerVirtualDisplayListener(any());
-        doAnswer(invocation -> {
-            mRegisteredAppsOnVirtualDeviceListener = invocation.getArgument(0);
-            return null;
-        }).when(mVirtualDeviceManagerInternalMock).registerAppsOnVirtualDeviceListener(any());
 
         removeServicesForTest();
         addServicesForTest();
@@ -654,62 +641,20 @@
     }
 
     @Test
-    public void shouldIgnoreVibrationFromVirtualDisplays_displayNonVirtual_neverIgnored() {
-        // Vibrations from the primary display is never ignored regardless of the creation and
-        // removal of virtual displays and of the changes of apps running on virtual displays.
-        mRegisteredVirtualDisplayListener.onVirtualDisplayCreated(VIRTUAL_DISPLAY_ID);
-        mRegisteredAppsOnVirtualDeviceListener.onAppsOnAnyVirtualDeviceChanged(
-                new ArraySet<>(Arrays.asList(UID)));
+    public void shouldIgnoreVibrationFromVirtualDevices_defaultDevice_neverIgnored() {
+        // Vibrations from the primary device is never ignored.
         for (int usage : ALL_USAGES) {
-            assertVibrationNotIgnoredForUsageAndDisplay(usage, Display.DEFAULT_DISPLAY);
-        }
-
-        mRegisteredVirtualDisplayListener.onVirtualDisplayRemoved(VIRTUAL_DISPLAY_ID);
-        for (int usage : ALL_USAGES) {
-            assertVibrationNotIgnoredForUsageAndDisplay(usage, Display.DEFAULT_DISPLAY);
-        }
-
-        mRegisteredAppsOnVirtualDeviceListener.onAppsOnAnyVirtualDeviceChanged(new ArraySet<>());
-        for (int usage : ALL_USAGES) {
-            assertVibrationNotIgnoredForUsageAndDisplay(usage, Display.DEFAULT_DISPLAY);
+            assertVibrationNotIgnoredForUsageAndDevice(usage, Context.DEVICE_ID_DEFAULT);
         }
     }
 
     @Test
-    public void shouldIgnoreVibrationFromVirtualDisplays_displayVirtual() {
-        // Ignore the vibration when the coming display id represents a virtual display.
-        mRegisteredVirtualDisplayListener.onVirtualDisplayCreated(VIRTUAL_DISPLAY_ID);
-
+    public void shouldIgnoreVibrationFromVirtualDevices_virtualDevice_alwaysIgnored() {
+        // Ignore the vibration when the coming device id represents a virtual device.
         for (int usage : ALL_USAGES) {
-            assertVibrationIgnoredForUsageAndDisplay(usage, VIRTUAL_DISPLAY_ID,
+            assertVibrationIgnoredForUsageAndDevice(usage, VIRTUAL_DEVICE_ID,
                     Vibration.Status.IGNORED_FROM_VIRTUAL_DEVICE);
         }
-
-        // Stop ignoring when the virtual display is removed.
-        mRegisteredVirtualDisplayListener.onVirtualDisplayRemoved(VIRTUAL_DISPLAY_ID);
-        for (int usage : ALL_USAGES) {
-            assertVibrationNotIgnoredForUsageAndDisplay(usage, VIRTUAL_DISPLAY_ID);
-        }
-    }
-
-
-    @Test
-    public void shouldIgnoreVibrationFromVirtualDisplays_appsOnVirtualDisplay() {
-        // Ignore when the passed-in display id is invalid and the calling uid is on a virtual
-        // display.
-        mRegisteredAppsOnVirtualDeviceListener.onAppsOnAnyVirtualDeviceChanged(
-                new ArraySet<>(Arrays.asList(UID)));
-        for (int usage : ALL_USAGES) {
-            assertVibrationIgnoredForUsageAndDisplay(usage, Display.INVALID_DISPLAY,
-                    Vibration.Status.IGNORED_FROM_VIRTUAL_DEVICE);
-        }
-
-        // Stop ignoring when the app is no longer on virtual display.
-        mRegisteredAppsOnVirtualDeviceListener.onAppsOnAnyVirtualDeviceChanged(new ArraySet<>());
-        for (int usage : ALL_USAGES) {
-            assertVibrationNotIgnoredForUsageAndDisplay(usage, Display.INVALID_DISPLAY);
-        }
-
     }
 
     @Test
@@ -932,13 +877,13 @@
 
     private void assertVibrationIgnoredForUsage(@VibrationAttributes.Usage int usage,
             Vibration.Status expectedStatus) {
-        assertVibrationIgnoredForUsageAndDisplay(usage, Display.DEFAULT_DISPLAY, expectedStatus);
+        assertVibrationIgnoredForUsageAndDevice(usage, Context.DEVICE_ID_DEFAULT, expectedStatus);
     }
 
-    private void assertVibrationIgnoredForUsageAndDisplay(@VibrationAttributes.Usage int usage,
-            int displayId, Vibration.Status expectedStatus) {
+    private void assertVibrationIgnoredForUsageAndDevice(@VibrationAttributes.Usage int usage,
+            int deviceId, Vibration.Status expectedStatus) {
         Vibration.CallerInfo callerInfo = new Vibration.CallerInfo(
-                VibrationAttributes.createForUsage(usage), UID, displayId, null, null);
+                VibrationAttributes.createForUsage(usage), UID, deviceId, null, null);
         assertEquals(errorMessageForUsage(usage), expectedStatus,
                 mVibrationSettings.shouldIgnoreVibration(callerInfo));
     }
@@ -946,7 +891,7 @@
     private void assertVibrationIgnoredForAttributes(VibrationAttributes attrs,
             Vibration.Status expectedStatus) {
         Vibration.CallerInfo callerInfo = new Vibration.CallerInfo(attrs, UID,
-                Display.DEFAULT_DISPLAY, null, null);
+                Context.DEVICE_ID_DEFAULT, null, null);
         assertEquals(errorMessageForAttributes(attrs), expectedStatus,
                 mVibrationSettings.shouldIgnoreVibration(callerInfo));
     }
@@ -957,27 +902,27 @@
 
     private void assertVibrationNotIgnoredForUsageAndFlags(@VibrationAttributes.Usage int usage,
             @VibrationAttributes.Flag int flags) {
-        assertVibrationNotIgnoredForUsageAndFlagsAndDisplay(usage, Display.DEFAULT_DISPLAY, flags);
+        assertVibrationNotIgnoredForUsageAndFlagsAndDevice(usage, Context.DEVICE_ID_DEFAULT, flags);
     }
 
-    private void assertVibrationNotIgnoredForUsageAndDisplay(@VibrationAttributes.Usage int usage,
-            int displayId) {
-        assertVibrationNotIgnoredForUsageAndFlagsAndDisplay(usage, displayId, /* flags= */ 0);
+    private void assertVibrationNotIgnoredForUsageAndDevice(@VibrationAttributes.Usage int usage,
+            int deviceId) {
+        assertVibrationNotIgnoredForUsageAndFlagsAndDevice(usage, deviceId, /* flags= */ 0);
     }
 
-    private void assertVibrationNotIgnoredForUsageAndFlagsAndDisplay(
-            @VibrationAttributes.Usage int usage, int displayId,
+    private void assertVibrationNotIgnoredForUsageAndFlagsAndDevice(
+            @VibrationAttributes.Usage int usage, int deviceId,
             @VibrationAttributes.Flag int flags) {
         Vibration.CallerInfo callerInfo = new Vibration.CallerInfo(
                 new VibrationAttributes.Builder().setUsage(usage).setFlags(flags).build(), UID,
-                displayId, null, null);
+                deviceId, null, null);
         assertNull(errorMessageForUsage(usage),
                 mVibrationSettings.shouldIgnoreVibration(callerInfo));
     }
 
     private void assertVibrationNotIgnoredForAttributes(VibrationAttributes attrs) {
         Vibration.CallerInfo callerInfo = new Vibration.CallerInfo(attrs, UID,
-                Display.DEFAULT_DISPLAY, null, null);
+                Context.DEVICE_ID_DEFAULT, null, null);
         assertNull(errorMessageForAttributes(attrs),
                 mVibrationSettings.shouldIgnoreVibration(callerInfo));
     }
@@ -1032,7 +977,7 @@
     private Vibration.CallerInfo createCallerInfo(int uid, String opPkg,
             @VibrationAttributes.Usage int usage) {
         VibrationAttributes attrs = VibrationAttributes.createForUsage(usage);
-        return new Vibration.CallerInfo(attrs, uid, VIRTUAL_DISPLAY_ID, opPkg, null);
+        return new Vibration.CallerInfo(attrs, uid, VIRTUAL_DEVICE_ID, opPkg, null);
     }
 
     private void setBatteryReceiverRegistrationResult(Intent result) {
diff --git a/services/tests/vibrator/src/com/android/server/vibrator/VibrationThreadTest.java b/services/tests/vibrator/src/com/android/server/vibrator/VibrationThreadTest.java
index 085241f..b0aef47 100644
--- a/services/tests/vibrator/src/com/android/server/vibrator/VibrationThreadTest.java
+++ b/services/tests/vibrator/src/com/android/server/vibrator/VibrationThreadTest.java
@@ -88,7 +88,7 @@
 
     private static final int TEST_TIMEOUT_MILLIS = 900;
     private static final int UID = Process.ROOT_UID;
-    private static final int DISPLAY_ID = 10;
+    private static final int DEVICE_ID = 10;
     private static final int VIBRATOR_ID = 1;
     private static final String PACKAGE_NAME = "package";
     private static final VibrationAttributes ATTRS = new VibrationAttributes.Builder().build();
@@ -250,7 +250,7 @@
         Vibration.EndInfo cancelVibrationInfo = new Vibration.EndInfo(
                 Vibration.Status.CANCELLED_SUPERSEDED, new Vibration.CallerInfo(
                 VibrationAttributes.createForUsage(VibrationAttributes.USAGE_ALARM), /* uid= */
-                1, /* displayId= */ -1, /* opPkg= */ null, /* reason= */ null));
+                1, /* deviceId= */ -1, /* opPkg= */ null, /* reason= */ null));
         mVibrationConductor.notifyCancelled(
                 cancelVibrationInfo,
                 /* immediate= */ false);
@@ -1641,7 +1641,7 @@
 
     private HalVibration createVibration(CombinedVibration effect) {
         return new HalVibration(mVibrationToken, effect,
-                new Vibration.CallerInfo(ATTRS, UID, DISPLAY_ID, PACKAGE_NAME, "reason"));
+                new Vibration.CallerInfo(ATTRS, UID, DEVICE_ID, PACKAGE_NAME, "reason"));
     }
 
     private SparseArray<VibratorController> createVibratorControllers() {
diff --git a/services/tests/vibrator/src/com/android/server/vibrator/VibratorManagerServiceTest.java b/services/tests/vibrator/src/com/android/server/vibrator/VibratorManagerServiceTest.java
index 3dfaed6..3fce9e7 100644
--- a/services/tests/vibrator/src/com/android/server/vibrator/VibratorManagerServiceTest.java
+++ b/services/tests/vibrator/src/com/android/server/vibrator/VibratorManagerServiceTest.java
@@ -84,10 +84,8 @@
 import android.os.vibrator.VibrationEffectSegment;
 import android.platform.test.flag.junit.SetFlagsRule;
 import android.provider.Settings;
-import android.util.ArraySet;
 import android.util.SparseArray;
 import android.util.SparseBooleanArray;
-import android.view.Display;
 import android.view.HapticFeedbackConstants;
 import android.view.InputDevice;
 import android.view.flags.Flags;
@@ -129,7 +127,7 @@
     // be cancelled in the body of the individual test.
     private static final int CLEANUP_TIMEOUT_MILLIS = 100;
     private static final int UID = Process.ROOT_UID;
-    private static final int VIRTUAL_DISPLAY_ID = 1;
+    private static final int VIRTUAL_DEVICE_ID = 1;
     private static final String PACKAGE_NAME = "package";
     private static final PowerSaveState NORMAL_POWER_STATE = new PowerSaveState.Builder().build();
     private static final PowerSaveState LOW_POWER_STATE = new PowerSaveState.Builder()
@@ -190,9 +188,6 @@
     private PowerManagerInternal.LowPowerModeListener mRegisteredPowerModeListener;
     private VibratorManagerService.ExternalVibratorService mExternalVibratorService;
     private VibrationConfig mVibrationConfig;
-    private VirtualDeviceManagerInternal.VirtualDisplayListener mRegisteredVirtualDisplayListener;
-    private VirtualDeviceManagerInternal.AppsOnVirtualDeviceListener
-            mRegisteredAppsOnVirtualDeviceListener;
     private InputManagerGlobal.TestSession mInputManagerGlobalSession;
     private InputManager mInputManager;
 
@@ -223,14 +218,6 @@
             mRegisteredPowerModeListener = invocation.getArgument(0);
             return null;
         }).when(mPowerManagerInternalMock).registerLowPowerModeObserver(any());
-        doAnswer(invocation -> {
-            mRegisteredVirtualDisplayListener = invocation.getArgument(0);
-            return null;
-        }).when(mVirtualDeviceManagerInternalMock).registerVirtualDisplayListener(any());
-        doAnswer(invocation -> {
-            mRegisteredAppsOnVirtualDeviceListener = invocation.getArgument(0);
-            return null;
-        }).when(mVirtualDeviceManagerInternalMock).registerAppsOnVirtualDeviceListener(any());
 
         setUserSetting(Settings.System.VIBRATE_WHEN_RINGING, 1);
         setUserSetting(Settings.System.HAPTIC_FEEDBACK_ENABLED, 1);
@@ -273,6 +260,7 @@
 
         LocalServices.removeServiceForTest(PackageManagerInternal.class);
         LocalServices.removeServiceForTest(PowerManagerInternal.class);
+        LocalServices.removeServiceForTest(VirtualDeviceManagerInternal.class);
         // Ignore potential exceptions about the looper having never dispatched any messages.
         mTestLooper.stopAutoDispatchAndIgnoreExceptions();
         if (mInputManagerGlobalSession != null) {
@@ -1510,65 +1498,33 @@
     }
 
     @Test
-    public void vibrate_withVirtualDisplayChange_ignoreVibrationFromVirtualDisplay()
-            throws Exception {
+    public void vibrate_ignoreVibrationFromVirtualDevice() throws Exception {
         mockVibrators(1);
         VibratorManagerService service = createSystemReadyService();
-        mRegisteredVirtualDisplayListener.onVirtualDisplayCreated(VIRTUAL_DISPLAY_ID);
 
-        vibrateWithDisplay(service,
-                VIRTUAL_DISPLAY_ID,
+        vibrateWithDevice(service,
+                VIRTUAL_DEVICE_ID,
                 CombinedVibration.startParallel()
                         .addVibrator(1, VibrationEffect.createOneShot(1000, 100))
                         .combine(),
                 HAPTIC_FEEDBACK_ATTRS);
 
-        // Haptic feedback ignored when it's from a virtual display.
+        // Haptic feedback ignored when it's from a virtual device.
         assertFalse(waitUntil(s -> s.isVibrating(1), service, /* timeout= */ 50));
 
-        mRegisteredVirtualDisplayListener.onVirtualDisplayRemoved(VIRTUAL_DISPLAY_ID);
-        vibrateWithDisplay(service,
-                VIRTUAL_DISPLAY_ID,
+        vibrateWithDevice(service,
+                Context.DEVICE_ID_DEFAULT,
                 CombinedVibration.startParallel()
                         .addVibrator(1, VibrationEffect.createOneShot(1000, 100))
                         .combine(),
                 HAPTIC_FEEDBACK_ATTRS);
-        // Haptic feedback played normally when the virtual display is removed.
+        // Haptic feedback played normally when it's from the default device.
         assertTrue(waitUntil(s -> s.isVibrating(1), service, TEST_TIMEOUT_MILLIS));
 
         cancelVibrate(service);  // Clean up long-ish effect.
     }
 
     @Test
-    public void vibrate_withAppsOnVirtualDisplayChange_ignoreVibrationFromVirtualDisplay()
-            throws Exception {
-        mockVibrators(1);
-        VibratorManagerService service = createSystemReadyService();
-        mRegisteredAppsOnVirtualDeviceListener.onAppsOnAnyVirtualDeviceChanged(
-                new ArraySet<>(Arrays.asList(UID)));
-        vibrateWithDisplay(service,
-                Display.INVALID_DISPLAY,
-                CombinedVibration.startParallel()
-                        .addVibrator(1, VibrationEffect.createOneShot(1000, 100))
-                        .combine(),
-                HAPTIC_FEEDBACK_ATTRS);
-
-        // Haptic feedback ignored when it's from an app running virtual display.
-        assertFalse(waitUntil(s -> s.isVibrating(1), service, /* timeout= */ 50));
-
-        mRegisteredAppsOnVirtualDeviceListener.onAppsOnAnyVirtualDeviceChanged(new ArraySet<>());
-        vibrateWithDisplay(service,
-                Display.INVALID_DISPLAY,
-                CombinedVibration.startParallel()
-                        .addVibrator(1, VibrationEffect.createOneShot(1000, 100))
-                        .combine(),
-                HAPTIC_FEEDBACK_ATTRS);
-        // Haptic feedback played normally when the same app no long runs on a virtual display.
-        assertTrue(waitUntil(s -> s.isVibrating(1), service, TEST_TIMEOUT_MILLIS));
-        cancelVibrate(service);  // Clean up long-ish effect.
-    }
-
-    @Test
     public void vibrate_prebakedAndComposedVibrationsWithFallbacks_playsFallbackOnlyForPredefined()
             throws Exception {
         mockVibrators(1);
@@ -1685,7 +1641,7 @@
     }
 
     @Test
-    public void onExternalVibration_ignoreVibrationFromVirtualDevices() throws Exception {
+    public void onExternalVibration_ignoreVibrationFromVirtualDevices() {
         mockVibrators(1);
         mVibratorProviders.get(1).setCapabilities(IVibrator.CAP_EXTERNAL_CONTROL);
         createSystemReadyService();
@@ -1697,8 +1653,8 @@
         int scale = mExternalVibratorService.onExternalVibrationStart(externalVibration);
         assertNotEquals(IExternalVibratorService.SCALE_MUTE, scale);
 
-        mRegisteredAppsOnVirtualDeviceListener.onAppsOnAnyVirtualDeviceChanged(
-                new ArraySet<>(Arrays.asList(UID)));
+        when(mVirtualDeviceManagerInternalMock.isAppRunningOnAnyVirtualDevice(UID))
+                .thenReturn(true);
         scale = mExternalVibratorService.onExternalVibrationStart(externalVibration);
         assertEquals(IExternalVibratorService.SCALE_MUTE, scale);
     }
@@ -2396,7 +2352,7 @@
     private HalVibration performHapticFeedbackAndWaitUntilFinished(VibratorManagerService service,
                 int constant, boolean always) throws InterruptedException {
         HalVibration vib =
-                service.performHapticFeedbackInternal(UID, Display.DEFAULT_DISPLAY, PACKAGE_NAME,
+                service.performHapticFeedbackInternal(UID, Context.DEVICE_ID_DEFAULT, PACKAGE_NAME,
                         constant, always, "some reason", service);
         if (vib != null) {
             vib.waitForEnd();
@@ -2414,7 +2370,7 @@
     private HalVibration vibrateAndWaitUntilFinished(VibratorManagerService service,
             CombinedVibration effect, VibrationAttributes attrs) throws InterruptedException {
         HalVibration vib =
-                service.vibrateWithPermissionCheck(UID, Display.DEFAULT_DISPLAY, PACKAGE_NAME,
+                service.vibrateWithPermissionCheck(UID, Context.DEVICE_ID_DEFAULT, PACKAGE_NAME,
                         effect, attrs, "some reason", service);
         if (vib != null) {
             vib.waitForEnd();
@@ -2430,12 +2386,12 @@
 
     private void vibrate(VibratorManagerService service, CombinedVibration effect,
             VibrationAttributes attrs) {
-        vibrateWithDisplay(service, Display.DEFAULT_DISPLAY, effect, attrs);
+        vibrateWithDevice(service, Context.DEVICE_ID_DEFAULT, effect, attrs);
     }
 
-    private void vibrateWithDisplay(VibratorManagerService service, int displayId,
+    private void vibrateWithDevice(VibratorManagerService service, int deviceId,
             CombinedVibration effect, VibrationAttributes attrs) {
-        service.vibrate(UID, displayId, PACKAGE_NAME, effect, attrs, "some reason", service);
+        service.vibrate(UID, deviceId, PACKAGE_NAME, effect, attrs, "some reason", service);
     }
 
     private boolean waitUntil(Predicate<VibratorManagerService> predicate,
diff --git a/services/usage/java/com/android/server/usage/UsageStatsService.java b/services/usage/java/com/android/server/usage/UsageStatsService.java
index f64ab22..63de41f 100644
--- a/services/usage/java/com/android/server/usage/UsageStatsService.java
+++ b/services/usage/java/com/android/server/usage/UsageStatsService.java
@@ -206,6 +206,7 @@
     static final int MSG_NOTIFY_ESTIMATED_LAUNCH_TIMES_CHANGED = 9;
     static final int MSG_UID_REMOVED = 10;
     static final int MSG_USER_STARTED = 11;
+    static final int MSG_NOTIFY_USAGE_EVENT_LISTENER = 12;
 
     private final Object mLock = new Object();
     private Handler mHandler;
@@ -315,6 +316,16 @@
                 Trace.traceEnd(Trace.TRACE_TAG_SYSTEM_SERVER);
                 return true;
             }
+            case MSG_NOTIFY_USAGE_EVENT_LISTENER: {
+                final int userId = msg.arg1;
+                final Event event = (Event) msg.obj;
+                synchronized (mUsageEventListeners) {
+                    final int size = mUsageEventListeners.size();
+                    for (int i = 0; i < size; ++i) {
+                        mUsageEventListeners.valueAt(i).onUsageEvent(userId, event);
+                    }
+                }
+            }
         }
         return false;
     };
@@ -532,9 +543,6 @@
             }
             reportEvent(unlockEvent, userId);
 
-            mIoHandler.obtainMessage(MSG_HANDLE_LAUNCH_TIME_ON_USER_UNLOCK,
-                    userId, 0).sendToTarget();
-
             // Remove all the stats stored in system DE.
             deleteRecursively(new File(Environment.getDataSystemDeDirectory(userId), "usagestats"));
 
@@ -546,6 +554,8 @@
                 userService.persistActiveStats();
             }
         }
+
+        mIoHandler.obtainMessage(MSG_HANDLE_LAUNCH_TIME_ON_USER_UNLOCK, userId, 0).sendToTarget();
     }
 
     /**
@@ -1240,12 +1250,7 @@
             service.reportEvent(event);
         }
 
-        synchronized (mUsageEventListeners) {
-            final int size = mUsageEventListeners.size();
-            for (int i = 0; i < size; ++i) {
-                mUsageEventListeners.valueAt(i).onUsageEvent(userId, event);
-            }
-        }
+        mIoHandler.obtainMessage(MSG_NOTIFY_USAGE_EVENT_LISTENER, userId, 0, event).sendToTarget();
     }
 
     private String getUsageSourcePackage(Event event) {
diff --git a/services/voiceinteraction/java/com/android/server/voiceinteraction/DetectorSession.java b/services/voiceinteraction/java/com/android/server/voiceinteraction/DetectorSession.java
index bb6cf52..fb18375 100644
--- a/services/voiceinteraction/java/com/android/server/voiceinteraction/DetectorSession.java
+++ b/services/voiceinteraction/java/com/android/server/voiceinteraction/DetectorSession.java
@@ -52,9 +52,12 @@
 import static com.android.internal.util.FrameworkStatsLog.HOTWORD_DETECTOR_KEYPHRASE_TRIGGERED__RESULT__DETECT_UNEXPECTED_CALLBACK;
 import static com.android.internal.util.FrameworkStatsLog.HOTWORD_DETECTOR_KEYPHRASE_TRIGGERED__RESULT__REJECT_UNEXPECTED_CALLBACK;
 import static com.android.internal.util.FrameworkStatsLog.HOTWORD_DETECTOR_KEYPHRASE_TRIGGERED__RESULT__TRAINING_DATA;
-import static com.android.internal.util.FrameworkStatsLog.HOTWORD_DETECTOR_KEYPHRASE_TRIGGERED__RESULT__TRAINING_DATA_SECURITY_EXCEPTION;
 import static com.android.internal.util.FrameworkStatsLog.HOTWORD_DETECTOR_KEYPHRASE_TRIGGERED__RESULT__TRAINING_DATA_EGRESS_LIMIT_REACHED;
 import static com.android.internal.util.FrameworkStatsLog.HOTWORD_DETECTOR_KEYPHRASE_TRIGGERED__RESULT__TRAINING_DATA_REMOTE_EXCEPTION;
+import static com.android.internal.util.FrameworkStatsLog.HOTWORD_DETECTOR_KEYPHRASE_TRIGGERED__RESULT__TRAINING_DATA_SECURITY_EXCEPTION;
+import static com.android.internal.util.FrameworkStatsLog.HOTWORD_EVENT_EGRESS_SIZE__EVENT_TYPE__HOTWORD_DETECTION;
+import static com.android.internal.util.FrameworkStatsLog.HOTWORD_EVENT_EGRESS_SIZE__EVENT_TYPE__HOTWORD_REJECTION;
+import static com.android.internal.util.FrameworkStatsLog.HOTWORD_EVENT_EGRESS_SIZE__EVENT_TYPE__HOTWORD_TRAINING_DATA;
 import static com.android.server.voiceinteraction.HotwordDetectionConnection.ENFORCE_HOTWORD_PHRASE_ID;
 
 import android.annotation.NonNull;
@@ -73,7 +76,9 @@
 import android.os.Bundle;
 import android.os.IBinder;
 import android.os.IRemoteCallback;
+import android.os.Parcel;
 import android.os.ParcelFileDescriptor;
+import android.os.Parcelable;
 import android.os.PersistableBundle;
 import android.os.RemoteException;
 import android.os.SharedMemory;
@@ -94,6 +99,7 @@
 import com.android.internal.annotations.GuardedBy;
 import com.android.internal.app.IHotwordRecognitionStatusCallback;
 import com.android.internal.infra.AndroidFuture;
+import com.android.internal.os.BackgroundThread;
 import com.android.server.LocalServices;
 import com.android.server.policy.AppOpsPolicy;
 import com.android.server.voiceinteraction.VoiceInteractionManagerServiceImpl.DetectorRemoteExceptionListener;
@@ -180,6 +186,13 @@
     private static final int METRICS_CALLBACK_ON_STATUS_REPORTED_EXCEPTION =
             HOTWORD_DETECTOR_EVENTS__EVENT__CALLBACK_ON_STATUS_REPORTED_EXCEPTION;
 
+    private static final int HOTWORD_EVENT_TYPE_DETECTION =
+            HOTWORD_EVENT_EGRESS_SIZE__EVENT_TYPE__HOTWORD_DETECTION;
+    private static final int HOTWORD_EVENT_TYPE_REJECTION =
+            HOTWORD_EVENT_EGRESS_SIZE__EVENT_TYPE__HOTWORD_REJECTION;
+    private static final int HOTWORD_EVENT_TYPE_TRAINING_DATA =
+            HOTWORD_EVENT_EGRESS_SIZE__EVENT_TYPE__HOTWORD_TRAINING_DATA;
+
     private final Executor mAudioCopyExecutor = Executors.newCachedThreadPool();
     // TODO: This may need to be a Handler(looper)
     final ScheduledExecutorService mScheduledExecutorService;
@@ -516,6 +529,7 @@
                                         if (result != null) {
                                             Slog.i(TAG, "Egressed 'hotword rejected result' "
                                                     + "from hotword trusted process");
+                                            logEgressSizeStats(result);
                                             if (mDebugHotwordLogging) {
                                                 Slog.i(TAG, "Egressed detected result: " + result);
                                             }
@@ -608,6 +622,7 @@
                                         Slog.i(TAG, "Egressed "
                                                 + HotwordDetectedResult.getUsageSize(newResult)
                                                 + " bits from hotword trusted process");
+                                        logEgressSizeStats(newResult);
                                         if (mDebugHotwordLogging) {
                                             Slog.i(TAG,
                                                     "Egressed detected result: " + newResult);
@@ -624,6 +639,32 @@
                 mVoiceInteractionServiceUid);
     }
 
+    void logEgressSizeStats(HotwordTrainingData data) {
+        logEgressSizeStats(data, HOTWORD_EVENT_TYPE_TRAINING_DATA);
+    }
+
+    void logEgressSizeStats(HotwordDetectedResult data) {
+        logEgressSizeStats(data, HOTWORD_EVENT_TYPE_DETECTION);
+
+    }
+
+    void logEgressSizeStats(HotwordRejectedResult data) {
+        logEgressSizeStats(data, HOTWORD_EVENT_TYPE_REJECTION);
+    }
+
+    /** Logs event size stats for events egressed from trusted hotword detection service. */
+    private void logEgressSizeStats(Parcelable data, int eventType) {
+        BackgroundThread.getExecutor().execute(() -> {
+            Parcel parcel = Parcel.obtain();
+            parcel.writeValue(data);
+            int dataSizeBytes = parcel.dataSize();
+            parcel.recycle();
+
+            HotwordMetricsLogger.writeHotwordDataEgressSize(eventType, dataSizeBytes,
+                    getDetectorType(), mVoiceInteractionServiceUid);
+        });
+    }
+
     /** Used to send training data.
      *
      * @hide
@@ -723,6 +764,7 @@
                     mVoiceInteractionServiceUid);
             throw e;
         }
+        logEgressSizeStats(data);
     }
 
     void initialize(@Nullable PersistableBundle options, @Nullable SharedMemory sharedMemory) {
diff --git a/services/voiceinteraction/java/com/android/server/voiceinteraction/DspTrustedHotwordDetectorSession.java b/services/voiceinteraction/java/com/android/server/voiceinteraction/DspTrustedHotwordDetectorSession.java
index 6418f3e..2938a58 100644
--- a/services/voiceinteraction/java/com/android/server/voiceinteraction/DspTrustedHotwordDetectorSession.java
+++ b/services/voiceinteraction/java/com/android/server/voiceinteraction/DspTrustedHotwordDetectorSession.java
@@ -186,6 +186,7 @@
                     if (mDebugHotwordLogging) {
                         Slog.i(TAG, "Egressed detected result: " + newResult);
                     }
+                    logEgressSizeStats(newResult);
                 }
             }
 
@@ -228,6 +229,7 @@
                     if (mDebugHotwordLogging && result != null) {
                         Slog.i(TAG, "Egressed rejected result: " + result);
                     }
+                    logEgressSizeStats(result);
                 }
             }
 
diff --git a/services/voiceinteraction/java/com/android/server/voiceinteraction/HotwordMetricsLogger.java b/services/voiceinteraction/java/com/android/server/voiceinteraction/HotwordMetricsLogger.java
index f7b66a2..ca72c85 100644
--- a/services/voiceinteraction/java/com/android/server/voiceinteraction/HotwordMetricsLogger.java
+++ b/services/voiceinteraction/java/com/android/server/voiceinteraction/HotwordMetricsLogger.java
@@ -34,6 +34,9 @@
 import static com.android.internal.util.FrameworkStatsLog.HOTWORD_DETECTOR_KEYPHRASE_TRIGGERED__DETECTOR_TYPE__NORMAL_DETECTOR;
 import static com.android.internal.util.FrameworkStatsLog.HOTWORD_DETECTOR_KEYPHRASE_TRIGGERED__DETECTOR_TYPE__TRUSTED_DETECTOR_DSP;
 import static com.android.internal.util.FrameworkStatsLog.HOTWORD_DETECTOR_KEYPHRASE_TRIGGERED__DETECTOR_TYPE__TRUSTED_DETECTOR_SOFTWARE;
+import static com.android.internal.util.FrameworkStatsLog.HOTWORD_EVENT_EGRESS_SIZE__DETECTOR_TYPE__NORMAL_DETECTOR;
+import static com.android.internal.util.FrameworkStatsLog.HOTWORD_EVENT_EGRESS_SIZE__DETECTOR_TYPE__TRUSTED_DETECTOR_DSP;
+import static com.android.internal.util.FrameworkStatsLog.HOTWORD_EVENT_EGRESS_SIZE__DETECTOR_TYPE__TRUSTED_DETECTOR_SOFTWARE;
 import static com.android.internal.util.LatencyTracker.ACTION_SHOW_VOICE_INTERACTION;
 
 import android.content.Context;
@@ -120,6 +123,16 @@
     }
 
     /**
+     * Logs hotword event egress size metrics.
+     */
+    public static void writeHotwordDataEgressSize(int eventType, long eventSize, int detectorType,
+            int uid) {
+        int metricsDetectorType = getHotwordEventEgressSizeDetectorType(detectorType);
+        FrameworkStatsLog.write(FrameworkStatsLog.HOTWORD_EGRESS_SIZE_ATOM_REPORTED,
+                eventType, eventSize, metricsDetectorType, uid);
+    }
+
+    /**
      * Starts a {@link LatencyTracker} log for the time it takes to show the
      * {@link android.service.voice.VoiceInteractionSession} system UI after a voice trigger.
      *
@@ -224,4 +237,15 @@
                 return AUDIO_EGRESS_NORMAL_DETECTOR;
         }
     }
+
+    private static int getHotwordEventEgressSizeDetectorType(int detectorType) {
+        switch (detectorType) {
+            case HotwordDetector.DETECTOR_TYPE_TRUSTED_HOTWORD_SOFTWARE:
+                return HOTWORD_EVENT_EGRESS_SIZE__DETECTOR_TYPE__TRUSTED_DETECTOR_SOFTWARE;
+            case HotwordDetector.DETECTOR_TYPE_TRUSTED_HOTWORD_DSP:
+                return HOTWORD_EVENT_EGRESS_SIZE__DETECTOR_TYPE__TRUSTED_DETECTOR_DSP;
+            default:
+                return HOTWORD_EVENT_EGRESS_SIZE__DETECTOR_TYPE__NORMAL_DETECTOR;
+        }
+    }
 }
diff --git a/services/voiceinteraction/java/com/android/server/voiceinteraction/SoftwareTrustedHotwordDetectorSession.java b/services/voiceinteraction/java/com/android/server/voiceinteraction/SoftwareTrustedHotwordDetectorSession.java
index 2e23eff..9de7f9a 100644
--- a/services/voiceinteraction/java/com/android/server/voiceinteraction/SoftwareTrustedHotwordDetectorSession.java
+++ b/services/voiceinteraction/java/com/android/server/voiceinteraction/SoftwareTrustedHotwordDetectorSession.java
@@ -179,6 +179,7 @@
                     }
                     Slog.i(TAG, "Egressed " + HotwordDetectedResult.getUsageSize(newResult)
                             + " bits from hotword trusted process");
+                    logEgressSizeStats(newResult);
                     if (mDebugHotwordLogging) {
                         Slog.i(TAG, "Egressed detected result: " + newResult);
                     }
@@ -194,6 +195,7 @@
                         HotwordDetector.DETECTOR_TYPE_TRUSTED_HOTWORD_SOFTWARE,
                         HOTWORD_DETECTOR_KEYPHRASE_TRIGGERED__RESULT__REJECTED,
                         mVoiceInteractionServiceUid);
+                logEgressSizeStats(result);
                 // onRejected isn't allowed here, and we are not expecting it.
             }
 
diff --git a/tests/permission/src/com/android/framework/permission/tests/VibratorManagerServicePermissionTest.java b/tests/permission/src/com/android/framework/permission/tests/VibratorManagerServicePermissionTest.java
index 421ceb7..07b7338 100644
--- a/tests/permission/src/com/android/framework/permission/tests/VibratorManagerServicePermissionTest.java
+++ b/tests/permission/src/com/android/framework/permission/tests/VibratorManagerServicePermissionTest.java
@@ -50,7 +50,7 @@
 public class VibratorManagerServicePermissionTest {
 
     private static final String PACKAGE_NAME = "com.android.framework.permission.tests";
-    private static final int DISPLAY_ID = 1;
+    private static final int DEVICE_ID = 1;
     private static final CombinedVibration EFFECT =
             CombinedVibration.createParallel(
                     VibrationEffect.createOneShot(100, VibrationEffect.DEFAULT_AMPLITUDE));
@@ -107,7 +107,7 @@
     @Test
     public void testVibrateWithoutPermissionFails() throws RemoteException {
         expectSecurityException("VIBRATE");
-        mVibratorService.vibrate(Process.myUid(), DISPLAY_ID, PACKAGE_NAME, EFFECT, ATTRS,
+        mVibratorService.vibrate(Process.myUid(), DEVICE_ID, PACKAGE_NAME, EFFECT, ATTRS,
                 "testVibrate",
                 new Binder());
     }
@@ -117,7 +117,7 @@
             throws RemoteException {
         getInstrumentation().getUiAutomation().adoptShellPermissionIdentity(
                 Manifest.permission.VIBRATE);
-        mVibratorService.vibrate(Process.myUid(), DISPLAY_ID, PACKAGE_NAME, EFFECT, ATTRS,
+        mVibratorService.vibrate(Process.myUid(), DEVICE_ID, PACKAGE_NAME, EFFECT, ATTRS,
                 "testVibrate",
                 new Binder());
     }
@@ -127,7 +127,7 @@
         expectSecurityException("UPDATE_APP_OPS_STATS");
         getInstrumentation().getUiAutomation().adoptShellPermissionIdentity(
                 Manifest.permission.VIBRATE);
-        mVibratorService.vibrate(Process.SYSTEM_UID, DISPLAY_ID, "android", EFFECT, ATTRS,
+        mVibratorService.vibrate(Process.SYSTEM_UID, DEVICE_ID, "android", EFFECT, ATTRS,
                 "testVibrate",
                 new Binder());
     }
@@ -137,7 +137,7 @@
         getInstrumentation().getUiAutomation().adoptShellPermissionIdentity(
                 Manifest.permission.VIBRATE,
                 Manifest.permission.UPDATE_APP_OPS_STATS);
-        mVibratorService.vibrate(Process.SYSTEM_UID, DISPLAY_ID, "android", EFFECT, ATTRS,
+        mVibratorService.vibrate(Process.SYSTEM_UID, DEVICE_ID, "android", EFFECT, ATTRS,
                 "testVibrate",
                 new Binder());
     }
diff --git a/tools/hoststubgen/hoststubgen/helper-runtime-src/com/android/hoststubgen/hosthelper/HostTestUtils.java b/tools/hoststubgen/hoststubgen/helper-runtime-src/com/android/hoststubgen/hosthelper/HostTestUtils.java
index 25abbac..c770b9c 100644
--- a/tools/hoststubgen/hoststubgen/helper-runtime-src/com/android/hoststubgen/hosthelper/HostTestUtils.java
+++ b/tools/hoststubgen/hoststubgen/helper-runtime-src/com/android/hoststubgen/hosthelper/HostTestUtils.java
@@ -65,7 +65,7 @@
      */
     public static void onThrowMethodCalled() {
         // TODO: Maybe add call tracking?
-        throw new AssumptionViolatedException("This method is not supported on the host side");
+        throw new RuntimeException("This method is not supported on the host side");
     }
 
     /**
diff --git a/tools/hoststubgen/hoststubgen/src/com/android/hoststubgen/filters/AnnotationBasedFilter.kt b/tools/hoststubgen/hoststubgen/src/com/android/hoststubgen/filters/AnnotationBasedFilter.kt
index b133c2a..248121c 100644
--- a/tools/hoststubgen/hoststubgen/src/com/android/hoststubgen/filters/AnnotationBasedFilter.kt
+++ b/tools/hoststubgen/hoststubgen/src/com/android/hoststubgen/filters/AnnotationBasedFilter.kt
@@ -377,7 +377,7 @@
                         throw HostStubGenInternalException("Policy $policy shouldn't show up here")
                     }
 
-                    val suffix = getAnnotationField(an, "suffix") ?: return@let
+                    val suffix = getAnnotationField(an, "suffix", false) ?: "\$ravenwood"
                     val renameFrom = mn.name + suffix
                     val renameTo = mn.name
 
@@ -387,13 +387,17 @@
                     }
 
                     // This mn has "SubstituteWith". This means,
-                        // 1. Re move the "rename-to" method, so add it to substitutedMethods.
+                    // 1. Re move the "rename-to" method, so add it to substitutedMethods.
                     policiesFromSubstitution[MethodKey(renameTo, mn.desc)] =
                             FilterPolicy.Remove.withReason("substitute-to")
 
+                    // If the policy is "stub", use "stub".
+                    // Otherwise, it must be "keep" or "throw", but there's no point in using
+                    // "throw", so let's use "keep".
+                    val newPolicy = if (policy.needsInStub) policy else FilterPolicy.Keep
                     // 2. We also keep the from-to in the map.
                     policiesFromSubstitution[MethodKey(renameFrom, mn.desc)] =
-                            policy.withReason("substitute-from")
+                            newPolicy.withReason("substitute-from")
                     substituteToMethods[MethodKey(renameFrom, mn.desc)] = renameTo
 
                     log.v("Substitution found: %s%s -> %s", renameFrom, mn.desc, renameTo)
@@ -405,10 +409,11 @@
     /**
      * Return the (String) value of 'value' parameter from an annotation.
      */
-    private fun getAnnotationField(an: AnnotationNode, name: String): String? {
+    private fun getAnnotationField(an: AnnotationNode, name: String,
+                                   required: Boolean = true): String? {
         try {
             val suffix = findAnnotationValueAsString(an, name)
-            if (suffix == null) {
+            if (suffix == null && required) {
                 errors.onErrorFound("Annotation \"${an.desc}\" must have field $name")
             }
             return suffix
@@ -438,4 +443,4 @@
             return ret
         }
     }
-}
\ No newline at end of file
+}